Golang指標隱式間接參照

2023-05-16 09:00:29

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