99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

Go語(yǔ)言 指針

2023-02-16 17:37 更新

雖然Go吸收融合了很多其語(yǔ)言中的各種特性,但是Go主要被歸入C語(yǔ)言家族。其中一個(gè)重要的原因就是Go和C一樣,也支持指針。 當(dāng)然Go中的指針相比C指針有很多限制。本篇文章將介紹指針相關(guān)的各種概念和Go指針相關(guān)的各種細(xì)節(jié)。

內(nèi)存地址

在編程中,一個(gè)內(nèi)存地址用來(lái)定位一段內(nèi)存。

通常地,一個(gè)內(nèi)存地址用一個(gè)操作系統(tǒng)原生字(native word)來(lái)存儲(chǔ)。 一個(gè)原生字在32位操作系統(tǒng)上占4個(gè)字節(jié),在64位操作系統(tǒng)上占8個(gè)字節(jié)。 所以,32位操作系統(tǒng)上的理論最大支持內(nèi)存容量為4GB(1GB == 230字節(jié)),64位操作系統(tǒng)上的理論最大支持內(nèi)存容量為264Byte,即16EB(EB:艾字節(jié),1EB == 1024PB, 1PB == 1024TB, 1TB == 1024GB)。

內(nèi)存地址的字面形式常用整數(shù)的十六進(jìn)制字面量來(lái)表示,比如?0x1234CDEF?。

以后我們常簡(jiǎn)稱(chēng)內(nèi)存地址為地址。

值的地址

一個(gè)值的地址是指此值的直接部分占據(jù)的內(nèi)存的起始地址。在Go中,每個(gè)值都包含一個(gè)直接部分,但有些值可能還包含一個(gè)或多個(gè)間接部分,下下章將對(duì)此詳述。

什么是指針?

指針是Go中的一種類(lèi)型分類(lèi)(kind)。 一個(gè)指針可以存儲(chǔ)一個(gè)內(nèi)存地址;從地址通常為另外一個(gè)值的地址。

和C指針不一樣,為了安全起見(jiàn),Go指針有很多限制,詳見(jiàn)下面的章節(jié)。

指針類(lèi)型和值

在Go中,一個(gè)無(wú)名指針類(lèi)型的字面形式為?*T?,其中?T?為一個(gè)任意類(lèi)型。類(lèi)型?T?稱(chēng)為指針類(lèi)型?*T?的基類(lèi)型(base type)。 如果一個(gè)指針類(lèi)型的基類(lèi)型為?T?,則我們可以稱(chēng)此指針類(lèi)型為一個(gè)?T?指針類(lèi)型。

雖然我們可以聲明具名指針類(lèi)型,但是一般不推薦這么做,因?yàn)闊o(wú)名指針類(lèi)型的可讀性更高。

如果一個(gè)指針類(lèi)型的底層類(lèi)型是?*T?,則它的基類(lèi)型為?T?。

如果兩個(gè)無(wú)名指針類(lèi)型的基類(lèi)型為同一類(lèi)型,則這兩個(gè)無(wú)名指針類(lèi)型亦為同一類(lèi)型。

一些指針類(lèi)型的例子:

*int  // 一個(gè)基類(lèi)型為int的無(wú)名指針類(lèi)型。
**int // 一個(gè)多級(jí)無(wú)名指針類(lèi)型,它的基類(lèi)型為*int。

type Ptr *int // Ptr是一個(gè)具名指針類(lèi)型,它的基類(lèi)型為int。
type PP *Ptr  // PP是一個(gè)具名多級(jí)指針類(lèi)型,它的基類(lèi)型為Ptr。

指針類(lèi)型的零值的字面量使用預(yù)聲明的?nil?來(lái)表示。一個(gè)nil指針(常稱(chēng)為空指針)中不存儲(chǔ)任何地址。

如果一個(gè)指針類(lèi)型的基類(lèi)型為?T?,則此指針類(lèi)型的值只能存儲(chǔ)類(lèi)型為?T?的值的地址。

關(guān)于引用(reference)這個(gè)術(shù)語(yǔ)

在《Go語(yǔ)言101》中,術(shù)語(yǔ)“引用”暗示著一個(gè)關(guān)系。比如,如果一個(gè)指針中存儲(chǔ)著另外一個(gè)值的地址,則我們可以說(shuō)此指針值引用著另外一個(gè)值;同時(shí)另外一個(gè)值當(dāng)前至少有一個(gè)引用。 本書(shū)對(duì)此術(shù)語(yǔ)的使用和Go白皮書(shū)是一致的。

當(dāng)一個(gè)指針引用著另外一個(gè)值,我們也常說(shuō)此指針指向另外一個(gè)值。

如何獲取一個(gè)指針值?

有兩種方式來(lái)得到一個(gè)指針值:

  1. 我們可以用內(nèi)置函數(shù)?new?來(lái)為任何類(lèi)型的值開(kāi)辟一塊內(nèi)存并將此內(nèi)存塊的起始地址做為此值的地址返回。 假設(shè)?T?是任一類(lèi)型,則函數(shù)調(diào)用?new(T)?返回一個(gè)類(lèi)型為?*T?的指針值。 存儲(chǔ)在返回指針值所表示的地址處的值(可被看作是一個(gè)匿名變量)為?T?的零值。
  2. 我們也可以使用前置取地址操作符?&?來(lái)獲取一個(gè)可尋址的值的地址。 對(duì)于一個(gè)類(lèi)型為?T?的可尋址的值?t?,我們可以用?&t?來(lái)取得它的地址。?&t?的類(lèi)型為?*T?。

一般說(shuō)來(lái),一個(gè)可尋址的值是指被放置在內(nèi)存中某固定位置處的一個(gè)值(但放置在某固定位置處的一個(gè)值并非一定是可尋址的)。 目前,我們只需知道所有變量都是可以尋址的;但是所有常量、函數(shù)返回值和強(qiáng)制轉(zhuǎn)換結(jié)果都是不可尋址的。 當(dāng)一個(gè)變量被聲明的時(shí)候,Go運(yùn)行時(shí)將為此變量開(kāi)辟一段內(nèi)存。此內(nèi)存的起始地址即為此變量的地址。

更多可被(或不可被)尋址的值將在以后的文章中逐漸提及。 如果你已經(jīng)對(duì)Go比較熟悉,你可以閱讀此條總結(jié)來(lái)了解在Go中哪些值可以或不可以被尋址。

下一節(jié)中的例子將展示如何獲取一些值的地址。

指針(地址)解引用

我們可以使用前置解引用操作符?*?來(lái)訪問(wèn)存儲(chǔ)在一個(gè)指針?biāo)硎镜牡刂诽幍闹担创酥羔標(biāo)弥闹担?比如,對(duì)于基類(lèi)型為?T?的指針類(lèi)型的一個(gè)指針值?p?,我們可以用?*p?來(lái)表示地址?p?處的值。 此值的類(lèi)型為?T?。?*p?稱(chēng)為指針?p?的解引用。解引用是取地址的逆過(guò)程。

解引用一個(gè)nil指針將產(chǎn)生一個(gè)恐慌

下面這個(gè)例子展示了如何取地址和解引用。

package main

import "fmt"

func main() {
	p0 := new(int)   // p0指向一個(gè)int類(lèi)型的零值
	fmt.Println(p0)  // (打印出一個(gè)十六進(jìn)制形式的地址)
	fmt.Println(*p0) // 0

	x := *p0              // x是p0所引用的值的一個(gè)復(fù)制。
	p1, p2 := &x, &x      // p1和p2中都存儲(chǔ)著x的地址。
	                      // x、*p1和*p2表示著同一個(gè)int值。
	fmt.Println(p1 == p2) // true
	fmt.Println(p0 == p1) // false
	p3 := &*p0            // <=> p3 := &(*p0)
	                      // <=> p3 := p0
	                      // p3和p0中存儲(chǔ)的地址是一樣的。
	fmt.Println(p0 == p3) // true
	*p0, *p1 = 123, 789
	fmt.Println(*p2, x, *p3) // 789 789 123

	fmt.Printf("%T, %T \n", *p0, x) // int, int
	fmt.Printf("%T, %T \n", p0, p1) // *int, *int
}

下面這張圖描繪了上面這個(gè)例子中各個(gè)值之間的關(guān)系。

指針值

我們?yōu)槭裁葱枰羔槪?/h3>

讓我們先看一個(gè)例子:

package main

import "fmt"

func double(x int) {
	x += x
}

func main() {
	var a = 3
	double(a)
	fmt.Println(a) // 3
}

我們本期望上例中的?double?函數(shù)將變量?a?的值放大為原來(lái)的兩倍,但是事實(shí)證明我們的期望沒(méi)有得到實(shí)現(xiàn)。 為什么呢?因?yàn)樵贕o中,所有的賦值(包括函數(shù)調(diào)用傳參)過(guò)程都是一個(gè)值復(fù)制過(guò)程。 所以在上面的?double?函數(shù)體內(nèi)修改的是變量?a?的一個(gè)副本,而沒(méi)有修改變量?a?本身。

當(dāng)然我們可以讓double函數(shù)返回輸入?yún)?shù)的兩倍數(shù),但是此方法并非適用于所有場(chǎng)合。 下面這個(gè)例子通過(guò)將輸入?yún)?shù)的類(lèi)型改為一個(gè)指針類(lèi)型來(lái)達(dá)到同樣的目的。

package main

import "fmt"

func double(x *int) {
	*x += *x
	x = nil // 此行僅為講解目的
}

func main() {
	var a = 3
	double(&a)
	fmt.Println(a) // 6
	p := &a
	double(p)
	fmt.Println(a, p == nil) // 12 false
}

從上例可以看出,通過(guò)將?double?函數(shù)的輸入?yún)?shù)的類(lèi)型改為?*int?,傳入的實(shí)參?&a?和它在此函數(shù)體內(nèi)的一個(gè)副本?x?都引用著變量?a?。 所以對(duì)?*x?的修改等價(jià)于對(duì)?*p?(也就是變量?a?)的修改。 換句話說(shuō),新版本的?double?函數(shù)內(nèi)的操作可以反映到此函數(shù)外了。

當(dāng)然,在此函數(shù)體內(nèi)對(duì)傳入的指針實(shí)參的修改?x = nil?依舊不能反映到函數(shù)外,因?yàn)榇诵薷陌l(fā)生在此指針的一個(gè)副本上。 所以在?double?函數(shù)調(diào)用之后,局部變量?p?的值并沒(méi)有被修改為?nil?。

簡(jiǎn)而言之,指針提供了一種間接的途徑來(lái)訪問(wèn)和修改一些值。 雖然很多語(yǔ)言中沒(méi)有指針這個(gè)概念,但是指針被隱藏其它概念之中。

在Go中返回一個(gè)局部變量的地址是安全的

和C不一樣,Go是支持垃圾回收的,所以一個(gè)函數(shù)返回其內(nèi)聲明的局部變量的地址是絕對(duì)安全的。比如:

func newInt() *int {
	a := 3
	return &a
}

Go指針的一些限制

為了安全起見(jiàn),Go指針在使用上相對(duì)于C指針有很多限制。 通過(guò)施加這些限制,Go指針保留了C指針的好處,同時(shí)也避免了C指針的危險(xiǎn)性。

Go指針不支持算術(shù)運(yùn)算

在Go中,指針是不能參與算術(shù)運(yùn)算的。比如,對(duì)于一個(gè)指針?p?, 運(yùn)算?p++?和?p-2?都是非法的。

如果?p?為一個(gè)指向一個(gè)數(shù)值類(lèi)型值的指針,?*p++?將被編譯器認(rèn)為是合法的并且等價(jià)于?(*p)++?。 換句話說(shuō),解引用操作符?*?的優(yōu)先級(jí)都高于自增?++?和自減?--?操作符。

例子:

package main

import "fmt"

func main() {
	a := int64(5)
	p := &a

	// 下面這兩行編譯不通過(guò)。
	/*
	p++
	p = (&a) + 8
	*/

	*p++
	fmt.Println(*p, a)   // 6 6
	fmt.Println(p == &a) // true

	*&a++
	*&*&a++
	**&p++
	*&*p++
	fmt.Println(*p, a) // 10 10
}

一個(gè)指針類(lèi)型的值不能被隨意轉(zhuǎn)換為另一個(gè)指針類(lèi)型

在Go中,只有如下某個(gè)條件被滿足的情況下,一個(gè)類(lèi)型為T1的指針值才能被顯式轉(zhuǎn)換為另一個(gè)指針類(lèi)型T2

  1. 類(lèi)型?T1?和?T2?的底層類(lèi)型必須一致(忽略結(jié)構(gòu)體字段的標(biāo)簽)。 特別地,如果類(lèi)型?T1?和?T2?中只要有一個(gè)是無(wú)名類(lèi)型并且它們的底層類(lèi)型一致(考慮結(jié)構(gòu)體字段的標(biāo)簽),則此轉(zhuǎn)換可以是隱式的。 關(guān)于結(jié)構(gòu)體,請(qǐng)參閱下一篇文章。
  2. 類(lèi)型?T1?和?T2?都為無(wú)名類(lèi)型并且它們的基類(lèi)型的底層類(lèi)型一致(忽略結(jié)構(gòu)體字段的標(biāo)簽)。

比如,

type MyInt int64
type Ta    *int64
type Tb    *MyInt

對(duì)于上面所示的這些指針類(lèi)型,下面的事實(shí)成立:

  1. 類(lèi)型?*int64?的值可以被隱式轉(zhuǎn)換到類(lèi)型?Ta?,反之亦然(因?yàn)樗鼈兊牡讓宇?lèi)型均為?*int64?)。
  2. 類(lèi)型 ?*MyInt?的值可以被隱式轉(zhuǎn)換到類(lèi)型?Tb?,反之亦然(因?yàn)樗鼈兊牡讓宇?lèi)型均為?*MyInt?)。
  3. 類(lèi)型?*MyInt?的值可以被顯式轉(zhuǎn)換為類(lèi)型?*int64?,反之亦然(因?yàn)樗鼈兌际菬o(wú)名的并且它們的基類(lèi)型的底層類(lèi)型均為?int64?)。
  4. 類(lèi)型?Ta?的值不能直接被轉(zhuǎn)換為類(lèi)型?Tb?,即使是顯式轉(zhuǎn)換也是不行的。 但是,通過(guò)上述三條事實(shí),通過(guò)三層顯式轉(zhuǎn)換?Tb((*MyInt)((*int64)(ta)))?,一個(gè)類(lèi)型為?Ta?的值?ta?可以被間接地轉(zhuǎn)換為類(lèi)型?Tb?。

這些指針類(lèi)型的任何值都無(wú)法被轉(zhuǎn)換到類(lèi)型?*uint64?。

一個(gè)指針值不能和其它任一指針類(lèi)型的值進(jìn)行比較

Go指針值是支持(使用比較運(yùn)算符==!=)比較的。 但是,兩個(gè)指針只有在下列任一條件被滿足的時(shí)候才可以比較:

  1. 這兩個(gè)指針的類(lèi)型相同。
  2. 其中一個(gè)指針可以被隱式轉(zhuǎn)換為另一個(gè)指針的類(lèi)型。換句話說(shuō),這兩個(gè)指針的類(lèi)型的底層類(lèi)型必須一致并且至少其中一個(gè)指針類(lèi)型為無(wú)名的(考慮結(jié)構(gòu)體字段的標(biāo)簽)。
  3. 其中一個(gè)并且只有一個(gè)指針用類(lèi)型不確定的?nil?標(biāo)識(shí)符表示。

例子:

package main

func main() {
	type MyInt int64
	type Ta    *int64
	type Tb    *MyInt

	// 4個(gè)不同類(lèi)型的指針:
	var pa0 Ta
	var pa1 *int64
	var pb0 Tb
	var pb1 *MyInt

	// 下面這6行編譯沒(méi)問(wèn)題。它們的比較結(jié)果都為true。
	_ = pa0 == pa1
	_ = pb0 == pb1
	_ = pa0 == nil
	_ = pa1 == nil
	_ = pb0 == nil
	_ = pb1 == nil

	// 下面這三行編譯不通過(guò)。
	/*
	_ = pa0 == pb0
	_ = pa1 == pb1
	_ = pa0 == Tb(nil)
	*/
}

一個(gè)指針值不能被賦值給其它任意類(lèi)型的指針值

一個(gè)指針值可以被賦值給另一個(gè)指針值的條件和這兩個(gè)指針值可以比較的條件(見(jiàn)上一小節(jié))是一致的。

上述Go指針的限制是可以被打破的

unsafe標(biāo)準(zhǔn)庫(kù)包中提供的非類(lèi)型安全指針(?unsafe.Pointer?)機(jī)制可以被用來(lái)打破上述Go指針的安全限制。 ?unsafe.Pointer?類(lèi)型類(lèi)似于C語(yǔ)言中的?void*?。 但是,通常地,非類(lèi)型安全指針機(jī)制不推薦在Go日常編程中使用。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)