在介紹Golang指標隱式間接參照前,先簡單說下Go 語言的指標 (Pointer),一個指標可以指向任何一個值的記憶體地址 它指向那個值的記憶體地址,在 32 位機器上佔用 4 個位元組,在 64 位機器上佔用 8 個位元組,並且與它所指向的值的大小無關。大致上理解如下:
使用一個指標參照一個值被稱為間接參照。
注意 1:golang 指標Dereferencing(解除參照)是什麼意思?
在 Go 語言中,指標解除參照(dereferencing)是指通過指標存取指標所指向的記憶體地址上儲存的值。在指標變數前加上 * 符號可以進行指標解除參照操作。指標解除參照會返回指標所指向的記憶體地址上儲存的值。例如,假設有一個指向int型別變數的指標:
var x int = 42 var p *int = &x要存取p指標指向的值,可以使用指標解除參照:
fmt.Println(*p) // 輸出:42可以看到,使用操作符存取指標指向的值時,需要將其放置在指標變數的前面。如果嘗試使用操作符存取一個空指標,會引發執行時錯誤。因此,在解除參照指標之前,通常需要確保指標不是空指標。
注意 2:在Go語言中,直接砍掉了 C 語言指標最複雜的指標運算部分,只留下了獲取指標(
&
運運算元)和獲取物件(*
運運算元)的運算,用法和C語言很類似。但不同的是,Go語言中沒有->
操作符來呼叫指標所屬的成員,而與一般物件一樣,都是使用.
來呼叫。注意 3:Go 語言中一個指標被定義後沒有分配到任何變數時,它的值為
nil
。
在 Go 語言中,new 函數用於動態地分配記憶體,返回一個指向新分配的零值的指標。它的語法如下:
func new(Type) *Type
其中,Type 表示要分配的記憶體的型別,new 函數返回一個指向 Type 型別的新分配的零值的指標。但是需要注意的是,new 函數只分配記憶體,並返回指向新分配的零值的指標,而不會初始化該記憶體。
注意 1:用new(structName):這個方法得到的是*structName型別,即類的指標型別;用structName{init para}:這個方法得到的是structName型別,即類的範例型別,不是指標。
注意 2:new函數更多細節介紹請參見《Go語言new( )函數》這篇博文。
Go 語言自帶指標隱式解除參照 :對於一些複雜型別的指標, 如果要存取成員變數時候需要寫成類似*p.field
的形式時,只需要p.field
即可存取相應的成員。以下複雜型別自帶指標隱式解除參照:
結構體欄位可以通過結構體指標來存取。如果我們有一個指向結構體的指標 p
,那麼可以通過 (*p).X
來存取其欄位 X
。不過這麼寫太囉嗦了,所以語言也允許我們使用隱式間接參照,直接寫 p.X
就可以。範例程式碼如下:
package main import ( "fmt" ) type Student struct { name string age int weight float32 score []int } func main(){ pp := new(Student) //使用 new 關鍵字建立一個指標 *pp = Student{"qishuangming", 23, 65.0, []int{2, 3, 6}} fmt.Printf("stu pp have %d subjects\n", len((*pp).score)) //按照我們對指標的瞭解,對Student結構體物件pp顯示賦值的話需要使用解除參照語法進行賦值,但是實際編碼時都會省去*,寫法如下行所示。 fmt.Printf("stu pp have %d subjects\n", len(pp.score)) //編譯器會自動將指標解除參照,並存取結構體中的對應欄位,這個過程被稱為隱式間接參照。 }
同樣指向陣列的指標可以隱式解除參照陣列中的元素。
var arr [3]int p := &arr p[0] = 1 // 等價於 (*p)[0] = 1
切片實際上是對底層陣列的封裝,因此指向切片的指標可以隱式解除參照切片中的元素。
s := []int{1, 2, 3} p := &s p[0] = 4 // 等價於 (*p)[0] = 4
map 是參照型別,當我們使用 map
型別的變數存取元素時,也不需要使用 *
運運算元進行解除參照,Golang 會自動幫我們解除參照。
m := map[string]int{"a": 1, "b": 2} fmt.Println(m["a"]) // 隱式解除參照
在 Golang 中,函數型別也是一種型別,它可以使用指標型別來表示函數的地址。如果我們定義了一個函數型別的變數,並將一個函數的地址賦值給它,那麼我們可以直接呼叫該變數,並且不需要使用 *
運運算元進行解除參照。
例如,以下程式碼演示了函數型別指標的隱式解除參照:
type Add func(a, b int) int func main() { var add Add add = func(a, b int) int { return a + b } println(add) //0x10cf168 sum := add(1, 2) // 隱式解除參照 fmt.Println(sum) }
在上面的程式碼中,我們定義了一個函數型別 Add
,它接受兩個 int
型別的引數並返回一個 int
型別的值。我們定義了一個變數 add
,它的型別是 Add
。我們將一個函數的地址賦值給了 add
變數,然後直接呼叫了 add
變數,不需要使用 *
運運算元進行解除參照。
注意 1:函數型別指標的隱式解除參照僅適用於函數型別變數的呼叫,而不適用於存取函數型別變數的成員。如果我們想要存取函數型別變數的成員,還是需要使用
*
運運算元進行解除參照。注意 2:在 Go 中,基本型別(如 int、float、bool 等)以及字串型別等非參照型別都沒有指標隱式解除參照的行為。這意味著,如果需要存取基本型別的指標指向的值,必須顯式地使用 * 運運算元來解除參照指標。下面是一個範例:
var i int p := &i *p = 1 // 顯式解除參照指標來修改指標所指向的值 fmt.Println(i) // 輸出 1另外,對於基本型別而言,使用指標可能會導致效能下降。因此,在使用指標時應該謹慎,並且只在必要的情況下使用指標來傳遞資料。
在 Go 中,指標隱式解除參照是指通過指標直接存取指標所指向的值,而不需要顯式地使用 * 運運算元來解除參照指標。對於一些複雜型別的指標(結構體型別指標、陣列/切片型別指標、字典型別、func型別), 如果要存取成員變數時候需要寫成類似*p.field
的形式時,只需要p.field
即可存取相應的成員。