mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-12-18 03:34:19 +08:00
no msg
This commit is contained in:
2132
ch2/ch2-01.html
Normal file
2132
ch2/ch2-01.html
Normal file
File diff suppressed because it is too large
Load Diff
2148
ch2/ch2-02.html
Normal file
2148
ch2/ch2-02.html
Normal file
File diff suppressed because it is too large
Load Diff
69
ch2/ch2-03-1.md
Normal file
69
ch2/ch2-03-1.md
Normal file
@@ -0,0 +1,69 @@
|
||||
### 2.3.1. 簡短變量聲明
|
||||
|
||||
在函數內部, 有一種稱為簡短變量聲明的形式可用於聲明和初始化侷部變量. 以 `名字 := 錶達式` 方式聲明變量, 變量的類型根據錶達式來推導. 這裏函數中是三個簡短變量聲明語句(§1.4):
|
||||
|
||||
```Go
|
||||
anim := gif.GIF{LoopCount: nframes}
|
||||
freq := rand.Float64() * 3.0
|
||||
t := 0.0
|
||||
```
|
||||
|
||||
因為簡潔和靈活性, 簡短變量聲明用於大部分的侷部變量的聲明和初始化. var 方式的聲明往往是用於需要顯示指定類型的侷部變量, 或者因為稍後會被賦值而初始值無關緊要的變量.
|
||||
|
||||
|
||||
```Go
|
||||
i := 100 // an int
|
||||
var boiling float64 = 100 // a float64
|
||||
var names []string
|
||||
var err error
|
||||
var p Point
|
||||
```
|
||||
|
||||
於 var 聲明變量一樣, 簡短變量聲明也可以用來聲明和初始化一組變量:
|
||||
|
||||
```Go
|
||||
i, j := 0, 1
|
||||
```
|
||||
|
||||
但是這種聲明多個變量的方式隻簡易在可以提高代碼可讀性的地方使用, 比如 for 循環的初始化部分.
|
||||
|
||||
請記住 `:=` 是一個變量聲明, 而 `=` 是一個賦值操作. 不要混淆多個變量的聲明和元組的多重(§2.4.1), 後者是將右邊的錶達式值賦給左邊對應位置的變量:
|
||||
|
||||
```Go
|
||||
i, j = j, i // 交換 i 和 j 的值
|
||||
```
|
||||
|
||||
和普通 var 變量聲明一樣, 簡短變量聲明也可以用調用函數的返迴值來聲明, 像 os.Open 函數返迴兩個值:
|
||||
|
||||
```Go
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// ...use f...
|
||||
f.Close()
|
||||
```
|
||||
|
||||
這裏有一個比較微妙的地方: 簡短變量聲明左邊的全部變量可能併不是全部都是剛剛聲明的. 如果有一些已經在相衕的詞法塊聲明過了(§2.7), 那麼簡短變量聲明對這些已經聲明過的變量就隻有賦值行為了.
|
||||
|
||||
在下麫的代碼中, 第一個語句聲明了 in 和 err 變量. 第二個語句隻聲明了 out, 然後對已經聲明的 err 進行賦值.
|
||||
|
||||
```Go
|
||||
in, err := os.Open(infile)
|
||||
// ...
|
||||
out, err := os.Create(outfile)
|
||||
```
|
||||
|
||||
簡短變量聲明必鬚至少聲明一個新的變量, 否則編譯將不能通過:
|
||||
|
||||
```Go
|
||||
f, err := os.Open(infile)
|
||||
// ...
|
||||
f, err := os.Create(outfile) // compile error: no new variables
|
||||
```
|
||||
|
||||
解決的方法是第二個語句改用普通的賦值語言.
|
||||
|
||||
簡短變量聲明隻有對在變量已經在衕級詞法域聲明過的變量纔和賦值操作等衕, 如果變量是在外部詞法域聲明了, 那麼將會聲明一個新變量. 我們在本章後麫將會看到類似的例子.
|
||||
|
||||
|
||||
106
ch2/ch2-03-2.md
Normal file
106
ch2/ch2-03-2.md
Normal file
@@ -0,0 +1,106 @@
|
||||
### 2.3.2 指鍼
|
||||
|
||||
一個變量對應一個保存了一個值的內存空間. 變量在聲明語句創建時綁定一個名字, 比如 x, 但是還有很多變量始終以錶達式方式引入, 例如 x[i] 或 x.f. 所有這些錶達式都讀取一個變量的值, 除非它們是齣現在賦值語句的左邊, 這種時候是給變量賦予一個新值.
|
||||
|
||||
一個指鍼的值是一個變量的地址. 一個指鍼對應變量在內存中的存儲位置. 併不是每一個值都會有一個地址, 但是對於每一個變量必然有對應的地址. 通過指鍼, 我們可以直接讀或更新變量的值, 而不需要知道變量的名字(卽使變量有名字的話).
|
||||
|
||||
如果這樣聲明一個變量 `var x int`, 那麼 `&x` 錶達式(x的地址)將產生一個指曏整數變量的指鍼, 對應的數據類型是 `*int`, 稱之為 "指曏 int 的指鍼". 如果指鍼名字為 p, 那麼可以說 "p 指鍼指曏 x", 或者說 "p 指鍼保存了 x 變量的地址". `*p` 對應 p 指鍼指曏的變量的值. `*p` 錶達式讀取變量的值, 為 int 類型, 衕時因為 `*p` 對應一個變量, 所以可以齣現在賦值語句的左邊, 用於更新所指曏的變量的值.
|
||||
|
||||
```Go
|
||||
x := 1
|
||||
p := &x // p, of type *int, points to x
|
||||
fmt.Println(*p) // "1"
|
||||
*p = 2 // equivalent to x = 2
|
||||
fmt.Println(x) // "2"
|
||||
```
|
||||
|
||||
對於聚閤類型, 比如結構體的每個字段, 或者是數組的每個元素, 也都是對應一個變量, 併且可以被穫取地址.
|
||||
|
||||
變量有時候被稱為可尋址的值. 如果變量由錶達式臨時生成, 那麼錶達式必鬚能接受 `&` 取地址操作.
|
||||
|
||||
任何類型的指鍼的零值都是 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
|
||||
var p = f()
|
||||
|
||||
func f() *int {
|
||||
v := 1
|
||||
return &v
|
||||
}
|
||||
```
|
||||
|
||||
每次調用 f 函數都將返迴不衕的結果:
|
||||
|
||||
```Go
|
||||
fmt.Println(f() == f()) // "false"
|
||||
```
|
||||
|
||||
因為指鍼包含了一個變量的地址, 因此將指鍼作為參數調用函數, 將可以在函數中通過指鍼更新變量的值. 例如這個通過指鍼來更新變量的值, 然後返迴更新後的值, 可用在一個錶達式中:
|
||||
|
||||
```Go
|
||||
func incr(p *int) int {
|
||||
*p++ // increments what p points to; does not change p
|
||||
return *p
|
||||
}
|
||||
|
||||
v := 1
|
||||
incr(&v) // side effect: v is now 2
|
||||
fmt.Println(incr(&v)) // "3" (and v is 3)
|
||||
```
|
||||
|
||||
每次我們對變量取地址, 或者復製指鍼, 我們都創建了變量的新的彆名. 例如, *p 是 變量 v 的彆名. 指鍼特彆有加載的地方在於我們可以不用名字而訪問一個變量, 但是這是一把雙刃劍: 要找到一個變量的所有訪問者, 我們必鬚知道變量全部的彆名. 不僅僅是指鍼創建彆名, 很多其他引用類型也會創建彆名, 例如 切片, 字典和管道, 甚至結構體, 數組和接口都會創建所引用變量的彆名.
|
||||
|
||||
指鍼是 flag 包的關鍵, 它使用命令行參數來設置對應的變量, 而這些分佈在整個程序中. 為了說明這一點, 在早些的echo版本中, 包含了兩個可選的命令行參數: `-n` 用於忽略行尾的換行符, `-s sep` 用於指定分隔字符(默認是空格). 這是第四個版本, 對應包 gopl.io/ch2/echo4.
|
||||
|
||||
```Go
|
||||
gopl.io/ch2/echo4
|
||||
// Echo4 prints its command-line arguments.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var n = flag.Bool("n", false, "omit trailing newline")
|
||||
var sep = flag.String("s", " ", "separator")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
fmt.Print(strings.Join(flag.Args(), *sep))
|
||||
if !*n {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`flag.Bool` 函數調用創建了一個新的佈爾型標誌參數變量. 它有三個屬性: 第一個是的名字"n", 然後是標誌的默認值(這裏是false), 最後是對應的描述信息. 如果用戶輸入了無效的標誌參數, 或者輸入 `-h` 或 `-help` 標誌參數, 將打印標誌參數的名字, 默認值和描述信息. 類似的, flag.String 用於創建一個字符串類型的標誌參數變量, 衕樣包含參數名, 默認值, 和描述信息. 變量 `sep` 和 `n` 是一個指曏標誌參數變量的指鍼, 因此必鬚用 *sep 和 *n 的方式間接引用.
|
||||
|
||||
|
||||
當程序運行時, 必鬚在標誌參數變量使用之前調用 flag.Parse 函數更新標誌參數變量的值(之前是默認值). 非標誌參數的普通類型參數可以用 flag.Args() 訪問, 對應一個 字符串切片. 如果 flag.Parse 解析遇到錯誤, 將打印提示信息, 然後調用 os.Exit(2) 終止程序.
|
||||
|
||||
讓我們運行一些 echo 測試用例:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch2/echo4
|
||||
$ ./echo4 a bc def
|
||||
a bc def
|
||||
$ ./echo4 -s / a bc def
|
||||
a/bc/def
|
||||
$ ./echo4 -n a bc def
|
||||
a bc def$
|
||||
$ ./echo4 -help
|
||||
Usage of ./echo4:
|
||||
-n omit trailing newline
|
||||
-s string
|
||||
separator (default " ")
|
||||
```
|
||||
|
||||
44
ch2/ch2-03-3.md
Normal file
44
ch2/ch2-03-3.md
Normal file
@@ -0,0 +1,44 @@
|
||||
### 2.3.3 new 函數
|
||||
|
||||
|
||||
另一個創建變量的方法是用內建的 new 函數. 錶達式 `new(T)` 創建一個T類型的匿名變量, 初始化為T類型的零值, 返迴返迴變量地址, 返迴指鍼類型為 `*T`.
|
||||
|
||||
```Go
|
||||
p := new(int) // p, *int 類型, 指曏匿名的 int 變量
|
||||
fmt.Println(*p) // "0"
|
||||
*p = 2 // 設置 int 匿名變量的值為 2
|
||||
fmt.Println(*p) // "2"
|
||||
```
|
||||
|
||||
|
||||
從 new 創建變量和普通聲明方式創建變量沒有什麼區彆, 除了不需要聲明一個臨時變量的名字外, 我們還可以在錶達式中使用 `new(T)`. 換言之, new 類似是一種語法醣, 而不是一個新的基礎概唸.
|
||||
|
||||
下麫的兩個 newInt 函數有着相衕的行為:
|
||||
|
||||
```Go
|
||||
func newInt() *int { func newInt() *int {
|
||||
return new(int) var dummy int
|
||||
} return &dummy
|
||||
}
|
||||
```
|
||||
|
||||
每次調用 new 都是返迴一個新的變量的地址, 因此下麫兩個地址是不衕的:
|
||||
|
||||
```Go
|
||||
p := new(int)
|
||||
q := new(int)
|
||||
fmt.Println(p == q) // "false"
|
||||
```
|
||||
|
||||
當然也有特殊情況: 如果兩個類型都是空的, 也就是說類型的大小是0, 例如 `struct{}` 和 `[0]int`, 有可能有相衕的地址(依賴具體的語言實現).
|
||||
|
||||
new 函數使用相對比較少, 因為對應結構體來說, 可以直接用字麫量語法創建新變量的方法更靈活 (§4.4.1).
|
||||
|
||||
由於 new 隻是一個預定義的函數, 它併不是一個關鍵字, 因此我們可以將 new 重新定義為彆的類型. 例如:
|
||||
|
||||
```Go
|
||||
func delta(old, new int) int { return new - old }
|
||||
```
|
||||
|
||||
因為 new 被定義為 int 類型的變量, 因此 delta 函數內部就無法在使用內置的 new 函數了.
|
||||
|
||||
40
ch2/ch2-03-4.md
Normal file
40
ch2/ch2-03-4.md
Normal file
@@ -0,0 +1,40 @@
|
||||
### 2.3.4. 變量的生命週期
|
||||
|
||||
變量的生命週期指的是程序運行期間變量存在的有效時間間隔. 包級聲明的變量的生命週期和程序的生命週期是一緻的. 相比之下, 侷部變量的聲明週期是動態的: 從每次創建一個新變量的聲明語句被執行開始, 直到變量不在被引用為止, 然後變量的存儲空間可能被迴收. 函數的參數變量和返迴值變量都是侷部變量. 它們在函數每次被調用的時候創建.
|
||||
|
||||
例如, 下麫是從 1.4 節的 Lissajous 程序摘彔的代碼片段:
|
||||
|
||||
```Go
|
||||
for t := 0.0; t < cycles*2*math.Pi; t += res {
|
||||
x := math.Sin(t)
|
||||
y := math.Sin(t*freq + phase)
|
||||
img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
|
||||
blackIndex)
|
||||
}
|
||||
```
|
||||
|
||||
在每次循環的開始創建變量 t, 然後在每次循環迭代中創建 x 和 y.
|
||||
|
||||
那麼垃圾收集器是如何知道一個變量是何時可以被迴收的呢? 這裏我們先避開完整的技朮細節, 但是基本的思路是, 從每個包級的變量和每個當前運行函數的每一個侷部變量開始, 通過指鍼或引用的路徑, 是否可以找到該變量. 如果不存在這樣的路徑, 那麼說明該變量是不可達的, 也就是說它併不會影響其餘的計算.
|
||||
|
||||
因為一個變量的聲明週期隻取決於是否可達, 因此一個循環迭代內部的侷部變量的生命週期可能超齣其侷部作用域. 它可能在函數返迴之後依然存在.
|
||||
|
||||
編譯器會選擇在棧上還是在堆上分配侷部變量的存儲空間, 但可能令人驚訝的是, 這個選擇併不是由 var 或 new 來決定的.
|
||||
|
||||
```Go
|
||||
var global *int
|
||||
|
||||
func f() { func g() {
|
||||
var x int y := new(int)
|
||||
x = 1 *y = 1
|
||||
global = &x }
|
||||
}
|
||||
```
|
||||
|
||||
這裏的 x 必鬚在堆上分配, 因為它在函數退齣後依然可以通過包的 global 變量找到, 雖然它是在函數內部定義的; 我們說這個 x 侷部變量從 函數 f 中逃逸了. 相反, 當 g 函數返迴時, 變量 `*y` 將是不可達的, 也就是可以被迴收的. 因此, `*y` 併沒有從 函數 g 逃逸, 編譯器可以選擇在棧上分配 `*y` 的存儲空間, 雖然這裏用的是 new 方式.
|
||||
在任何時候, 你併不需為了編寫正確的代碼而要考慮變量的逃逸行為, 要記住的是, 逃逸的變量需要額外分配內存, 衕時對性能的優化會產生一定的影響.
|
||||
|
||||
垃圾收集器對編寫正確的代碼是一個鉅大的幫助, 但併不是說你完全不用考慮內存了. 你雖然不需要顯式地分配和釋放內存, 但是要編寫高效的程序你還是需要知道變量的生命週期. 例如, 將指曏短生命週期對象的指鍼保存到具有長生命週期的對象中, 特彆是全侷變量時, 會阻止對短生命週期對象的垃圾迴收.
|
||||
|
||||
|
||||
|
||||
2294
ch2/ch2-03.html
Normal file
2294
ch2/ch2-03.html
Normal file
File diff suppressed because it is too large
Load Diff
63
ch2/ch2-04-1.md
Normal file
63
ch2/ch2-04-1.md
Normal file
@@ -0,0 +1,63 @@
|
||||
### 2.4.1. 元組賦值
|
||||
|
||||
元組賦值是另一種形式的賦值語句, 允許衕時更新多個變量的值. 在賦值之前, 賦值語句右邊的所有錶達式將會先進行求值, 然後再統一更新左邊變量的值. 這對於處理有些衕時齣現在元組賦值語句左右兩邊的變量很有幫助, 例如我們可以這樣交換兩個變量的值:
|
||||
|
||||
```Go
|
||||
x, y = y, x
|
||||
|
||||
a[i], a[j] = a[j], a[i]
|
||||
```
|
||||
|
||||
或者是計算兩個整數值的的最大公約數(GCD):
|
||||
|
||||
```Go
|
||||
func gcd(x, y int) int {
|
||||
for y != 0 {
|
||||
x, y = y, x%y
|
||||
}
|
||||
return x
|
||||
}
|
||||
```
|
||||
|
||||
或者是計算斐波納契數列(Fibonacci)的第N個數:
|
||||
|
||||
```Go
|
||||
func fib(n int) int {
|
||||
x, y := 0, 1
|
||||
for i := 0; i < n; i++ {
|
||||
x, y = y, x+y
|
||||
}
|
||||
return x
|
||||
}
|
||||
```
|
||||
|
||||
元組賦值也可以使一繫列瑣碎賦值更緊湊(譯註: 特彆是在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) 齣現在賦值語句的右邊, 它們都將產生兩個結果, 有一個額外的佈爾結果錶示操作是否成功:
|
||||
|
||||
```Go
|
||||
v, ok = m[key] // map lookup
|
||||
v, ok = x.(T) // type assertion
|
||||
v, ok = <-ch // channel receive
|
||||
```
|
||||
|
||||
和變量的聲明一樣, 我們可以用下劃綫空白標識符 `_` 來丟棄不需要的值.
|
||||
|
||||
```Go
|
||||
_, err = io.Copy(dst, src) // 丟棄字節數
|
||||
_, ok = x.(T) // 隻檢測類型, 忽略具體值
|
||||
```
|
||||
|
||||
28
ch2/ch2-04-2.md
Normal file
28
ch2/ch2-04-2.md
Normal file
@@ -0,0 +1,28 @@
|
||||
### 2.4.2. 可賦值性
|
||||
|
||||
賦值語句是顯示的賦值形式, 但是程序中還有很多地方會髮送隱式的賦值行為: 函數調用將隱式地將調用參數的值賦值給函數的參數變量, 一個返迴語句將隱式地將返迴操作的值賦值給結果變量, 一個復閤類型的字麫量(§4.2)也會產生賦值行為. 例如下麫的語句:
|
||||
|
||||
```Go
|
||||
medals := []string{"gold", "silver", "bronze"}
|
||||
```
|
||||
|
||||
隱式地對切片的每個元素進行賦值操作, 類似這樣寫的行為:
|
||||
|
||||
```Go
|
||||
medals[0] = "gold"
|
||||
medals[1] = "silver"
|
||||
medals[2] = "bronze"
|
||||
```
|
||||
|
||||
字典和管道的元素, 雖然不是普通的變量, 但是也有類似的隱式賦值行為.
|
||||
|
||||
不管是隱式還是顯示地賦值, 在賦值語句坐標的變量和右邊最終的求到的值必鬚有相衕的數據類型. 更直白地說, 隻有右邊的值對於左邊的變量是可賦值的, 賦值語句纔是允許的.
|
||||
|
||||
可賦值性的規則對於不衕類型有不衕要求, 對每個新類型有關的地方我們會專門解釋.
|
||||
對於目前我們已經討論過的類型, 它的規則是簡單的: 類型必鬚完全匹配, nil 可以賦值給任何指鍼或引用類型的變量. 常量(§3.6)有更靈活的規則, 這樣可以避免不必要的顯示類型轉換.
|
||||
|
||||
對於兩個值是否可以用 `==` 或 `!=` 進行相等比較的能力也和可賦值能力有關繫:
|
||||
對於任何的比較, 第一個操作必鬚是可用於第二個操作類型的變量的賦值的, 反之依然.
|
||||
和前麫一樣, 我們會對每個新類型比較有關的地方會做專門解釋.
|
||||
|
||||
|
||||
2181
ch2/ch2-04.html
Normal file
2181
ch2/ch2-04.html
Normal file
File diff suppressed because it is too large
Load Diff
2176
ch2/ch2-05.html
Normal file
2176
ch2/ch2-05.html
Normal file
File diff suppressed because it is too large
Load Diff
59
ch2/ch2-06-1.md
Normal file
59
ch2/ch2-06-1.md
Normal file
@@ -0,0 +1,59 @@
|
||||
### 2.6.1. 導入包
|
||||
|
||||
在Go程序中, 每個包都是有一個全侷唯一的導入路徑. 聲明中類似 "gopl.io/ch2/tempconv" 的字符串對應導入路徑. 語言的規範併沒有定義這些字符串的具體含義或包來自哪裏, 它們是由工具來解釋. 當使用 go 工具箱時(第十章), 一個導入路徑代錶一個目彔中的一個或多個Go源文件.
|
||||
|
||||
除了到導入路徑, 每個包還有一個包名, 包名一般是短小的(也不要求是是唯一的), 包名在包的聲明處指定. 按照慣例, 一個包的名字和包的導入路徑的最後一個字段相衕, 例如 gopl.io/ch2/tempconv 包的名字是 tempconv.
|
||||
|
||||
要使用 gopl.io/ch2/tempconv 包, 需要先導入:
|
||||
|
||||
```Go
|
||||
gopl.io/ch2/cf
|
||||
// Cf converts its numeric argument to Celsius and Fahrenheit.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"gopl.io/ch2/tempconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for _, arg := range os.Args[1:] {
|
||||
t, err := strconv.ParseFloat(arg, 64)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cf: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
f := tempconv.Fahrenheit(t)
|
||||
c := tempconv.Celsius(t)
|
||||
fmt.Printf("%s = %s, %s = %s\n",
|
||||
f, tempconv.FToC(f), c, tempconv.CToF(c))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
導入聲明將導入的包綁定到一個短小的名字, 然後通過該名字就可以引用包中導齣的全部內容. 上麫的導入聲明將允許我們以 tempconv.CToF 的方式來訪問 gopl.io/ch2/tempconv 包中的內容. 默認情況下, 導入的包綁定到 tempconv 名字, 但是我們也可以綁定到另一個名稱, 以避免名字衝突(§10.3).
|
||||
|
||||
cf 程序將命令行輸入的一個溫度在 Celsius 和 Fahrenheit 之間轉換:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch2/cf
|
||||
$ ./cf 32
|
||||
32°F = 0°C, 32°C = 89.6°F
|
||||
$ ./cf 212
|
||||
212°F = 100°C, 212°C = 413.6°F
|
||||
$ ./cf -40
|
||||
-40°F = -40°C, -40°C = -40°F
|
||||
```
|
||||
|
||||
如果導入一個包, 但是沒有使用該包將被當作一個錯誤. 這種強製檢測可以有效減少不必要的依賴, 雖然在調試期間會讓人討厭, 因為刪除一個類似 log.Print("got here!") 的打印可能導緻需要衕時刪除 log 包導入聲明, 否則, 編譯器將會髮齣一個錯誤. 在這種情況下, 我們需要將不必要的導入刪除或註釋掉.
|
||||
|
||||
不過有更好的解決方案, 我們可以使用 golang.org/x/tools/cmd/goimports 工具, 它可以根據需要自動添加或刪除導入的包; 許多編輯器都可以集成 goimports 工具, 然後在保存文件的時候自動允許它. 類似的還有 gofmt 工具, 可以用來格式化Go源文件.
|
||||
|
||||
**練習 2.2:** 寫一個通用的單位轉換程序, 用類似 cf 程序的方式從命令行讀取參數, 如果缺省的話則是從標準輸入讀取參數, 然後做類似 Celsius 和 Fahrenheit 的轉換,
|
||||
長度單位對應英尺和米, 重量單位對應磅和公斤 等等.
|
||||
|
||||
|
||||
|
||||
67
ch2/ch2-06-2.md
Normal file
67
ch2/ch2-06-2.md
Normal file
@@ -0,0 +1,67 @@
|
||||
### 2.6.2. 包的初始化
|
||||
|
||||
包的初始化首先是解決包級變量的依賴順序, 然後安裝包級變量聲明齣現的順序依次初始化:
|
||||
|
||||
```Go
|
||||
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 文件根據文件名排序, 然後依次調用編譯器編譯.
|
||||
|
||||
對於在包級彆聲明的變量, 如果有初始化錶達式則用錶達式初始化, 還有一些沒有初始化錶達式的, 例如 某些錶格數據 初始化併不是一個簡單的賦值過程. 在這種情況下, 我們可以用 init 初始化函數來簡化工作. 每個文件都可以包含多個 init 初始化函數
|
||||
|
||||
```Go
|
||||
func init() { /* ... */ }
|
||||
```
|
||||
|
||||
這樣的init初始化函數除了不能被調用或引用外, 其他行為和普通函數類似. 在每個文件中的init初始化函數, 在程序開始執行時按照它們聲明的順序被自動調用.
|
||||
|
||||
每個包在解決依賴的前提下, 以導入聲明的順序初始化, 每個包隻會被初始化一次. 因此, 如果一個 p 包導入了 q 包, 那麼在 p 包初始化的時候可以認為 q 包已經初始化過了. 初始化工作是自下而上進行的, main 包最後被初始化. 以這種方式, 確保 在 main 函數執行之前, 所有的包都已經初始化了.
|
||||
|
||||
下麫的代碼定義了一個 PopCount 函數, 用於返迴一個數字中含二進製1bit的個數. 它使用 init 初始化函數來生成輔助錶格 pc, pc 錶格用於處理每個8bit寬度的數字含二進製的1bit的個數, 這樣的話在處理64bit寬度的數字時就沒有必要循環64次, 隻需要8次査錶就可以了. (這併不是最快的統計1bit數目的算法, 但是他可以方便演示init函數的用法, 併且演示了如果預生成輔助錶格, 這是編程中常用的技朮.)
|
||||
|
||||
```Go
|
||||
gopl.io/ch2/popcount
|
||||
package popcount
|
||||
|
||||
// pc[i] is the population count of i.
|
||||
var pc [256]byte
|
||||
|
||||
func init() {
|
||||
for i := range pc {
|
||||
pc[i] = pc[i/2] + byte(i&1)
|
||||
}
|
||||
}
|
||||
|
||||
// PopCount returns the population count (number of set bits) of x.
|
||||
func PopCount(x uint64) int {
|
||||
return int(pc[byte(x>>(0*8))] +
|
||||
pc[byte(x>>(1*8))] +
|
||||
pc[byte(x>>(2*8))] +
|
||||
pc[byte(x>>(3*8))] +
|
||||
pc[byte(x>>(4*8))] +
|
||||
pc[byte(x>>(5*8))] +
|
||||
pc[byte(x>>(6*8))] +
|
||||
pc[byte(x>>(7*8))])
|
||||
}
|
||||
```
|
||||
|
||||
要註意的是 init 函數中, range 循環隻使用了索引, 省略了沒有用到的值部分.
|
||||
循環也可以這樣寫:
|
||||
|
||||
```Go
|
||||
for i, _ := range pc {
|
||||
```
|
||||
|
||||
我們在下一節和10.5節還將看到其它使用init函數的地方.
|
||||
|
||||
**練習2.3:** 重寫 PopCount 函數, 用一個循環代替單一的錶達式. 比較兩個版本的性能. (11.4節將展示如何繫統地比較兩個不衕實現的性能.)
|
||||
|
||||
**練習2.4:** 用移位的算法重寫 PopCount 函數, 每次測試最右邊的1bit, 然後統計總數. 比較和査錶算法的性能差異.
|
||||
|
||||
**練習2.5:** 錶達式 `x&(x-1)` 用於將 x 的最低的一個1bit位清零. 使用這個格式重寫 PopCount 函數, 然後比較性能.
|
||||
|
||||
2245
ch2/ch2-06.html
Normal file
2245
ch2/ch2-06.html
Normal file
File diff suppressed because it is too large
Load Diff
2225
ch2/ch2-07.html
Normal file
2225
ch2/ch2-07.html
Normal file
File diff suppressed because it is too large
Load Diff
2112
ch2/ch2.html
Normal file
2112
ch2/ch2.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user