15. 指針
什么是指針?
指針是一種存儲變量內存地址(Memory Address)的變量。
如上圖所示,變量 b
的值為 156
,而 b
的內存地址為 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 可能處於內存的任何位置,你應該會得到一個不同的地址。
指針的零值(Zero Value)
指針的零值是 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
解引用的語法是 *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,由於 a
指向了 b
,因此 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 行,我們向函數 change
傳遞了指針變量 b
,而 b
存儲了 a
的地址。程序的第 8 行在 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 行中,我們將數組的地址傳遞給了 modify
函數。在第 8 行,我們在 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)
}
該程序也會輸出 [90 90 91]
。
這種方式向函數傳遞一個數組指針參數,並在函數內修改數組。盡管它是有效的,但卻不是 Go 語言慣用的實現方式。我們最好使用切片來處理。
接下來我們用[切片]來重寫之前的代碼。
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 語言的習慣。
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)。