make loop

This commit is contained in:
chai2010
2015-12-18 14:49:31 +08:00
parent 9fde1ff772
commit f9ac065e47
106 changed files with 725 additions and 725 deletions

View File

@@ -1,18 +1,18 @@
## 2.7. 作用域
一個聲明語句將程序中的實體和一個名字關聯, 比如一個函數或一個變量. 聲明的作用域是指源代碼中可以有效使用這個名字的圍.
一個聲明語句將程序中的實體和一個名字關聯, 比如一個函數或一個變量. 聲明的作用域是指源代碼中可以有效使用這個名字的圍.
不要將作用域和生命週期混爲一談. 聲明的作用域對應的是一個源代碼的文本區域; 它是一個編譯時的屬性. 一個變量的生命週期是程序運行時變量存在的有效時間段, 在此時間區域內存它可以被程序的其他部分引用. 是一個運行時的概.
不要將作用域和生命週期混爲一談. 聲明的作用域對應的是一個源代碼的文本區域; 它是一個編譯時的屬性. 一個變量的生命週期是程序運行時變量存在的有效時間段, 在此時間區域內存它可以被程序的其他部分引用. 是一個運行時的概.
語法塊是由花括弧所包含的一繫列語句, 就像函數體或循環體那樣. 語法塊內部聲明的名字是無法被外部語法塊訪問的. 語法決定了內部聲明的名字的作用域圍. 我們可以這樣理解, 語法塊可以包含其他類似組批量聲明等沒有用花括弧包含的代碼, 我們稱之爲詞滙塊. 有一個語法決爲整個源代碼, 稱爲全塊; 然後是每個包的語法決; 每個 for, if 和 switch 語句的語法決; 每個 switch 或 select 分支的 語法決; 當然也包含顯示編寫的語法塊(花括弧包含).
語法塊是由花括弧所包含的一繫列語句, 就像函數體或循環體那樣. 語法塊內部聲明的名字是無法被外部語法塊訪問的. 語法決定了內部聲明的名字的作用域圍. 我們可以這樣理解, 語法塊可以包含其他類似組批量聲明等沒有用花括弧包含的代碼, 我們稱之爲詞滙塊. 有一個語法決爲整個源代碼, 稱爲全塊; 然後是每個包的語法決; 每個 for, if 和 switch 語句的語法決; 每個 switch 或 select 分支的 語法決; 當然也包含顯示編寫的語法塊(花括弧包含).
聲明的詞法域決定了作用域圍是大還是小. 內置的類型, 函數和常量, 比如 int, len 和 true 等是在全作用域的, 可以在整個程序中直接使用. 任何在在函數外部(也就是包級作用域)聲明的名字可以在同一個包的任何Go文件訪問. 導入的包, 例如 tempconv 導入的 fmt 包, 則是對應文件級的作用域, 因此隻能在當前的文件中訪問 fmt 包, 當前包的其它文件無法訪問當前文件導入的包. 還有許多聲明, 比如 tempconv.CToF 函數中的變量 c, 則是部作用域的, 它隻能在函數內部(甚至隻能是某些部分)訪問.
聲明的詞法域決定了作用域圍是大還是小. 內置的類型, 函數和常量, 比如 int, len 和 true 等是在全作用域的, 可以在整個程序中直接使用. 任何在在函數外部(也就是包級作用域)聲明的名字可以在同一個包的任何Go文件訪問. 導入的包, 例如 tempconv 導入的 fmt 包, 則是對應文件級的作用域, 因此隻能在當前的文件中訪問 fmt 包, 當前包的其它文件無法訪問當前文件導入的包. 還有許多聲明, 比如 tempconv.CToF 函數中的變量 c, 則是部作用域的, 它隻能在函數內部(甚至隻能是某些部分)訪問.
控製流標, 例如 break, continue 或 goto 後面跟着的那種標, 則是函數級的作用域.
控製流標, 例如 break, continue 或 goto 後面跟着的那種標, 則是函數級的作用域.
一個程序可能包含多個同名的聲明, 隻有它們在不同的詞法域就沒有關繫. 例如, 你可以聲明一個部變量, 和包級的變量同名. 或者是 2.3.3節的那樣, 你可以將一個函數參數的名字聲明爲 new, 雖然內置的new是全作用域的. 但是物極必反, 如果濫用重名的特性, 可能導程序很難讀.
一個程序可能包含多個同名的聲明, 隻有它們在不同的詞法域就沒有關繫. 例如, 你可以聲明一個部變量, 和包級的變量同名. 或者是 2.3.3節的那樣, 你可以將一個函數參數的名字聲明爲 new, 雖然內置的new是全作用域的. 但是物極必反, 如果濫用重名的特性, 可能導程序很難讀.
當編譯器遇到一個名字引用, 它看起來像一個聲明, 它首先從最內層的詞法域向全的作用域査找. 如果査找失敗, 則報告 "未聲明的名字" 這樣的錯誤. 如果名字在內部和外部的塊分聲明, 則內部塊的聲明首先被找到. 在這種情況下, 內部聲明屏蔽了外部同名的聲明, 讓外部的聲明無法被訪問:
當編譯器遇到一個名字引用, 它看起來像一個聲明, 它首先從最內層的詞法域向全的作用域査找. 如果査找失敗, 則報告 "未聲明的名字" 這樣的錯誤. 如果名字在內部和外部的塊分聲明, 則內部塊的聲明首先被找到. 在這種情況下, 內部聲明屏蔽了外部同名的聲明, 讓外部的聲明無法被訪問:
```Go
func f() {}
@@ -42,9 +42,9 @@ func main() {
}
```
`x[i]``x + 'A' - 'a'` 聲明初始化的表達式中都引用了外部作用域聲明的x變量, 稍後我們會解釋這個. (註意, 後面的表達式和unicode.ToUpper不等價.)
`x[i]``x + 'A' - 'a'` 聲明初始化的表達式中都引用了外部作用域聲明的x變量, 稍後我們會解釋這個. (註意, 後面的表達式和unicode.ToUpper不等價.)
正如上面所示, 不是所有的詞法域都顯示地對應到由花括弧包含的語句; 還有一些隱含的規則. 上面的for語句創建了兩個詞法域: 花括弧包含的是顯式的部分是for的循環體, 另外一個隱式的部分則是循環的初始化部分, 比如用於迭代變量 i 的初始化. 隱式的部分的作用域還包含條件測試部分和循環後的迭代部分(i++), 當然也包含循環體.
正如上面所示, 不是所有的詞法域都顯示地對應到由花括弧包含的語句; 還有一些隱含的規則. 上面的for語句創建了兩個詞法域: 花括弧包含的是顯式的部分是for的循環體, 另外一個隱式的部分則是循環的初始化部分, 比如用於迭代變量 i 的初始化. 隱式的部分的作用域還包含條件測試部分和循環後的迭代部分(i++), 當然也包含循環體.
下面的例子同樣有三個不同的x變量, 每個聲明在不同的塊, 一個在函數體塊, 一個在for語句塊, 一個在循環體塊; 隻有兩個塊是顯式創建的:
@@ -58,7 +58,7 @@ func main() {
}
```
和彿如循環類似, if和switch語句也會在條件部分創建隱式塊, 還有它們對應的執行體塊. 下面的 if-else 測試鏈演示的 x 和 y 的作用域圍:
和彿如循環類似, if和switch語句也會在條件部分創建隱式塊, 還有它們對應的執行體塊. 下面的 if-else 測試鏈演示的 x 和 y 的作用域圍:
```Go
if x := f(); x == 0 {
@@ -73,7 +73,7 @@ fmt.Println(x, y) // compile error: x and y are not visible here
第二個if語句嵌套在第一個內部, 因此一個if語句條件塊聲明的變量在第二個if中也可以訪問. switch語句的每個分支也有類似的規則: 條件部分爲一個隱式塊, 然後每個是每個分支的主體塊.
在包級, 聲明的順序不會影響作用域圍, 因此一個先聲明的可以引用它自身或者是引用後面的一個聲明, 這可以讓我們定義一些相互嵌套或遞歸的類型或函數. 但是如果一個變量或常量遞歸引用了自身, 則會生編譯錯誤.
在包級, 聲明的順序不會影響作用域圍, 因此一個先聲明的可以引用它自身或者是引用後面的一個聲明, 這可以讓我們定義一些相互嵌套或遞歸的類型或函數. 但是如果一個變量或常量遞歸引用了自身, 則會生編譯錯誤.
在這個程序中:
@@ -85,7 +85,7 @@ f.ReadByte() // compile error: undefined f
f.Close() // compile error: undefined f
```
變量 f 的作用域隻有if語句內, 因此後面的語句將無法引入它, 將導編譯錯誤. 你可能會收到一個部變量f沒有聲明的錯誤提示, 具體錯誤信息依賴編譯器的實現.
變量 f 的作用域隻有if語句內, 因此後面的語句將無法引入它, 將導編譯錯誤. 你可能會收到一個部變量f沒有聲明的錯誤提示, 具體錯誤信息依賴編譯器的實現.
通常需要在if之前聲明變量, 這樣可以確保後面的語句依然可以訪問變量:
@@ -112,7 +112,7 @@ if f, err := os.Open(fname); err != nil {
但這不是Go推薦的做法, Go的習慣是在if中處理錯誤然後直接返迴, 這樣可以確保正常成功執行的語句不需要代碼縮進.
要特註意短的變量聲明的作用域圍, 考慮下面的程序, 它的目的是取當前的工作目録然後保存到一個包級的變量中. 這可以通過直接調用 os.Getwd 完成, 但是將這個從主邏輯中分離齣來可能會更好, 特是在需要處理錯誤的時候. 函數 log.Fatalf 打印信息, 然後調用 os.Exit(1) 終止程序.
要特註意短的變量聲明的作用域圍, 考慮下面的程序, 它的目的是取當前的工作目録然後保存到一個包級的變量中. 這可以通過直接調用 os.Getwd 完成, 但是將這個從主邏輯中分離齣來可能會更好, 特是在需要處理錯誤的時候. 函數 log.Fatalf 打印信息, 然後調用 os.Exit(1) 終止程序.
```Go
var cwd string
@@ -125,9 +125,9 @@ func init() {
}
```
雖然cwd在外部已經聲明過, 但是 `:=` 語句還是將 cwd 和 err 重新聲明爲部變量. 內部聲明的 cwd 將屏蔽外部的聲明, 因此上面的代碼不會更新包級聲明的 cwd 變量.
雖然cwd在外部已經聲明過, 但是 `:=` 語句還是將 cwd 和 err 重新聲明爲部變量. 內部聲明的 cwd 將屏蔽外部的聲明, 因此上面的代碼不會更新包級聲明的 cwd 變量.
當前的編譯器將檢測到部聲明的cwd沒有本使用, 然後報告這可能是一個錯誤, 但是這種檢測不可靠. 一些小的代碼變更, 例如增加一個部cwd的打印語句, 就可能導這種檢測失效.
當前的編譯器將檢測到部聲明的cwd沒有本使用, 然後報告這可能是一個錯誤, 但是這種檢測不可靠. 一些小的代碼變更, 例如增加一個部cwd的打印語句, 就可能導這種檢測失效.
```Go
var cwd string
@@ -141,7 +141,7 @@ func init() {
}
```
的cwd變量依然是沒有被正確初始化的, 而且看似正常的日誌輸齣更是這個BUG更加隱晦.
的cwd變量依然是沒有被正確初始化的, 而且看似正常的日誌輸齣更是這個BUG更加隱晦.
有許多方式可以避免齣現類似潛在的問題. 最直接的是通過單獨聲明err變量, 來避免使用 `:=` 的簡短聲明方式:
@@ -159,4 +159,4 @@ func init() {
我們已經看到包, 文件, 聲明和語句如何來表達一個程序結構. 在下面的兩個章節, 我們將探討數據的結構.
**譯註: 本章的詞法域和作用域概有些混淆, 需要重譯一遍.**
**譯註: 本章的詞法域和作用域概有些混淆, 需要重譯一遍.**