[譯] part 15: golang 指標pointers
指標是儲存另一個變數的記憶體地址的變數。

在上面的圖示中,變數 b
值為 156 並存儲在記憶體地址 0x1040a124 處。變數 a
儲存了 b
的地址,那麼 a
就是指標並指向 b
。
宣告指標
* T
是指標變數的型別,它指向型別為 T
的值。
看段程式碼吧,
package main import ( "fmt" ) func main() { b := 255 var a *int = &b fmt.Printf("Type of a is %T\n", a) fmt.Println("address of b is", a) } 複製程式碼
&
運算子用於獲取變數的地址。上面程式的第 9 行,我們將 b
的地址分配給其型別為 * int
的 a
。現在可以說 a
指向 b
。當我們列印 a``的值時就是
b`的地址。輸出,
Type of a is *int address of b is 0x1040a124 複製程式碼
你可能會獲得不同的地址,因為 b
可以在記憶體中的任何位置。
指標的零值
指標的零值是 nil
package main import ( "fmt" ) func main() { a := 25 var b *int if b == nil { fmt.Println("b is", b) b = &a fmt.Println("b after initialization is", b) } } 複製程式碼
b
在上述程式中最初為 nil
,然後將 a
的地址賦值給 b
。輸出,
b is <nil> b after initialisation is 0x1040a124 複製程式碼
指標解引用
解引用指標意味著訪問指標指向的變數的值。 * a
是解引用的語法。
看看是如何執行的,
package main import ( "fmt" ) func main() { b := 255 a := &b fmt.Println("address of b is", a) fmt.Println("value of b is", *a) } 複製程式碼
在上述程式的第 10 行,我們解引用指標 a
並列印它的值。正如預期的那樣,它打印出 b
的值。該程式的輸出是
address of b is 0x1040a124 value of b is 255 複製程式碼
再寫一個程式,我們用指標改變 b 中的值。
package main import ( "fmt" ) func main() { b := 255 a := &b fmt.Println("address of b is", a) fmt.Println("value of b is", *a) *a++ fmt.Println("new value of b is", b) } 複製程式碼
上面程式的第 12 行,我們將 a
指向的值增加 1,它將改變 b
的值,因為 a
指向 b
。因此, b
的值變為 256。程式的輸出是
address of b is 0x1040a124 value of b is 255 new value of b is 256 複製程式碼
將指標傳遞給函式
package main import ( "fmt" ) func change(val *int) { *val = 55 } func main() { a := 58 fmt.Println("value of a before function call is",a) b := &a change(b) fmt.Println("value of a after function call is", a) } 複製程式碼
在上面程式的第 14 行,我們傳遞指標變數 b
給 change
函式。在 change
函式內部,使用解引用來修改 a
的值。此程式輸出,
value of a before function call is 58 value of a after function call is 55 複製程式碼
不要將指向陣列的指標作為函式的引數,應該改用切片
我們假設想在函式內部對陣列進行一些修改,並且陣列所做的修改應該對呼叫者可見。一種方法是將指向陣列的指標作為函式的引數傳遞。
package main import ( "fmt" ) func modify(arr *[3]int) { (*arr)[0] = 90 } func main() { a := [3]int{89, 90, 91} modify(&a) fmt.Println(a) } 複製程式碼
在上面的程式的第 13 行,我們將陣列 a
的地址傳遞給 modify
函式。在 modify
函式中,我們解除引用 arr
並將 90 分配給陣列的第一個元素。該程式輸出[90 90 91]
a [x]
是 (* a)[x]
的簡寫。所以上面程式中的 (* arr)[0]
可以用 arr [0]
代替。讓我們用這個語法重寫上面的程式。
package main import ( "fmt" ) func modify(arr *[3]int) { arr[0] = 90 } func main() { a := [3]int{89, 90, 91} modify(&a) fmt.Println(a) } 複製程式碼
Run in playgroud 該程式也會輸出 [90 90 91]
雖然這種將指向陣列的指標作為函式的引數傳遞並對其進行修改的方式有效,但這並不是 Go 中的慣用方法。我們有切片slice )。
我們使用切片重新上述程式碼,
package main import ( "fmt" ) func modify(sls []int) { sls[0] = 90 } func main() { a := [3]int{89, 90, 91} modify(a[:]) fmt.Println(a) } 複製程式碼
在上面程式的第 13 行中,我們將一個切片傳遞給 modify
函式。切片的第一個元素在 modify
函式內被修改為 90。該程式也輸出 [90 90 91]
。所以忘記將指標傳遞給陣列吧,使用切片更乾淨,是慣用的 Go :)。 譯者注: But even this style isn't idiomatic Go. Use slices instead.
這句話是 Go 官方文件推薦的,其實重要的一點是,在 Go 中陣列是定長的,所以看到按引用傳遞的定義 func modify(arr *[3]int)
是這個樣子。如果我的陣列要擴容還得修改入參,非常不靈活。當然還有其他的弊病,如果沒有確定陣列能幹什麼,那就按官方文件的建議來吧。
Go 不支援指標算數運算
Go 不支援指標算運算,這點和像 C 這樣的其他語言不同。
package main func main() { b := [...]int{109, 110, 111} p := &b p++ } 複製程式碼
上面的程式將丟擲編譯錯誤 main.go:6: invalid operation: p++ (non-numeric type *[3]int)
我在 github 中建立了一個 程式 ,它涵蓋了我們這一節討論過的所有內容。