update tw

This commit is contained in:
chai2010
2015-12-18 10:53:03 +08:00
parent 510c741a6f
commit c66a96ee52
106 changed files with 864 additions and 864 deletions

View File

@@ -1,6 +1,6 @@
## 2.1. 命名
Go語言中的的函數名, 變量名, 常量名, 類型名, 語句段標簽名, 和 包名 等所有的命名, 都遵循一個命名規則: 一個名字必鬚以一個字母(Unicode字母)或下劃綫開頭, 後可以跟任意數量的字母,數字或下劃綫. 不大小寫字母是不的: `heapSort``Heapsort` 是兩個不的名字.
Go語言中的的函數名, 變量名, 常量名, 類型名, 語句段標簽名, 和 包名 等所有的命名, 都遵循一個命名規則: 一個名字必鬚以一個字母(Unicode字母)或下劃綫開頭, 後可以跟任意數量的字母,數字或下劃綫. 不大小寫字母是不的: `heapSort``Heapsort` 是兩個不的名字.
Go語言類似 `if``switch` 的關鍵字有25個; 關鍵字不能用於自定義名字, 隻能在特定語法中使用.
@@ -29,8 +29,8 @@ Functions: make len cap new append copy close delete
這些內部預先定義的名字不是關鍵字, 你可以在定義中重現使用它們. 在一些特殊的場景重新定義是有意義的, 但是也要註意避免引起混亂.
如果一個實體是在函數內部定義, 那它的就隻在函數內部有效. 如果是在函數外部定義, 那將在當前包的所有文件中都可以訪問. 名字的開頭字母的大小寫決定了名字在包外的可見性. 如果一個名字是大寫字母開頭的, 那它將是導齣的, 也就是可以被外部的包訪問, 例如 `fmt` 包的 `Printf` 函數就是導齣的, 可以在 `fmt` 包外部訪問. 包本身的名字一般總是用小寫字母.
如果一個實體是在函數內部定義, 那它的就隻在函數內部有效. 如果是在函數外部定義, 那將在當前包的所有文件中都可以訪問. 名字的開頭字母的大小寫決定了名字在包外的可見性. 如果一個名字是大寫字母開頭的, 那它將是導齣的, 也就是可以被外部的包訪問, 例如 `fmt` 包的 `Printf` 函數就是導齣的, 可以在 `fmt` 包外部訪問. 包本身的名字一般總是用小寫字母.
名字的長度沒有限製, 但是Go的風格是量使用短小的名字, 對於侷部變量尤其是這樣; 你會經常看到 `i` 之類的名字, 而是冗長的 `theLoopIndex`. 通常來說, 如果一個名字的作用域比較大, 生命週期較長, 那用長的名字將更有意義.
名字的長度沒有限製, 但是Go的風格是量使用短小的名字, 對於侷部變量尤其是這樣; 你會經常看到 `i` 之類的名字, 而是冗長的 `theLoopIndex`. 通常來說, 如果一個名字的作用域比較大, 生命週期較長, 那用長的名字將更有意義.
在習慣上, Go程序員推薦使用`駝峯式`命名, 當名字有幾個單詞的時優先使用大小寫分隔, 而不是優先用下劃綫分隔. 因此, 標準庫有 `QuoteRuneToASCII``parseRequestLine` 這樣的函數命名, 但是不會用 `quote_rune_to_ASCII``parse_request_line` 這樣的命名. 像 `ASCII``HTML` 這樣的縮略詞避免使用大小寫混, 它們可能被稱 `htmlEscape`, `HTMLEscape``escapeHTML`, 但不會是 `escapeHtml`.
在習慣上, Go程序員推薦使用`駝峯式`命名, 當名字有幾個單詞的時優先使用大小寫分隔, 而不是優先用下劃綫分隔. 因此, 標準庫有 `QuoteRuneToASCII``parseRequestLine` 這樣的函數命名, 但是不會用 `quote_rune_to_ASCII``parse_request_line` 這樣的命名. 像 `ASCII``HTML` 這樣的縮略詞避免使用大小寫混, 它們可能被稱 `htmlEscape`, `HTMLEscape``escapeHTML`, 但不會是 `escapeHtml`.

View File

@@ -1,9 +1,9 @@
## 2.2. 聲明
聲明定義了程序的入口以及部分或全部的屬性. Go主要有四種聲明類型: var, const, type, 和 func, 分對應 變量, 常量, 類型, 和 函數的 聲明. 這一章我們重點討論變量和類型的聲明, 第三章將討論常量的聲明, 第五章將討論函數的聲明.
聲明定義了程序的入口以及部分或全部的屬性. Go主要有四種聲明類型: var, const, type, 和 func, 分對應 變量, 常量, 類型, 和 函數的 聲明. 這一章我們重點討論變量和類型的聲明, 第三章將討論常量的聲明, 第五章將討論函數的聲明.
一個Go程序存儲在一個或多個以`.go`後綴名的文件中. 每個文件以個包的聲明開始, 以說明文件是屬於包的一部分.
包聲明之後是 import 導入聲明, 然後是包一級的類型/變量/常量/函數的聲明, 聲明的順序無關緊要. 例如, 下的例子聲明了一個常量, 一個函數和兩個變量:
一個Go程序存儲在一個或多個以`.go`後綴名的文件中. 每個文件以個包的聲明開始, 以說明文件是屬於包的一部分.
包聲明之後是 import 導入聲明, 然後是包一級的類型/變量/常量/函數的聲明, 聲明的順序無關緊要. 例如, 下的例子聲明了一個常量, 一個函數和兩個變量:
```Go
gopl.io/ch2/boiling
@@ -25,9 +25,9 @@ func main() {
其中 常量 `boilingF` 是在包一級聲明的, 然後 `f``c` 是在 main 函數內部聲明的. 在包一級聲明的名字可在整個包訪問, 而不僅僅在其聲明的文件中訪問. 相比之下, 侷部聲明的名字就隻能在函數內部很小的部分可訪問.
一個函數的聲明有一個函數名字, 參數列(由函數的調用者提供參數變量的具體值), 一個可選的返迴值列, 和包含函數語句定義的函數體. 如果函數沒有返迴值, 那返迴值列是省略的. 執行函數從函數的第一個語句開始, 但是順序執行直到遇到 renturn 返迴語言, 如果沒有返迴語句則是到函數末尾, 然後返迴到調用者.
一個函數的聲明有一個函數名字, 參數列(由函數的調用者提供參數變量的具體值), 一個可選的返迴值列, 和包含函數語句定義的函數體. 如果函數沒有返迴值, 那返迴值列是省略的. 執行函數從函數的第一個語句開始, 但是順序執行直到遇到 renturn 返迴語言, 如果沒有返迴語句則是到函數末尾, 然後返迴到調用者.
我們已經看到過很多函數的例子了, 在第五章將深入討論函數的細節, 這隻粗略說下. 下`fToC` 函數封裝了溫度轉換的邏輯, 這樣它隻需要定義一次, 就可以在多個地方多次使用. 這個例子中, main 函數就調用了兩次 `fToC` 函數, 分是使用侷部定義的兩個常量作函數參數.
我們已經看到過很多函數的例子了, 在第五章將深入討論函數的細節, 這隻粗略說下. 下`fToC` 函數封裝了溫度轉換的邏輯, 這樣它隻需要定義一次, 就可以在多個地方多次使用. 這個例子中, main 函數就調用了兩次 `fToC` 函數, 分是使用侷部定義的兩個常量作函數參數.
```Go

View File

@@ -1,6 +1,6 @@
### 2.3.1. 簡短變量聲明
在函數內部, 有一種稱簡短變量聲明的形式可用於聲明和初始化侷部變量. 以 `名字 := 達式` 方式聲明變量, 變量的類型根據達式來推導. 這函數中是三個簡短變量聲明語句(§1.4):
在函數內部, 有一種稱簡短變量聲明的形式可用於聲明和初始化侷部變量. 以 `名字 := 達式` 方式聲明變量, 變量的類型根據達式來推導. 這函數中是三個簡短變量聲明語句(§1.4):
```Go
anim := gif.GIF{LoopCount: nframes}
@@ -8,7 +8,7 @@ freq := rand.Float64() * 3.0
t := 0.0
```
簡潔和靈活性, 簡短變量聲明用於大部分的侷部變量的聲明和初始化. var 方式的聲明往往是用於需要顯示指定類型的侷部變量, 或者因稍後會被賦值而初始值無關緊要的變量.
簡潔和靈活性, 簡短變量聲明用於大部分的侷部變量的聲明和初始化. var 方式的聲明往往是用於需要顯示指定類型的侷部變量, 或者因稍後會被賦值而初始值無關緊要的變量.
```Go
@@ -27,7 +27,7 @@ i, j := 0, 1
但是這種聲明多個變量的方式隻簡易在可以提高代碼可讀性的地方使用, 比如 for 循環的初始化部分.
請記住 `:=` 是一個變量聲明, 而 `=` 是一個賦值操作. 不要混淆多個變量的聲明和元組的多重(§2.4.1), 後者是將右邊的達式值賦給左邊對應位置的變量:
請記住 `:=` 是一個變量聲明, 而 `=` 是一個賦值操作. 不要混淆多個變量的聲明和元組的多重(§2.4.1), 後者是將右邊的達式值賦給左邊對應位置的變量:
```Go
i, j = j, i // 交換 i 和 j 的值
@@ -44,9 +44,9 @@ if err != nil {
f.Close()
```
有一個比較微妙的地方: 簡短變量聲明左邊的全部變量可能不是全部都是剛剛聲明的. 如果有一些已經在相的詞法塊聲明過了(§2.7), 那簡短變量聲明對這些已經聲明過的變量就隻有賦值行了.
有一個比較微妙的地方: 簡短變量聲明左邊的全部變量可能不是全部都是剛剛聲明的. 如果有一些已經在相的詞法塊聲明過了(§2.7), 那簡短變量聲明對這些已經聲明過的變量就隻有賦值行了.
在下的代碼中, 第一個語句聲明了 in 和 err 變量. 第二個語句隻聲明了 out, 然後對已經聲明的 err 進行賦值.
在下的代碼中, 第一個語句聲明了 in 和 err 變量. 第二個語句隻聲明了 out, 然後對已經聲明的 err 進行賦值.
```Go
in, err := os.Open(infile)
@@ -64,6 +64,6 @@ f, err := os.Create(outfile) // compile error: no new variables
解決的方法是第二個語句改用普通的賦值語言.
簡短變量聲明隻有對在變量已經在級詞法域聲明過的變量纔和賦值操作等, 如果變量是在外部詞法域聲明了, 那將會聲明一個新變量. 我們在本章後將會看到類似的例子.
簡短變量聲明隻有對在變量已經在級詞法域聲明過的變量纔和賦值操作等, 如果變量是在外部詞法域聲明了, 那將會聲明一個新變量. 我們在本章後將會看到類似的例子.

View File

@@ -1,10 +1,10 @@
### 2.3.2 指鍼
一個變量對應一個保存了一個值的內存空間. 變量在聲明語句創建時綁定一個名字, 比如 x, 但是還有很多變量始終以達式方式引入, 例如 x[i] 或 x.f. 所有這些達式都讀取一個變量的值, 除非它們是齣現在賦值語句的左邊, 這種時候是給變量賦予一個新值.
一個變量對應一個保存了一個值的內存空間. 變量在聲明語句創建時綁定一個名字, 比如 x, 但是還有很多變量始終以達式方式引入, 例如 x[i] 或 x.f. 所有這些達式都讀取一個變量的值, 除非它們是齣現在賦值語句的左邊, 這種時候是給變量賦予一個新值.
一個指鍼的值是一個變量的地址. 一個指鍼對應變量在內存中的存儲位置. 不是每一個值都會有一個地址, 但是對於每一個變量必然有對應的地址. 通過指鍼, 我們可以直接讀或更新變量的值, 而不需要知道變量的名字(卽使變量有名字的話).
一個指鍼的值是一個變量的地址. 一個指鍼對應變量在內存中的存儲位置. 不是每一個值都會有一個地址, 但是對於每一個變量必然有對應的地址. 通過指鍼, 我們可以直接讀或更新變量的值, 而不需要知道變量的名字(卽使變量有名字的話).
如果這樣聲明一個變量 `var x int`, 那 `&x` 達式(x的地址)將產生一個指整數變量的指鍼, 對應的數據類型是 `*int`, 稱之 "指 int 的指鍼". 如果指鍼名字 p, 那可以說 "p 指鍼指 x", 或者說 "p 指鍼保存了 x 變量的地址". `*p` 對應 p 指鍼指的變量的值. `*p` 達式讀取變量的值, int 類型, 時因 `*p` 對應一個變量, 所以可以齣現在賦值語句的左邊, 用於更新所指的變量的值.
如果這樣聲明一個變量 `var x int`, 那 `&x` 達式(x的地址)將產生一個指整數變量的指鍼, 對應的數據類型是 `*int`, 稱之 "指 int 的指鍼". 如果指鍼名字 p, 那可以說 "p 指鍼指 x", 或者說 "p 指鍼保存了 x 變量的地址". `*p` 對應 p 指鍼指的變量的值. `*p` 達式讀取變量的值, int 類型, 時因 `*p` 對應一個變量, 所以可以齣現在賦值語句的左邊, 用於更新所指的變量的值.
```Go
x := 1
@@ -14,18 +14,18 @@ fmt.Println(*p) // "1"
fmt.Println(x) // "2"
```
對於聚類型, 比如結構體的每個字段, 或者是數組的每個元素, 也都是對應一個變量, 且可以被取地址.
對於聚類型, 比如結構體的每個字段, 或者是數組的每個元素, 也都是對應一個變量, 且可以被取地址.
變量有時候被稱可尋址的值. 如果變量由達式臨時生成, 那麼錶達式必鬚能接受 `&` 取地址操作.
變量有時候被稱可尋址的值. 如果變量由達式臨時生成, 那麽表達式必鬚能接受 `&` 取地址操作.
任何類型的指鍼的零值都是 nil. 如果 `p != nil` 測試眞, 那 p 是指變量. 指鍼直接也是可以進行相等測試的, 隻有當它們指曏衕一個變量或全部是 nil 時纔相等.
任何類型的指鍼的零值都是 nil. 如果 `p != nil` 測試眞, 那 p 是指變量. 指鍼直接也是可以進行相等測試的, 隻有當它們指向同一個變量或全部是 nil 時纔相等.
```Go
var x, y int
fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
```
在Go語言中, 返迴函數中侷部變量的地址是安全的. 例如下的代碼, 調用 f 函數時創建 v 侷部變量, 在地址被返迴之後依然有效, 因指鍼 p 依然引用這個變量.
在Go語言中, 返迴函數中侷部變量的地址是安全的. 例如下的代碼, 調用 f 函數時創建 v 侷部變量, 在地址被返迴之後依然有效, 因指鍼 p 依然引用這個變量.
```Go
var p = f()
@@ -36,13 +36,13 @@ func f() *int {
}
```
每次調用 f 函數都將返迴不的結果:
每次調用 f 函數都將返迴不的結果:
```Go
fmt.Println(f() == f()) // "false"
```
指鍼包含了一個變量的地址, 因此將指鍼作參數調用函數, 將可以在函數中通過指鍼更新變量的值. 例如這個通過指鍼來更新變量的值, 然後返迴更新後的值, 可用在一個達式中:
指鍼包含了一個變量的地址, 因此將指鍼作參數調用函數, 將可以在函數中通過指鍼更新變量的值. 例如這個通過指鍼來更新變量的值, 然後返迴更新後的值, 可用在一個達式中:
```Go
func incr(p *int) int {
@@ -55,9 +55,9 @@ incr(&v) // side effect: v is now 2
fmt.Println(incr(&v)) // "3" (and v is 3)
```
每次我們對變量取地址, 或者復製指鍼, 我們都創建了變量的新的名. 例如, *p 是 變量 v 的名. 指鍼特有加載的地方在於我們可以不用名字而訪問一個變量, 但是這是一把雙刃劍: 要找到一個變量的所有訪問者, 我們必鬚知道變量全部的名. 不僅僅是指鍼創建名, 很多其他引用類型也會創建名, 例如 切片, 字典和管道, 甚至結構體, 數組和接口都會創建所引用變量的名.
每次我們對變量取地址, 或者復製指鍼, 我們都創建了變量的新的名. 例如, *p 是 變量 v 的名. 指鍼特有加載的地方在於我們可以不用名字而訪問一個變量, 但是這是一把雙刃劍: 要找到一個變量的所有訪問者, 我們必鬚知道變量全部的名. 不僅僅是指鍼創建名, 很多其他引用類型也會創建名, 例如 切片, 字典和管道, 甚至結構體, 數組和接口都會創建所引用變量的名.
指鍼是 flag 包的關鍵, 它使用命令行參數來設置對應的變量, 而這些分佈在整個程序中. 了說明這一點, 在早些的echo版本中, 包含了兩個可選的命令行參數: `-n` 用於忽略行尾的換行符, `-s sep` 用於指定分隔字符(默認是空格). 這是第四個版本, 對應包 gopl.io/ch2/echo4.
指鍼是 flag 包的關鍵, 它使用命令行參數來設置對應的變量, 而這些分佈在整個程序中. 了說明這一點, 在早些的echo版本中, 包含了兩個可選的命令行參數: `-n` 用於忽略行尾的換行符, `-s sep` 用於指定分隔字符(默認是空格). 這是第四個版本, 對應包 gopl.io/ch2/echo4.
```Go
gopl.io/ch2/echo4
@@ -82,7 +82,7 @@ func main() {
}
```
`flag.Bool` 函數調用創建了一個新的佈爾型標誌參數變量. 它有三個屬性: 第一個是的名字"n", 然後是標誌的默認值(這是false), 最後是對應的描述信息. 如果用戶輸入了無效的標誌參數, 或者輸入 `-h``-help` 標誌參數, 將打印標誌參數的名字, 默認值和描述信息. 類似的, flag.String 用於創建一個字符串類型的標誌參數變量, 樣包含參數名, 默認值, 和描述信息. 變量 `sep``n` 是一個指標誌參數變量的指鍼, 因此必鬚用 *sep 和 *n 的方式間接引用.
`flag.Bool` 函數調用創建了一個新的佈爾型標誌參數變量. 它有三個屬性: 第一個是的名字"n", 然後是標誌的默認值(這是false), 最後是對應的描述信息. 如果用戶輸入了無效的標誌參數, 或者輸入 `-h``-help` 標誌參數, 將打印標誌參數的名字, 默認值和描述信息. 類似的, flag.String 用於創建一個字符串類型的標誌參數變量, 樣包含參數名, 默認值, 和描述信息. 變量 `sep``n` 是一個指標誌參數變量的指鍼, 因此必鬚用 *sep 和 *n 的方式間接引用.
當程序運行時, 必鬚在標誌參數變量使用之前調用 flag.Parse 函數更新標誌參數變量的值(之前是默認值). 非標誌參數的普通類型參數可以用 flag.Args() 訪問, 對應一個 字符串切片. 如果 flag.Parse 解析遇到錯誤, 將打印提示信息, 然後調用 os.Exit(2) 終止程序.

View File

@@ -1,19 +1,19 @@
### 2.3.3 new 函數
另一個創建變量的方法是用內建的 new 函數. 達式 `new(T)` 創建一個T類型的匿名變量, 初始化T類型的零值, 返迴返迴變量地址, 返迴指鍼類型 `*T`.
另一個創建變量的方法是用內建的 new 函數. 達式 `new(T)` 創建一個T類型的匿名變量, 初始化T類型的零值, 返迴返迴變量地址, 返迴指鍼類型 `*T`.
```Go
p := new(int) // p, *int 類型, 指匿名的 int 變量
p := new(int) // p, *int 類型, 指匿名的 int 變量
fmt.Println(*p) // "0"
*p = 2 // 設置 int 匿名變量的值 2
*p = 2 // 設置 int 匿名變量的值 2
fmt.Println(*p) // "2"
```
從 new 創建變量和普通聲明方式創建變量沒有什麼區彆, 除了不需要聲明一個臨時變量的名字外, 我們還可以在達式中使用 `new(T)`. 換言之, new 類似是一種語法醣, 而不是一個新的基礎概唸.
從 new 創建變量和普通聲明方式創建變量沒有什麽區別, 除了不需要聲明一個臨時變量的名字外, 我們還可以在達式中使用 `new(T)`. 換言之, new 類似是一種語法醣, 而不是一個新的基礎概唸.
的兩個 newInt 函數有着相的行:
的兩個 newInt 函數有着相的行:
```Go
func newInt() *int { func newInt() *int {
@@ -22,7 +22,7 @@ func newInt() *int { func newInt() *int {
}
```
每次調用 new 都是返迴一個新的變量的地址, 因此下兩個地址是不的:
每次調用 new 都是返迴一個新的變量的地址, 因此下兩個地址是不的:
```Go
p := new(int)
@@ -30,15 +30,15 @@ q := new(int)
fmt.Println(p == q) // "false"
```
當然也有特殊情況: 如果兩個類型都是空的, 也就是說類型的大小是0, 例如 `struct{}``[0]int`, 有可能有相的地址(依賴具體的語言實現).
當然也有特殊情況: 如果兩個類型都是空的, 也就是說類型的大小是0, 例如 `struct{}``[0]int`, 有可能有相的地址(依賴具體的語言實現).
new 函數使用相對比較少, 因對應結構體來說, 可以直接用字量語法創建新變量的方法更靈活 (§4.4.1).
new 函數使用相對比較少, 因對應結構體來說, 可以直接用字量語法創建新變量的方法更靈活 (§4.4.1).
由於 new 隻是一個預定義的函數, 它不是一個關鍵字, 因此我們可以將 new 重新定義為彆的類型. 例如:
由於 new 隻是一個預定義的函數, 它不是一個關鍵字, 因此我們可以將 new 重新定義爲別的類型. 例如:
```Go
func delta(old, new int) int { return new - old }
```
new 被定義 int 類型的變量, 因此 delta 函數內部就無法在使用內置的 new 函數了.
new 被定義 int 類型的變量, 因此 delta 函數內部就無法在使用內置的 new 函數了.

View File

@@ -1,8 +1,8 @@
### 2.3.4. 變量的生命週期
變量的生命週期指的是程序運行期間變量存在的有效時間間隔. 包級聲明的變量的生命週期和程序的生命週期是一緻的. 相比之下, 侷部變量的聲明週期是動態的: 從每次創建一個新變量的聲明語句被執行開始, 直到變量不在被引用止, 然後變量的存儲空間可能被迴收. 函數的參數變量和返迴值變量都是侷部變量. 它們在函數每次被調用的時候創建.
變量的生命週期指的是程序運行期間變量存在的有效時間間隔. 包級聲明的變量的生命週期和程序的生命週期是一緻的. 相比之下, 侷部變量的聲明週期是動態的: 從每次創建一個新變量的聲明語句被執行開始, 直到變量不在被引用止, 然後變量的存儲空間可能被迴收. 函數的參數變量和返迴值變量都是侷部變量. 它們在函數每次被調用的時候創建.
例如, 下是從 1.4 節的 Lissajous 程序摘的代碼片段:
例如, 下是從 1.4 節的 Lissajous 程序摘的代碼片段:
```Go
for t := 0.0; t < cycles*2*math.Pi; t += res {
@@ -15,11 +15,11 @@ for t := 0.0; t < cycles*2*math.Pi; t += res {
在每次循環的開始創建變量 t, 然後在每次循環迭代中創建 x 和 y.
垃圾收集器是如何知道一個變量是何時可以被迴收的呢? 這我們先避開完整的技細節, 但是基本的思路是, 從每個包級的變量和每個當前運行函數的每一個侷部變量開始, 通過指鍼或引用的路徑, 是否可以找到該變量. 如果不存在這樣的路徑, 那說明該變量是不可達的, 也就是說它不會影響其餘的計算.
垃圾收集器是如何知道一個變量是何時可以被迴收的呢? 這我們先避開完整的技細節, 但是基本的思路是, 從每個包級的變量和每個當前運行函數的每一個侷部變量開始, 通過指鍼或引用的路徑, 是否可以找到該變量. 如果不存在這樣的路徑, 那說明該變量是不可達的, 也就是說它不會影響其餘的計算.
一個變量的聲明週期隻取決於是否可達, 因此一個循環迭代內部的侷部變量的生命週期可能超齣其侷部作用域. 它可能在函數返迴之後依然存在.
一個變量的聲明週期隻取決於是否可達, 因此一個循環迭代內部的侷部變量的生命週期可能超齣其侷部作用域. 它可能在函數返迴之後依然存在.
編譯器會選擇在棧上還是在堆上分配侷部變量的存儲空間, 但可能令人驚訝的是, 這個選擇不是由 var 或 new 來決定的.
編譯器會選擇在棧上還是在堆上分配侷部變量的存儲空間, 但可能令人驚訝的是, 這個選擇不是由 var 或 new 來決定的.
```Go
var global *int
@@ -31,10 +31,10 @@ func f() { func g() {
}
```
的 x 必鬚在堆上分配, 因它在函數退齣後依然可以通過包的 global 變量找到, 雖然它是在函數內部定義的; 我們說這個 x 侷部變量從 函數 f 中逃逸了. 相反, 當 g 函數返迴時, 變量 `*y` 將是不可達的, 也就是可以被迴收的. 因此, `*y` 沒有從 函數 g 逃逸, 編譯器可以選擇在棧上分配 `*y` 的存儲空間, 雖然這用的是 new 方式.
在任何時候, 你不需了編寫正確的代碼而要考慮變量的逃逸行, 要記住的是, 逃逸的變量需要額外分配內存, 時對性能的優化會產生一定的影響.
的 x 必鬚在堆上分配, 因它在函數退齣後依然可以通過包的 global 變量找到, 雖然它是在函數內部定義的; 我們說這個 x 侷部變量從 函數 f 中逃逸了. 相反, 當 g 函數返迴時, 變量 `*y` 將是不可達的, 也就是可以被迴收的. 因此, `*y` 沒有從 函數 g 逃逸, 編譯器可以選擇在棧上分配 `*y` 的存儲空間, 雖然這用的是 new 方式.
在任何時候, 你不需了編寫正確的代碼而要考慮變量的逃逸行, 要記住的是, 逃逸的變量需要額外分配內存, 時對性能的優化會產生一定的影響.
垃圾收集器對編寫正確的代碼是一個鉅大的幫助, 但不是說你完全不用考慮內存了. 你雖然不需要顯式地分配和釋放內存, 但是要編寫高效的程序你還是需要知道變量的生命週期. 例如, 將指短生命週期對象的指鍼保存到具有長生命週期的對象中, 特是全侷變量時, 會阻止對短生命週期對象的垃圾迴收.
垃圾收集器對編寫正確的代碼是一個鉅大的幫助, 但不是說你完全不用考慮內存了. 你雖然不需要顯式地分配和釋放內存, 但是要編寫高效的程序你還是需要知道變量的生命週期. 例如, 將指短生命週期對象的指鍼保存到具有長生命週期的對象中, 特是全侷變量時, 會阻止對短生命週期對象的垃圾迴收.

View File

@@ -1,31 +1,31 @@
## 2.3. 變量
var 聲明可以創建一個特定類型的變量, 然後給變量附加一個名字, 且設置變量的初始值. 變量聲明的一般語法:
var 聲明可以創建一個特定類型的變量, 然後給變量附加一個名字, 且設置變量的初始值. 變量聲明的一般語法:
```Go
var name type = 達式
var name type = 達式
```
其中類型或 `= 達式` 可以省略其中的一個. 如果省略的是類型信息, 那將根據初始化達式類推導類型信息. 如果初始化達式被省略, 那將用零值初始化變量. 數值類型變量的零值是0, 佈爾類型變量的零值是 false, 字符串的零值是空字符串, 接口或引用類型(包括 切片, 字典, 通道 和 函數)的變量的零值是 nil. 數組或結構體等聚類型的零值是每個元素或字段都是零值.
其中類型或 `= 達式` 可以省略其中的一個. 如果省略的是類型信息, 那將根據初始化達式類推導類型信息. 如果初始化達式被省略, 那將用零值初始化變量. 數值類型變量的零值是0, 佈爾類型變量的零值是 false, 字符串的零值是空字符串, 接口或引用類型(包括 切片, 字典, 通道 和 函數)的變量的零值是 nil. 數組或結構體等聚類型的零值是每個元素或字段都是零值.
零值機製可以確保每個聲明的變量總是有一個良好定義的值, 在 Go 中不存在未初始化的變量. 這個可以簡化很多代碼, 在沒有增加額外工作的前提下確保邊界條件下的理行. 例如:
零值機製可以確保每個聲明的變量總是有一個良好定義的值, 在 Go 中不存在未初始化的變量. 這個可以簡化很多代碼, 在沒有增加額外工作的前提下確保邊界條件下的理行. 例如:
```Go
var s string
fmt.Println(s) // ""
```
這段代碼將打印一個空字符串, 而不是導緻錯誤或產生不可預知的行. Go 程序員經常讓一些聚類型的零值也有意義, 這樣不管任何類型的變量總是有一個理的零值狀態.
這段代碼將打印一個空字符串, 而不是導緻錯誤或產生不可預知的行. Go 程序員經常讓一些聚類型的零值也有意義, 這樣不管任何類型的變量總是有一個理的零值狀態.
可以在一個聲明語句中時聲明一組變量, 或用一組初始化達式聲明初始化一組變量.
如果省略每個變量的類型, 將可以聲明多個不類型的變量(類型由初始化達式推導):
可以在一個聲明語句中時聲明一組變量, 或用一組初始化達式聲明初始化一組變量.
如果省略每個變量的類型, 將可以聲明多個不類型的變量(類型由初始化達式推導):
```Go
var i, j, k int // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string
```
初始化可以是字量或任意的達式. 包級聲明的變量會在 main 函數執行前完成初始化 (§2.6.2), 侷部變量將在聲明語句被執行到的時候初始化.
初始化可以是字量或任意的達式. 包級聲明的變量會在 main 函數執行前完成初始化 (§2.6.2), 侷部變量將在聲明語句被執行到的時候初始化.
一組變量的初始化也可以通過調用一個函數, 由函數返迴的多個返迴值初始化:

View File

@@ -1,6 +1,6 @@
### 2.4.1. 元組賦值
元組賦值是另一種形式的賦值語句, 允許時更新多個變量的值. 在賦值之前, 賦值語句右邊的所有達式將會先進行求值, 然後再統一更新左邊變量的值. 這對於處理有些時齣現在元組賦值語句左右兩邊的變量很有幫助, 例如我們可以這樣交換兩個變量的值:
元組賦值是另一種形式的賦值語句, 允許時更新多個變量的值. 在賦值之前, 賦值語句右邊的所有達式將會先進行求值, 然後再統一更新左邊變量的值. 這對於處理有些時齣現在元組賦值語句左右兩邊的變量很有幫助, 例如我們可以這樣交換兩個變量的值:
```Go
x, y = y, x
@@ -31,22 +31,22 @@ func fib(n int) int {
}
```
元組賦值也可以使一繫列瑣碎賦值更緊湊(譯註: 特是在for循環的初始化部分),
元組賦值也可以使一繫列瑣碎賦值更緊湊(譯註: 特是在for循環的初始化部分),
```Go
i, j, k = 2, 3, 5
```
但如果達式太復雜的話, 應該量避免元組賦值; 因一個個單獨的賦值語句的可讀性會更好.
但如果達式太復雜的話, 應該量避免元組賦值; 因一個個單獨的賦值語句的可讀性會更好.
某些達式會產生多個值, 比如調用一個有多個返迴值的函數.
當這樣一個函數調用齣現在元組賦值右邊的達式中時(譯註: 右邊不能再有其他達式), 左邊變量的數目必鬚和右邊一緻.
某些達式會產生多個值, 比如調用一個有多個返迴值的函數.
當這樣一個函數調用齣現在元組賦值右邊的達式中時(譯註: 右邊不能再有其他達式), 左邊變量的數目必鬚和右邊一緻.
```Go
f, err = os.Open("foo.txt") // function call returns two values
```
通常, 這類函數會用額外的返迴值達某種錯誤類型, 例如 os.Open 是返迴一個 error 類型的錯誤, 還有一些是返迴佈爾值, 通常被稱ok. 在稍後我們看到的三個操作都是類似的行. 如果 字典査找(§4.3), 類型斷言(§7.10), 或 通道接收(§8.4.2) 齣現在賦值語句的右邊, 它們都將產生兩個結果, 有一個額外的佈爾結果示操作是否成功:
通常, 這類函數會用額外的返迴值達某種錯誤類型, 例如 os.Open 是返迴一個 error 類型的錯誤, 還有一些是返迴佈爾值, 通常被稱ok. 在稍後我們看到的三個操作都是類似的行. 如果 字典査找(§4.3), 類型斷言(§7.10), 或 通道接收(§8.4.2) 齣現在賦值語句的右邊, 它們都將產生兩個結果, 有一個額外的佈爾結果示操作是否成功:
```Go
v, ok = m[key] // map lookup

View File

@@ -1,12 +1,12 @@
### 2.4.2. 可賦值性
賦值語句是顯示的賦值形式, 但是程序中還有很多地方會送隱式的賦值行: 函數調用將隱式地將調用參數的值賦值給函數的參數變量, 一個返迴語句將隱式地將返迴操作的值賦值給結果變量, 一個復類型的字量(§4.2)也會產生賦值行. 例如下的語句:
賦值語句是顯示的賦值形式, 但是程序中還有很多地方會送隱式的賦值行: 函數調用將隱式地將調用參數的值賦值給函數的參數變量, 一個返迴語句將隱式地將返迴操作的值賦值給結果變量, 一個復類型的字量(§4.2)也會產生賦值行. 例如下的語句:
```Go
medals := []string{"gold", "silver", "bronze"}
```
隱式地對切片的每個元素進行賦值操作, 類似這樣寫的行:
隱式地對切片的每個元素進行賦值操作, 類似這樣寫的行:
```Go
medals[0] = "gold"
@@ -14,15 +14,15 @@ medals[1] = "silver"
medals[2] = "bronze"
```
字典和管道的元素, 雖然不是普通的變量, 但是也有類似的隱式賦值行.
字典和管道的元素, 雖然不是普通的變量, 但是也有類似的隱式賦值行.
不管是隱式還是顯示地賦值, 在賦值語句坐標的變量和右邊最終的求到的值必鬚有相的數據類型. 更直白地說, 隻有右邊的值對於左邊的變量是可賦值的, 賦值語句纔是允許的.
不管是隱式還是顯示地賦值, 在賦值語句坐標的變量和右邊最終的求到的值必鬚有相的數據類型. 更直白地說, 隻有右邊的值對於左邊的變量是可賦值的, 賦值語句纔是允許的.
可賦值性的規則對於不類型有不要求, 對每個新類型有關的地方我們會專門解釋.
可賦值性的規則對於不類型有不要求, 對每個新類型有關的地方我們會專門解釋.
對於目前我們已經討論過的類型, 它的規則是簡單的: 類型必鬚完全匹配, nil 可以賦值給任何指鍼或引用類型的變量. 常量(§3.6)有更靈活的規則, 這樣可以避免不必要的顯示類型轉換.
對於兩個值是否可以用 `==``!=` 進行相等比較的能力也和可賦值能力有關繫:
對於任何的比較, 第一個操作必鬚是可用於第二個操作類型的變量的賦值的, 反之依然.
和前一樣, 我們會對每個新類型比較有關的地方會做專門解釋.
和前一樣, 我們會對每個新類型比較有關的地方會做專門解釋.

View File

@@ -1,6 +1,6 @@
## 2.4. 賦值
使用賦值語句可以更新一個變量的值, 最簡單的賦值語句是將要被賦值的變量放在 `=` 的左邊, 新值的達式放在 `=` 右邊.
使用賦值語句可以更新一個變量的值, 最簡單的賦值語句是將要被賦值的變量放在 `=` 的左邊, 新值的達式放在 `=` 右邊.
```Go
x = 1 // 命令變量的賦值
@@ -9,13 +9,13 @@ person.name = "bob" // 結構體字段賦值
count[x] = count[x] * scale // 數組, 切片 或 字典的 元素賦值
```
特定的賦值語句和二元算朮復閤操作有一個簡潔形式, 例如上最後的語句可以重寫:
特定的賦值語句和二元算術復合操作有一個簡潔形式, 例如上最後的語句可以重寫:
```Go
count[x] *= scale
```
這樣可以省去對變量達式的重復計算.
這樣可以省去對變量達式的重復計算.
數值變量也可以支持 `++` 遞增和 `--` 遞減語句:

View File

@@ -1,23 +1,23 @@
## 2.5. 類型聲明
變量或達式的類型定義了對應存儲值的特徵, 例如數值的存儲大小(或者是元素的bit個數), 它們在內部是如何達的, 是否支持一些操作符, 以及它們自己關聯的方法集,
變量或達式的類型定義了對應存儲值的特徵, 例如數值的存儲大小(或者是元素的bit個數), 它們在內部是如何達的, 是否支持一些操作符, 以及它們自己關聯的方法集,
在任何程序中都會有一些變量有着相的內部實現, 但是示完全不的概唸.
例如, int 類型的變量可以用來示一個循環的迭代索引, 或者一個時間戳, 或者一個文件描述符, 或者一個月份; 一個 float64 類型的變量可以用來示每秒幾米的速度, 或者是不溫度單位的溫度;
一個字符串可以用來示一個密碼或者一個色的名稱.
在任何程序中都會有一些變量有着相的內部實現, 但是示完全不的概唸.
例如, int 類型的變量可以用來示一個循環的迭代索引, 或者一個時間戳, 或者一個文件描述符, 或者一個月份; 一個 float64 類型的變量可以用來示每秒幾米的速度, 或者是不溫度單位的溫度;
一個字符串可以用來示一個密碼或者一個色的名稱.
一個類型的聲明創建了一個新的類型名稱, 和現有類型具有相的底層結構.
新命名的類型提供了一個方法, 用來分隔不概唸的類型, 卽使它們底層類型相也是不兼容的.
一個類型的聲明創建了一個新的類型名稱, 和現有類型具有相的底層結構.
新命名的類型提供了一個方法, 用來分隔不概唸的類型, 卽使它們底層類型相也是不兼容的.
```Go
type name underlying-type
```
類型的聲明一般齣現在包級, 因此如果新創建的類型名字名字的首字符大寫, 則在外部包也可以使用.
類型的聲明一般齣現在包級, 因此如果新創建的類型名字名字的首字符大寫, 則在外部包也可以使用.
了說明類型聲明, 我們將不溫度單位分定義為不衕的類型:
了說明類型聲明, 我們將不溫度單位分定義爲不同的類型:
了說明類型聲明,讓我們把不溫度範圍分為不衕的類型:
了說明類型聲明,讓我們把不溫度範圍分爲不同的類型:
```Go
gopl.io/ch2/tempconv0
@@ -30,7 +30,7 @@ type Celsius float64 // 攝氏溫度
type Fahrenheit float64 // 華氏溫度
const (
AbsoluteZeroC Celsius = -273.15 // 對零度
AbsoluteZeroC Celsius = -273.15 // 對零度
FreezingC Celsius = 0 // 結冰點溫度
BoilingC Celsius = 100 // 沸水問題
)
@@ -40,16 +40,16 @@ func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
```
這個包定義了兩種類型, Celsius 和 Fahrenheit 分對應不的溫度單位. 它們都有着相的底層類型 float64, 但是它們是不的數據類型, 因此它們不可以被相互比較或混在一個達式計算. 可以區分類型, 可以避免一些像無意中結單位的溫度進行計算的錯誤; 因需要一個類似 Celsius(t) 或 Fahrenheit(t) 顯式的轉型操作纔能將 float64 轉對應的類型. Celsius(t) 和 Fahrenheit(t) 是類型轉換操作, 不是函數調用. 類型轉換不會改變值本身, 但是會使它們的語義生變化. 另一方, 函數 CToF 和 FToC 則是對兩個不的溫度單位進行轉換, 它們會返迴不的值.
這個包定義了兩種類型, Celsius 和 Fahrenheit 分對應不的溫度單位. 它們都有着相的底層類型 float64, 但是它們是不的數據類型, 因此它們不可以被相互比較或混在一個達式計算. 可以區分類型, 可以避免一些像無意中結單位的溫度進行計算的錯誤; 因需要一個類似 Celsius(t) 或 Fahrenheit(t) 顯式的轉型操作纔能將 float64 轉對應的類型. Celsius(t) 和 Fahrenheit(t) 是類型轉換操作, 不是函數調用. 類型轉換不會改變值本身, 但是會使它們的語義生變化. 另一方, 函數 CToF 和 FToC 則是對兩個不的溫度單位進行轉換, 它們會返迴不的值.
對於每一個類型 T, 都有一個對應的類型轉換操作 T(x), 用於將 x 轉 T 類型.
隻有當兩個類型的底層基礎類型相時, 纔允許這種轉型操作, 或者是兩者都是指曏相衕底層結構的指鍼類型,
這些轉換隻改變類型而不會影響值本身. 如果x是可以賦值給T類型的, 那x必然可以被轉T類型, 但是一般沒有必要.
對於每一個類型 T, 都有一個對應的類型轉換操作 T(x), 用於將 x 轉 T 類型.
隻有當兩個類型的底層基礎類型相時, 纔允許這種轉型操作, 或者是兩者都是指向相同底層結構的指鍼類型,
這些轉換隻改變類型而不會影響值本身. 如果x是可以賦值給T類型的, 那x必然可以被轉T類型, 但是一般沒有必要.
數值類型之間的轉型也是允許的, 且在字符串和一些特定切片之間也是可以轉換的, 在下一章我們會看到這樣的例子. 這類轉換可能改變值的現. 例如, 將一個浮點數轉整數將丟棄小數部分, 將一個字符串轉 []byte 切片將拷貝一個字符串數據的副本. 在任何情況下, 運行時不會送轉換失敗的錯誤(譯註: 錯誤隻會生在編譯階段).
數值類型之間的轉型也是允許的, 且在字符串和一些特定切片之間也是可以轉換的, 在下一章我們會看到這樣的例子. 這類轉換可能改變值的現. 例如, 將一個浮點數轉整數將丟棄小數部分, 將一個字符串轉 []byte 切片將拷貝一個字符串數據的副本. 在任何情況下, 運行時不會送轉換失敗的錯誤(譯註: 錯誤隻會生在編譯階段).
底層數據類型決定了內部結構和達方式, 也包決定是否可以像底層類型一樣對內置運算符的支持.
這意味着, Celsius 和 Fahrenheit 類型的算朮行為和底層的 float64 類型一樣, 正如你所期望的.
底層數據類型決定了內部結構和達方式, 也包決定是否可以像底層類型一樣對內置運算符的支持.
這意味着, Celsius 和 Fahrenheit 類型的算術行爲和底層的 float64 類型一樣, 正如你所期望的.
```Go
fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C
@@ -58,8 +58,8 @@ fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °F
fmt.Printf("%g\n", boilingF-FreezingC) // compile error: type mismatch
```
比較運算符 `==``<` 也可以用來比較一個命名類型的變量和另一個有相類型的變量或相的底層類型的值做比較.
但是如果兩個值有着不的類型, 則不能直接進行比較:
比較運算符 `==``<` 也可以用來比較一個命名類型的變量和另一個有相類型的變量或相的底層類型的值做比較.
但是如果兩個值有着不的類型, 則不能直接進行比較:
```Go
var c Celsius
@@ -70,19 +70,19 @@ fmt.Println(c == f) // compile error: type mismatch
fmt.Println(c == Celsius(f)) // "true"!
```
註意最後那個語句. 管看起來想函數調用, 但是Celsius(f)類型轉換, 不會改變值, 它僅僅是改變值的類型而已. 測試眞的原因是因 c 和 g 都是零值.
註意最後那個語句. 管看起來想函數調用, 但是Celsius(f)類型轉換, 不會改變值, 它僅僅是改變值的類型而已. 測試眞的原因是因 c 和 g 都是零值.
一個命名的類型可以提供符號方便, 特是可以避免一遍又一遍地書寫復雜類型(譯註: 例如用匿名的結構體定義變量). 雖然對於像float64這種簡單的底層類型沒有簡潔很多, 但是如果是復雜的類型將會簡潔很多, 正如我們卽將討論的結構體類型:
一個命名的類型可以提供符號方便, 特是可以避免一遍又一遍地書寫復雜類型(譯註: 例如用匿名的結構體定義變量). 雖然對於像float64這種簡單的底層類型沒有簡潔很多, 但是如果是復雜的類型將會簡潔很多, 正如我們卽將討論的結構體類型:
命名類型還可以該類型的值定義新的行. 這些行為錶示為一組關聯到類型的函數, 我們成類型的方法集. 我們將在第六章討論方法的細節, 這值說寫簡單用法.
命名類型還可以該類型的值定義新的行. 這些行爲表示爲一組關聯到類型的函數, 我們成類型的方法集. 我們將在第六章討論方法的細節, 這值說寫簡單用法.
的聲明, Celsius 類型的參數 c 齣現在了函數名的前, 示聲明一個 Celsius 類型的 名叫 String 的方法, 方法返迴 帶着 °C 溫度單位 的參數 c 的數字打印字符串:
的聲明, Celsius 類型的參數 c 齣現在了函數名的前, 示聲明一個 Celsius 類型的 名叫 String 的方法, 方法返迴 帶着 °C 溫度單位 的參數 c 的數字打印字符串:
```Go
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
```
許多類型都會定義個 String 方法, 因當然用 fmt 包的打印方法時, 將會優先使用 String 方法返迴的結果打印, 將在 7.1節 講述.
許多類型都會定義個 String 方法, 因當然用 fmt 包的打印方法時, 將會優先使用 String 方法返迴的結果打印, 將在 7.1節 講述.
```Go
c := FToC(212.0)

View File

@@ -1,8 +1,8 @@
### 2.6.1. 導入包
在Go程序中, 每個包都是有一個全侷唯一的導入路徑. 聲明中類似 "gopl.io/ch2/tempconv" 的字符串對應導入路徑. 語言的規範沒有定義這些字符串的具體含義或包來自哪, 它們是由工具來解釋. 當使用 go 工具箱時(第十章), 一個導入路徑代一個目中的一個或多個Go源文件.
在Go程序中, 每個包都是有一個全侷唯一的導入路徑. 聲明中類似 "gopl.io/ch2/tempconv" 的字符串對應導入路徑. 語言的規範沒有定義這些字符串的具體含義或包來自哪, 它們是由工具來解釋. 當使用 go 工具箱時(第十章), 一個導入路徑代一個目中的一個或多個Go源文件.
除了到導入路徑, 每個包還有一個包名, 包名一般是短小的(也不要求是是唯一的), 包名在包的聲明處指定. 按照慣例, 一個包的名字和包的導入路徑的最後一個字段相, 例如 gopl.io/ch2/tempconv 包的名字是 tempconv.
除了到導入路徑, 每個包還有一個包名, 包名一般是短小的(也不要求是是唯一的), 包名在包的聲明處指定. 按照慣例, 一個包的名字和包的導入路徑的最後一個字段相, 例如 gopl.io/ch2/tempconv 包的名字是 tempconv.
要使用 gopl.io/ch2/tempconv 包, 需要先導入:
@@ -34,7 +34,7 @@ func main() {
}
```
導入聲明將導入的包綁定到一個短小的名字, 然後通過該名字就可以引用包中導齣的全部內容. 上的導入聲明將允許我們以 tempconv.CToF 的方式來訪問 gopl.io/ch2/tempconv 包中的內容. 默認情況下, 導入的包綁定到 tempconv 名字, 但是我們也可以綁定到另一個名稱, 以避免名字突(§10.3).
導入聲明將導入的包綁定到一個短小的名字, 然後通過該名字就可以引用包中導齣的全部內容. 上的導入聲明將允許我們以 tempconv.CToF 的方式來訪問 gopl.io/ch2/tempconv 包中的內容. 默認情況下, 導入的包綁定到 tempconv 名字, 但是我們也可以綁定到另一個名稱, 以避免名字突(§10.3).
cf 程序將命令行輸入的一個溫度在 Celsius 和 Fahrenheit 之間轉換:
@@ -48,7 +48,7 @@ $ ./cf -40
-40°F = -40°C, -40°C = -40°F
```
如果導入一個包, 但是沒有使用該包將被當作一個錯誤. 這種強製檢測可以有效減少不必要的依賴, 雖然在調試期間會讓人討厭, 因刪除一個類似 log.Print("got here!") 的打印可能導緻需要時刪除 log 包導入聲明, 否則, 編譯器將會齣一個錯誤. 在這種情況下, 我們需要將不必要的導入刪除或註釋掉.
如果導入一個包, 但是沒有使用該包將被當作一個錯誤. 這種強製檢測可以有效減少不必要的依賴, 雖然在調試期間會讓人討厭, 因刪除一個類似 log.Print("got here!") 的打印可能導緻需要時刪除 log 包導入聲明, 否則, 編譯器將會齣一個錯誤. 在這種情況下, 我們需要將不必要的導入刪除或註釋掉.
不過有更好的解決方案, 我們可以使用 golang.org/x/tools/cmd/goimports 工具, 它可以根據需要自動添加或刪除導入的包; 許多編輯器都可以集成 goimports 工具, 然後在保存文件的時候自動允許它. 類似的還有 gofmt 工具, 可以用來格式化Go源文件.

View File

@@ -3,26 +3,26 @@
包的初始化首先是解決包級變量的依賴順序, 然後安裝包級變量聲明齣現的順序依次初始化:
```Go
var a = b + c // a 第三個初始化, 3
var b = f() // b 第二個初始化, 2, 通過調用 f (依賴c)
var c = 1 // c 第一個初始化, 1
var a = b + c // a 第三個初始化, 3
var b = f() // b 第二個初始化, 2, 通過調用 f (依賴c)
var c = 1 // c 第一個初始化, 1
func f() int { return c + 1 }
```
如果包中含有多個 .go 文件, 它們按照給編譯器的順序進行初始化, Go的構建工具首先將 .go 文件根據文件名排序, 然後依次調用編譯器編譯.
如果包中含有多個 .go 文件, 它們按照給編譯器的順序進行初始化, Go的構建工具首先將 .go 文件根據文件名排序, 然後依次調用編譯器編譯.
對於在包級聲明的變量, 如果有初始化達式則用達式初始化, 還有一些沒有初始化達式的, 例如 某些格數據 初始化不是一個簡單的賦值過程. 在這種情況下, 我們可以用 init 初始化函數來簡化工作. 每個文件都可以包含多個 init 初始化函數
對於在包級聲明的變量, 如果有初始化達式則用達式初始化, 還有一些沒有初始化達式的, 例如 某些格數據 初始化不是一個簡單的賦值過程. 在這種情況下, 我們可以用 init 初始化函數來簡化工作. 每個文件都可以包含多個 init 初始化函數
```Go
func init() { /* ... */ }
```
這樣的init初始化函數除了不能被調用或引用外, 其他行和普通函數類似. 在每個文件中的init初始化函數, 在程序開始執行時按照它們聲明的順序被自動調用.
這樣的init初始化函數除了不能被調用或引用外, 其他行和普通函數類似. 在每個文件中的init初始化函數, 在程序開始執行時按照它們聲明的順序被自動調用.
每個包在解決依賴的前提下, 以導入聲明的順序初始化, 每個包隻會被初始化一次. 因此, 如果一個 p 包導入了 q 包, 那在 p 包初始化的時候可以認 q 包已經初始化過了. 初始化工作是自下而上進行的, main 包最後被初始化. 以這種方式, 確保 在 main 函數執行之前, 所有的包都已經初始化了.
每個包在解決依賴的前提下, 以導入聲明的順序初始化, 每個包隻會被初始化一次. 因此, 如果一個 p 包導入了 q 包, 那在 p 包初始化的時候可以認 q 包已經初始化過了. 初始化工作是自下而上進行的, main 包最後被初始化. 以這種方式, 確保 在 main 函數執行之前, 所有的包都已經初始化了.
的代碼定義了一個 PopCount 函數, 用於返迴一個數字中含二進製1bit的個數. 它使用 init 初始化函數來生成輔助格 pc, pc 格用於處理每個8bit寬度的數字含二進製的1bit的個數, 這樣的話在處理64bit寬度的數字時就沒有必要循環64次, 隻需要8次査就可以了. (這不是最快的統計1bit數目的算法, 但是他可以方便演示init函數的用法, 且演示了如果預生成輔助格, 這是編程中常用的技.)
的代碼定義了一個 PopCount 函數, 用於返迴一個數字中含二進製1bit的個數. 它使用 init 初始化函數來生成輔助格 pc, pc 格用於處理每個8bit寬度的數字含二進製的1bit的個數, 這樣的話在處理64bit寬度的數字時就沒有必要循環64次, 隻需要8次査就可以了. (這不是最快的統計1bit數目的算法, 但是他可以方便演示init函數的用法, 且演示了如果預生成輔助格, 這是編程中常用的技.)
```Go
gopl.io/ch2/popcount
@@ -59,9 +59,9 @@ for i, _ := range pc {
我們在下一節和10.5節還將看到其它使用init函數的地方.
**練習2.3:** 重寫 PopCount 函數, 用一個循環代替單一的達式. 比較兩個版本的性能. (11.4節將展示如何繫統地比較兩個不實現的性能.)
**練習2.3:** 重寫 PopCount 函數, 用一個循環代替單一的達式. 比較兩個版本的性能. (11.4節將展示如何繫統地比較兩個不實現的性能.)
**練習2.4:** 用移位的算法重寫 PopCount 函數, 每次測試最右邊的1bit, 然後統計總數. 比較和査算法的性能差異.
**練習2.4:** 用移位的算法重寫 PopCount 函數, 每次測試最右邊的1bit, 然後統計總數. 比較和査算法的性能差異.
**練習2.5:** 達式 `x&(x-1)` 用於將 x 的最低的一個1bit位清零. 使用這個格式重寫 PopCount 函數, 然後比較性能.
**練習2.5:** 達式 `x&(x-1)` 用於將 x 的最低的一個1bit位清零. 使用這個格式重寫 PopCount 函數, 然後比較性能.

View File

@@ -1,14 +1,14 @@
## 2.6. 包和文件
Go語言中的包和其他語言的庫或模塊概唸類似, 目的都是了支持模塊好, 封裝, 單獨編譯和代碼重用. 一個包的源代碼保存在一個或多個以.後綴名的文件中, 通常一個包所在目路徑的後綴是包的導入路徑; 例如包 gopl.io/ch1/helloworld 對應的目路徑是 $GOPATH/src/gopl.io/ch1/helloworld.
Go語言中的包和其他語言的庫或模塊概唸類似, 目的都是了支持模塊好, 封裝, 單獨編譯和代碼重用. 一個包的源代碼保存在一個或多個以.後綴名的文件中, 通常一個包所在目路徑的後綴是包的導入路徑; 例如包 gopl.io/ch1/helloworld 對應的目路徑是 $GOPATH/src/gopl.io/ch1/helloworld.
每個包作一個獨立的名字空間. 例如, 在 image 包中的 Decode 函數 和 unicode/utf16 包中的 Decode 函數是不的. 要在外部包引用該函數, 必鬚顯式使用 image.Decode 或 utf16.Decode 訪問.
每個包作一個獨立的名字空間. 例如, 在 image 包中的 Decode 函數 和 unicode/utf16 包中的 Decode 函數是不的. 要在外部包引用該函數, 必鬚顯式使用 image.Decode 或 utf16.Decode 訪問.
包可以讓我們通過控製那些名字是外部可見的來隱藏信息. 在Go中, 一個簡單的規則是: 如果一個名字是大寫字母開頭的, 那該名字是導齣的.
包可以讓我們通過控製那些名字是外部可見的來隱藏信息. 在Go中, 一個簡單的規則是: 如果一個名字是大寫字母開頭的, 那該名字是導齣的.
了演示基本的用法, 假設我們的溫度轉換軟件已經很流行, 我們希望到Go社區也能使用這個包. 我們該如何做呢?
了演示基本的用法, 假設我們的溫度轉換軟件已經很流行, 我們希望到Go社區也能使用這個包. 我們該如何做呢?
讓我們創建一個名 gopl.io/ch2/tempconv 的包, 是前例子的一個改進版本. (我們約定我們的例子都是以章節順序來編號的, 這樣的路徑更容易閱讀.) 包代碼存儲在兩個文件, 用來演示如何在一個文件聲明然後在其他的文件訪問; 在現實中, 這樣小的包一般值需要一個文件.
讓我們創建一個名 gopl.io/ch2/tempconv 的包, 是前例子的一個改進版本. (我們約定我們的例子都是以章節順序來編號的, 這樣的路徑更容易閱讀.) 包代碼存儲在兩個文件, 用來演示如何在一個文件聲明然後在其他的文件訪問; 在現實中, 這樣小的包一般值需要一個文件.
我們把變量的聲明, 對應的常量, 還有方法都放到 tempconv.go 文件:
@@ -44,16 +44,16 @@ func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
```
每個文件都是以包的聲明語句開始, 用來指定包的名字. 當包被導入的時候, 包內部的成員將通過類似 tempconv.CToF 的方式訪問. 包級的名字, 例如在一個文件聲明的類型和常量, 在一個包的其他文件也是可以直接訪問的,
就好像所有代碼都在一個文件一樣. 要註意的是 tempconv.go 文件導入了 fmt 包, 但是 conv.go 文件沒有, 因為它併沒有用到 fmt 包.
每個文件都是以包的聲明語句開始, 用來指定包的名字. 當包被導入的時候, 包內部的成員將通過類似 tempconv.CToF 的方式訪問. 包級的名字, 例如在一個文件聲明的類型和常量, 在一個包的其他文件也是可以直接訪問的,
就好像所有代碼都在一個文件一樣. 要註意的是 tempconv.go 文件導入了 fmt 包, 但是 conv.go 文件沒有, 因爲它並沒有用到 fmt 包.
包級的常量名都是以大寫字母開頭, 它們也是可以像 tempconv.AbsoluteZeroC 這樣被訪問的:
包級的常量名都是以大寫字母開頭, 它們也是可以像 tempconv.AbsoluteZeroC 這樣被訪問的:
```Go
fmt.Printf("Brrrr! %v\n", tempconv.AbsoluteZeroC) // "Brrrr! -273.15°C"
```
要將 攝氏溫度轉換 華氏溫度, 需要先導入 gopl.io/ch2/tempconv, 然後就可以使用下的代碼轉換了:
要將 攝氏溫度轉換 華氏溫度, 需要先導入 gopl.io/ch2/tempconv, 然後就可以使用下的代碼轉換了:
```Go
fmt.Println(tempconv.CToF(tempconv.BoilingC)) // "212°F"
@@ -62,8 +62,8 @@ fmt.Println(tempconv.CToF(tempconv.BoilingC)) // "212°F"
在每個文件的包聲明前僅跟着的註釋是包註釋(§10.7.4). 通常, 第一句應該先是包的功能概要.
一個包通常隻有一個文件有包註釋. 如果包註釋很大, 通常會放到一個獨立的 doc.go 文件中.
**練習 2.1:** tempconv 包 添加類型, 常量和函數用來處理 Kelvin 對溫度的轉換,
Kelvin 對零度是 273.15°C, Kelvin 對溫度1K和攝氏度1°C的單位間隔是一樣的.
**練習 2.1:** tempconv 包 添加類型, 常量和函數用來處理 Kelvin 對溫度的轉換,
Kelvin 對零度是 273.15°C, Kelvin 對溫度1K和攝氏度1°C的單位間隔是一樣的.
{% include "./ch2-06-1.md" %}

View File

@@ -2,17 +2,17 @@
一個聲明語句將程序中的實體和一個名字關聯, 比如一個函數或一個變量. 聲明的作用域是指源代碼中可以有效使用這個名字的範圍.
不要將作用域和生命週期混一談. 聲明的作用域對應的是一個源代碼的文本區域; 它是一個編譯時的屬性. 一個變量的生命週期是程序運行時變量存在的有效時間段, 在此時間區域內存它可以被程序的其他部分引用. 是一個運行時的概唸.
不要將作用域和生命週期混一談. 聲明的作用域對應的是一個源代碼的文本區域; 它是一個編譯時的屬性. 一個變量的生命週期是程序運行時變量存在的有效時間段, 在此時間區域內存它可以被程序的其他部分引用. 是一個運行時的概唸.
語法塊是由花括弧所包含的一繫列語句, 就像函數體或循環體那樣. 語法塊內部聲明的名字是無法被外部語法塊訪問的. 語法決定了內部聲明的名字的作用域範圍. 我們可以這樣理解, 語法塊可以包含其他類似組批量聲明等沒有用花括弧包含的代碼, 我們稱之詞滙塊. 有一個語法決整個源代碼, 稱全侷塊; 然後是每個包的語法決; 每個 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() {}
@@ -27,7 +27,7 @@ func main() {
}
```
在函數中詞法域可以深度嵌套, 因此內部的一個聲明可能屏蔽外部的聲明. 還有許多塊是if或for等控製流語句構造的. 下的代碼有三個不的變量x, 因它們是定義在不的詞法域的原因. (這個例子隻是了演示作用域規則, 但不是好的編程風格.)
在函數中詞法域可以深度嵌套, 因此內部的一個聲明可能屏蔽外部的聲明. 還有許多塊是if或for等控製流語句構造的. 下的代碼有三個不的變量x, 因它們是定義在不的詞法域的原因. (這個例子隻是了演示作用域規則, 但不是好的編程風格.)
```Go
func main() {
@@ -42,11 +42,11 @@ 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語句塊, 一個在循環體塊; 隻有兩個塊是顯式創建的:
的例子樣有三個不的x變量, 每個聲明在不的塊, 一個在函數體塊, 一個在for語句塊, 一個在循環體塊; 隻有兩個塊是顯式創建的:
```Go
func main() {
@@ -58,7 +58,7 @@ func main() {
}
```
和彿如循環類似, if和switch語句也會在條件部分創建隱式塊, 還有它們對應的執行體塊. 下的 if-else 測試鏈演示的 x 和 y 的作用域範圍:
和彿如循環類似, if和switch語句也會在條件部分創建隱式塊, 還有它們對應的執行體塊. 下的 if-else 測試鏈演示的 x 和 y 的作用域範圍:
```Go
if x := f(); x == 0 {
@@ -71,9 +71,9 @@ if x := f(); x == 0 {
fmt.Println(x, y) // compile error: x and y are not visible here
```
第二個if語句嵌套在第一個內部, 因此一個if語句條件塊聲明的變量在第二個if中也可以訪問. switch語句的每個分支也有類似的規則: 條件部分一個隱式塊, 然後每個是每個分支的主體塊.
第二個if語句嵌套在第一個內部, 因此一個if語句條件塊聲明的變量在第二個if中也可以訪問. switch語句的每個分支也有類似的規則: 條件部分一個隱式塊, 然後每個是每個分支的主體塊.
在包級, 聲明的順序不會影響作用域範圍, 因此一個先聲明的可以引用它自身或者是引用後的一個聲明, 這可以讓我們定義一些相互嵌套或遞歸的類型或函數. 但是如果一個變量或常量遞歸引用了自身, 則會產生編譯錯誤.
在包級, 聲明的順序不會影響作用域範圍, 因此一個先聲明的可以引用它自身或者是引用後的一個聲明, 這可以讓我們定義一些相互嵌套或遞歸的類型或函數. 但是如果一個變量或常量遞歸引用了自身, 則會產生編譯錯誤.
在這個程序中:
@@ -85,9 +85,9 @@ f.ReadByte() // compile error: undefined f
f.Close() // compile error: undefined f
```
變量 f 的作用域隻有if語句內, 因此後的語句將無法引入它, 將導緻編譯錯誤. 你可能會收到一個侷部變量f沒有聲明的錯誤提示, 具體錯誤信息依賴編譯器的實現.
變量 f 的作用域隻有if語句內, 因此後的語句將無法引入它, 將導緻編譯錯誤. 你可能會收到一個侷部變量f沒有聲明的錯誤提示, 具體錯誤信息依賴編譯器的實現.
通常需要在if之前聲明變量, 這樣可以確保後的語句依然可以訪問變量:
通常需要在if之前聲明變量, 這樣可以確保後的語句依然可以訪問變量:
```Go
f, err := os.Open(fname)
@@ -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
@@ -157,6 +157,6 @@ func init() {
}
```
我們已經看到包, 文件, 聲明和語句如何來達一個程序結構. 在下的兩個章節, 我們將探討數據的結構.
我們已經看到包, 文件, 聲明和語句如何來達一個程序結構. 在下的兩個章節, 我們將探討數據的結構.
**譯註: 本章的詞法域和作用域概唸有些混淆, 需要重譯一遍.**

View File

@@ -1,5 +1,5 @@
# 第2章 程序結構
Go語言和任何其他語言一樣, 一個大的程序是有很多小的基礎構件組成的. 變量保存值. 簡單的加法和減法運算被組成較大的達式. 基礎類型被聚閤為數組或結構體. 然後使用if和for之類的控製語句來組織和控製達式的執行順序. 然後多個語句被組織到函數中, 以便代碼的隔離和復用. 函數以源文件和包的方式組織.
Go語言和任何其他語言一樣, 一個大的程序是有很多小的基礎構件組成的. 變量保存值. 簡單的加法和減法運算被組成較大的達式. 基礎類型被聚合爲數組或結構體. 然後使用if和for之類的控製語句來組織和控製達式的執行順序. 然後多個語句被組織到函數中, 以便代碼的隔離和復用. 函數以源文件和包的方式組織.
我們已經在前的章節的例子中看到了大部分的例子. 在本章中, 我們將深入討論Go程序的基礎結構的一些細節. 每個示例程序都是刻意寫的簡單, 這樣我們可以減少被復雜的算法和數據結構所擾, 從而專註於語言本身的學習.
我們已經在前的章節的例子中看到了大部分的例子. 在本章中, 我們將深入討論Go程序的基礎結構的一些細節. 每個示例程序都是刻意寫的簡單, 這樣我們可以減少被復雜的算法和數據結構所擾, 從而專註於語言本身的學習.