站長資訊網(wǎng)
最全最豐富的資訊網(wǎng)站

golang有指針嗎

golang有指針。Go語言對指針的支持介于Java語言和C/C++語言之間,它既沒有像Java那樣取消了代碼對指針的直接操作的能力,也避免了C/C++中由于對指針的濫用而造成的安全和可靠性問題。

golang有指針嗎

本教程操作環(huán)境:windows10系統(tǒng)、GO 1.11.2、thinkpad t480電腦。

指針是一個代表著某個內(nèi)存地址的值,這個內(nèi)存地址往往是在內(nèi)存中存儲的另一個變量的值的起始位置。

指針地址和變量空間

Go語言保留了指針, 但是與C語言指針有所不同. 主要體現(xiàn)在:

  • 默認值:nil

  • 操作符 & 取變量地址, * 通過指針訪問目標對象。

  • 不支持指針運算,不支持 -> 運算符,直接用 . 訪問目標成員。

先來看一段代碼:

package main  import "fmt"  func main(){  var x int = 99 var p *int = &x fmt.Println(p) }

當我們運行到 var x int = 99 時,在內(nèi)存中就會生成一個空間,這個空間我們給它起了個名字叫 x,同時, 它也有一個地址,例如: 0xc00000a0c8,當我們想要使用這個空間時,我們可以用地址去訪問,也可以用我們給它起的名字 x 去訪問.

繼續(xù)運行到 var p *int = &x 時,我們定義了一個指針變量 p,這個 p 就存儲了變量 x 的地址.

所以,指針就是地址,指針變量就是存儲地址的變量。

接著,我們更改 x 的內(nèi)容:

package main  import "fmt"  func main() { 	var x int = 99 	var p *int = &x  	fmt.Println(p)  	x = 100  	fmt.Println("x: ", x) 	fmt.Println("*p: ", *p) 	 	*p = 999  	fmt.Println("x: ", x) 	fmt.Println("*p: ", *p) }

可以發(fā)現(xiàn), x*p 的結(jié)果一樣的。

其中, *p 稱為 解引用 或者 間接引用

*p = 999 是通過借助 x 變量的地址,來操作 x 對應的空間。

不管是 x 還是 *p , 我們操作的都是同一個空間。

推薦學習:Golang教程

棧幀的內(nèi)存布局

首先, 先來看一下內(nèi)存布局圖, 以 32位 為例.

golang有指針嗎

其中, 數(shù)據(jù)區(qū)保存的是初始化后的數(shù)據(jù).

上面的代碼都存儲在棧區(qū). 一般 make() 或者 new() 出來的都存儲在堆區(qū)

接下來, 我們來了解一個新的概念: 棧幀.

棧幀: 用來給函數(shù)運行提供內(nèi)存空間, 取內(nèi)存于 stack 上.

當函數(shù)調(diào)用時, 產(chǎn)生棧幀; 函數(shù)調(diào)用結(jié)束, 釋放棧幀.

那么棧幀用來存放什么?

  • 局部變量
  • 形參
  • 內(nèi)存字段描述值

其中, 形參與局部變量存儲地位等同

當我們的程序運行時, 首先運行 main(), 這時就產(chǎn)生了一個棧幀.

當運行到 var x int = 99 時, 就會在棧幀里面產(chǎn)生一個空間.

同理, 運行到 var p *int = &x 時也會在棧幀里產(chǎn)生一個空間.

如下圖所示:

golang有指針嗎

我們增加一個函數(shù), 再來研究一下.

package mainimport "fmt"func test(m int){ 	var y int = 66 	y += m}func main() { 	var x int = 99 	var p *int = &x  	fmt.Println(p)  	x = 100  	fmt.Println("x: ", x) 	fmt.Println("*p: ", *p)  	test(11)  	*p = 999  	fmt.Println("x: ", x) 	fmt.Println("*p: ", *p)}

如下圖所示, 當運行到 test(11) 時, 會繼續(xù)產(chǎn)生一個棧幀, 這時 main() 產(chǎn)生的棧幀還沒有結(jié)束.

golang有指針嗎

test() 運行完畢時, 就會釋放掉這個棧幀.

golang有指針嗎

空指針與野指針

空指針: 未被初始化的指針.

var p *int

這時如果我們想要對其取值操作 *p, 會報錯.

野指針: 被一片無效的地址空間初始化.

var p *int = 0xc00000a0c8

指針變量的內(nèi)存存儲

表達式 new(T) 將創(chuàng)建一個 T 類型的匿名變量, 所做的是為 T 類型的新值分配并清零一塊內(nèi)存空間, 然后將這塊內(nèi)存空間的地址作為結(jié)果返回, 而這個結(jié)果就是指向這個新的 T 類型值的指針值, 返回的指針類型為 *T.

new() 創(chuàng)建的內(nèi)存空間位于heap上, 空間的默認值為數(shù)據(jù)類型的默認值. 如: p := new(int)*p0.

package mainimport "fmt"func main(){ 	p := new(int) 	fmt.Println(p) 	fmt.Println(*p)}

這時 p 就不再是空指針或者野指針.

我們只需使用 new() 函數(shù), 無需擔心其內(nèi)存的生命周期或者怎樣將其刪除, 因為Go語言的內(nèi)存管理系統(tǒng)會幫我們打理一切.

接著我們改一下*p的值:

package mainimport "fmt"func main(){ 	p := new(int) 	 	*p = 1000 	 	fmt.Println(p) 	fmt.Println(*p)}

這個時候注意了, *p = 1000 中的 *pfmt.Println(*p) 中的 *p 是一樣的嗎?

大家先思考一下, 然后先來看一個簡單的例子:

var x int = 10var y int = 20x = y

好, 大家思考一下上面代碼中, var y int = 20 中的 yx = y 中的 y 一樣不一樣?

結(jié)論: 不一樣

var y int = 20 中的 y 代表的是內(nèi)存空間, 我們一般把這樣的稱之為左值; 而 x = y 中的 y 代表的是內(nèi)存空間中的內(nèi)容, 我們一般稱之為右值.

x = y 表示的是把 y 對應的內(nèi)存空間的內(nèi)容寫到x內(nèi)存空間中.

等號左邊的變量代表變量所指向的內(nèi)存空間, 相當于操作.

等號右邊的變量代表變量內(nèi)存空間存儲的數(shù)據(jù)值, 相當于操作.

在了解了這個之后, 我們再來看一下之前的代碼.

p := new(int)*p = 1000fmt.Println(*p)

所以, *p = 1000 的意思是把1000寫到 *p 的內(nèi)存中去;

fmt.Println(*p) 是把 *p的內(nèi)存空間中存儲的數(shù)據(jù)值打印出來.

所以這兩者是不一樣的.

如果我們不在main()創(chuàng)建會怎樣?

func foo() { 	p := new(int)  	*p = 1000}

我們上面已經(jīng)說過了, 當運行 foo() 時會產(chǎn)生一個棧幀, 運行結(jié)束, 釋放棧幀.

那么這個時候, p 還在不在?

p 在哪? 棧幀是在棧上, 而 p 因為是 new() 生成的, 所以在 上. 所以, p 沒有消失, p 對應的內(nèi)存值也沒有消失, 所以利用這個我們可以實現(xiàn)傳地址.

對于堆區(qū), 我們通常認為它是無限的. 但是無限的前提是必須申請完使用, 使用完后立即釋放.

函數(shù)的傳參

明白了上面的內(nèi)容, 我們再去了解指針作為函數(shù)參數(shù)就會容易很多.

傳地址(引用): 將地址值作為函數(shù)參數(shù)傳遞.

傳值(數(shù)據(jù)): 將實參的值拷貝一份給形參.

無論是傳地址還是傳值, 都是實參將自己的值拷貝一份給形參.只不過這個值有可能是地址, 有可能是數(shù)據(jù).

所以, 函數(shù)傳參永遠都是值傳遞.

了解了概念之后, 我們來看一個經(jīng)典的例子:

package mainimport "fmt"func swap(x, y int){ 	x, y = y, x 	fmt.Println("swap  x: ", x, "y: ", y)}func main(){ 	x, y := 10, 20 	swap(x, y) 	fmt.Println("main  x: ", x, "y: ", y)}

結(jié)果:

swap  x:  20 y:  10main  x:  10 y:  20

我們先來簡單分析一下為什么不一樣.

首先當運行 main() 時, 系統(tǒng)在棧區(qū)產(chǎn)生一個棧幀, 該棧幀里有 xy 兩個變量.

當運行 swap() 時, 系統(tǒng)在棧區(qū)產(chǎn)生一個棧幀, 該棧幀里面有 xy 兩個變量.

運行 x, y = y, x 后, 交換 swap() 產(chǎn)生的棧幀里的 xy 值. 這時 main() 里的 xy 沒有變.

swap() 運行完畢后, 對應的棧幀釋放, 棧幀里的x y 值也隨之消失.

所以, 當運行 fmt.Println("main x: ", x, "y: ", y) 這句話時, 其值依然沒有變.

接下來我們看一下參數(shù)為地址值時的情況.

傳地址的核心思想是: 在自己的棧幀空間中修改其它棧幀空間中的值.

而傳值的思想是: 在自己的棧幀空間中修改自己棧幀空間中的值.

注意理解其中的差別.

繼續(xù)看以下這段代碼:

package mainimport "fmt"func swap2(a, b *int){ 	*a, *b = *b, *a}func main(){ 	x, y := 10, 20 	swap(x, y) 	fmt.Println("main  x: ", x, "y: ", y)}

結(jié)果:

main  x:  20 y:  10

這里并沒有違反 函數(shù)傳參永遠都是值傳遞 這句話, 只不過這個時候這個值為地址值.

這個時候, xy 的值就完成了交換.

我們來分析一下這個過程.

首先運行 main() 后創(chuàng)建一個棧幀, 里面有 x y 兩個變量.

運行 swap2() 時, 同樣創(chuàng)建一個棧幀, 里面有 a b 兩個變量.

注意這個時候, a b 中存儲的值是 x y 的地址.

當運行到 *a, *b = *b, *a 時, 左邊的 *a 代表的是 x 的內(nèi)存地址, 右邊的 *b 代表的是 y 的內(nèi)存地址中的內(nèi)容. 所以這個時候, main() 中的 x 就被替換掉了.

所以, 這是在 swap2() 中操作 main() 里的變量值.

現(xiàn)在 swap2() 再釋放也沒有關(guān)系了, 因為 main() 里的值已經(jīng)被改了.

贊(0)
分享到: 更多 (0)
網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號
…久久精品99久久香蕉国产| 精品国产日产一区二区三区 | 亚洲中文字幕一区精品自拍| 精品深夜AV无码一区二区| 99精品视频在线| 国产精品婷婷午夜在线观看| 亚洲欧美日韩久久精品| 一本色道久久88综合日韩精品| 精品国产一区在线观看| 国产精品爽黄69天堂a片| 亚洲日韩精品无码AV海量| 2020亚洲男人天堂精品| 国产精品免费精品自在线观看| 亚洲mv国产精品mv日本mv| 久久精品黄AA片一区二区三区| 久久99蜜桃精品久久久久小说| 国产精品久久久久jk制服| 久热这里只精品99re8久| 久久亚洲国产精品一区二区| 奇米影视国产精品四色| 国内精品99亚洲免费高清| 成人午夜视频精品一区| 久久精品国产精品亚洲人人 | 精品熟女少妇a∨免费久久| 老司机午夜精品视频资源| 日韩精品无码熟人妻视频| 亚洲国产精品久久久久婷婷老年| 亚洲AV永久无码精品水牛影视| 国产成人精品一区二区三区免费| 国产精品va无码一区二区| 久久精品国产亚洲AV网站| 久久精品一区二区免费看| 国产精品多人p群无码| 久久无码专区国产精品| 午夜精品美女自拍福到在线| 99精品热女视频专线| 久久99国产乱子伦精品免费| 亚洲美女精品视频| 国产精品吹潮香蕉在线观看| 国产精品久久婷婷六月丁香| 精品一区二区ww|