Golang指標隱式間接引用

人艱不拆_zmc發表於2023-05-16

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即可訪問相應的成員。

相關文章