原文:CalliCoder — Playing with Pointers in Golang
指標是一個變數,用來儲存另一個變數的記憶體位址。疑惑嗎?讓我來解釋一下。
首先讓我們來了解什麼是變數。當我們在撰寫任何程式時,我們需要在記憶體中儲存一些資料。資料儲存在記憶體中的特定位址。記憶體位址看起來會像 0xAFFFF
(這是以十六進制來表示記憶體位址)。
現在,要存取資料的話,我們需要知道資料的儲存位址。我們可以追蹤所有與我們的程式相關的儲存資料的記憶體位址。但是,想像一下要記住所有記憶體位址並使用它們存取資料有多麼困難。
這就是為什麼我們有變數的概念。變數只是為儲存資料的記憶體位置所取的一個方便的名稱。
指標也是一個變數。但它是一種特殊的變數,因為它儲存的資料不是一般的數值,如簡單的整數或字串,它是另一個變數的記憶體位址。
在上面的例子中,指標 p
含有數值 0x0001
,它是變數 a
的位址。
宣告指標
使用以下語法宣告一個型別為 T 的指標:
// A pointer of type T
var p *T
型別 T
是指標指向的變數的型別。舉例來說,以下是型別為 int
的指標:
// A pointer of type int
var p *int
上面的指標只能儲存 int
變數的記憶體位址。
指標的零值為 nil
。也就是說,任何未初始化的指標都會是 nil
。讓我們來看一個完整範例:
package main
import "fmt"
func main() {
var p *int
fmt.Println("p = ", p)
}
# Output
p = <nil>
初始化指標
你可以使用另一個變數的記憶體位址來初始化指標。可以使用 &
運算子取得變數的位址:
var x = 100
var p *int = &x
注意我們如何使用 &
運算子與變數 x
來取得它的位址,然後將位址指派給指標 p
。
與 Golang 中的其他變數一樣,編譯器也可以推斷出指標變數的型別。因此,你可以省略上面範例中指標 p
的型別宣告,並像這樣撰寫它:
var p = &a
讓我們看一個完整範例來更清楚的了解:
package main
import "fmt"
func main() {
var a = 5.67
var p = &a
fmt.Println("Value stored in variable a = ", a)
fmt.Println("Address of variable a = ", &a)
fmt.Println("Value stored in variable p = ", p)
}
# Output
Value stored in variable a = 5.67
Address of variable a = 0xc4200120a8
Value stored in variable p = 0xc4200120a8
解參考指標
你可以對指標使用 *
運算子來存取儲存在指標所指向的變數中的值。這稱為_解參考_或_間接取值_:
package main
import "fmt"
func main() {
var a = 100
var p = &a
fmt.Println("a = ", a)
fmt.Println("p = ", p)
fmt.Println("*p = ", *p)
}
# Output
a = 100
p = 0xc4200120a8
*p = 100
你不僅可以使用 *
運算子來存取所指向變數的值,還可以對其進行修改。以下範例透過指標 p
來設定儲存在變數 a
中的值:
package main
import "fmt"
func main() {
var a = 1000
var p = &a
fmt.Println("a (before) = ", a)
// Changing the value stored in the pointed variable through the pointer
*p = 2000
fmt.Println("a (after) = ", a)
}
# Output
a (before) = 1000
a (after) = 2000
使用內建的 new() 函數建立指標
你也可以使用內建的 new()
函數建立指標。new()
函數將型別當作參數,分配足夠的記憶體來容納該型別的值,然後回傳指向該型別的指標。
這有一個範例:
package main
import "fmt"
func main() {
ptr := new(int) // Pointer to an `int` type
*ptr = 100
fmt.Printf("Ptr = %#x, Ptr value = %d\n", ptr, *ptr)
}
# Output
Ptr = 0xc420014058, Ptr value = 100
指標的指標
指標可以指向任何型別的變數。它也可以指向另一個指標。下面的範例展示了如何建立指向另一個指標的指標:
package main
import "fmt"
func main() {
var a = 7.98
var p = &a
var pp = &p
fmt.Println("a = ", a)
fmt.Println("address of a = ", &a)
fmt.Println("p = ", p)
fmt.Println("address of p = ", &p)
fmt.Println("pp = ", pp)
// Dereferencing a pointer to pointer
fmt.Println("*pp = ", *pp)
fmt.Println("**pp = ", **pp)
}
# Output
a = 7.98
address of a = 0xc4200120a8
p = 0xc4200120a8
address of p = 0xc42000c028
pp = 0xc42000c028
*pp = 0xc4200120a8
**pp = 7.98
Go 沒有指標運算
如果你使用過 C/C++,那麼你必須知道這些語言支援指標運算。
舉例來說,你可以遞增/遞減指標來移動到下一個/上一個記憶體位址。
你可以對指標增加或減去一個整數值,你也可以使用關係運算子 ==
、<
、>
等比較兩個指標。
但是 Go 不支援對指標進行此類算術運算。任何此類運算都會導致編譯時期錯誤:
package main
func main() {
var x = 67
var p = &x
var p1 = p + 1 // Compiler Error: invalid operation
}
但是,你可以使用 ==
運算子比較兩個相同型別的指標是否相等。
package main
import "fmt"
func main() {
var a = 75
var p1 = &a
var p2 = &a
if p1 == p2 {
fmt.Println("Both pointers p1 and p2 point to the same variable.")
}
}
結論
我希望你了解什麼是指標、如何宣告和初始化指標,以及如何解參考指標。