[譯] part 15: golang 指標pointers

咔嘰咔嘰發表於2019-04-07

什麼是指標

指標是儲存另一個變數的記憶體地址的變數。

[譯] 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)
}
複製程式碼

Run in playgroud

運算子用於獲取變數的地址。上面程式的第 9 行,我們將b的地址分配給其型別為* inta。現在可以說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)
    }
}
複製程式碼

Run in playgroud

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)
}
複製程式碼

Run in playground

在上述程式的第 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)
}
複製程式碼

Run in playgroud

上面程式的第 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)
}
複製程式碼

Run in playgroud

在上面程式的第 14 行,我們傳遞指標變數bchange函式。在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)
}
複製程式碼

Run in playgroud

在上面的程式的第 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)
}
複製程式碼

Run in playgroud

在上面程式的第 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++
}
複製程式碼

Run in playgroud )

上面的程式將丟擲編譯錯誤main.go:6: invalid operation: p++ (non-numeric type *[3]int)

我在 github 中建立了一個程式,它涵蓋了我們這一節討論過的所有內容。

相關文章