1、Golang指標
在介紹Golang指標隱式間接引用前,先簡單說下Go 語言的指標 (Pointer),一個指標可以指向任何一個值的記憶體地址 它指向那個值的記憶體地址,在 32 位機器上佔用 4 個位元組,在 64 位機器上佔用 8 個位元組,並且與它所指向的值的大小無關。大致上理解如下:
- 變數名前的 & 符號,是取變數的記憶體地址,不是取值;
- 資料型別前的 * 符號,代表要儲存的是對應資料型別的記憶體地址,不是存值;
- 變數名前的 * 符號,代表從記憶體地址中取值 (Dereferencing)。
使用一個指標引用一個值被稱為間接引用。
注意 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
。
2、new函式
在 Go 語言中,new 函式用於動態地分配記憶體,返回一個指向新分配的零值的指標。它的語法如下:
func new(Type) *Type
其中,Type 表示要分配的記憶體的型別,new 函式返回一個指向 Type 型別的新分配的零值的指標。但是需要注意的是,new 函式只分配記憶體,並返回指向新分配的零值的指標,而不會初始化該記憶體。
注意 1:用new(structName):這個方法得到的是*structName型別,即類的指標型別;用structName{init para}:這個方法得到的是structName型別,即類的例項型別,不是指標。
注意 2:new函式更多細節介紹請參見《Go語言new( )函式》這篇博文。
3、Golang指標隱式間接引用
Go 語言自帶指標隱式解引用 :對於一些複雜型別的指標, 如果要訪問成員變數時候需要寫成類似*p.field
的形式時,只需要p.field
即可訪問相應的成員。以下複雜型別自帶指標隱式解引用:
3.1 結構體型別指標隱式間接引用
結構體欄位可以透過結構體指標來訪問。如果我們有一個指向結構體的指標 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)) //編譯器會自動將指標解引用,並訪問結構體中的對應欄位,這個過程被稱為隱式間接引用。 }
3.2 陣列型別指標隱式間接引用
同樣指向陣列的指標可以隱式解引用陣列中的元素。
var arr [3]int p := &arr p[0] = 1 // 等價於 (*p)[0] = 1
3.3 切片型別指標隱式間接引用
切片實際上是對底層陣列的封裝,因此指向切片的指標可以隱式解引用切片中的元素。
s := []int{1, 2, 3} p := &s p[0] = 4 // 等價於 (*p)[0] = 4
3.4 字典型別隱式間接引用
map 是引用型別,當我們使用 map
型別的變數訪問元素時,也不需要使用 *
運算子進行解引用,Golang 會自動幫我們解引用。
m := map[string]int{"a": 1, "b": 2} fmt.Println(m["a"]) // 隱式解引用
3.5 func 型別隱式間接引用
在 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另外,對於基本型別而言,使用指標可能會導致效能下降。因此,在使用指標時應該謹慎,並且只在必要的情況下使用指標來傳遞資料。
4、總結
在 Go 中,指標隱式解引用是指透過指標直接訪問指標所指向的值,而不需要顯式地使用 * 運算子來解引用指標,編譯器會自動將指標解引用。對於一些複雜型別的指標(結構體型別指標、陣列/切片型別指標、字典型別、func型別), 如果要訪問成員變數時候需要寫成類似*p.field
的形式時,只需要p.field
即可訪問相應的成員。