這篇文章將為大家詳細(xì)講解有關(guān)golang中是否有指針,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
創(chuàng)新互聯(lián)建站主打移動(dòng)網(wǎng)站、網(wǎng)站設(shè)計(jì)、成都網(wǎng)站設(shè)計(jì)、網(wǎng)站改版、網(wǎng)絡(luò)推廣、網(wǎng)站維護(hù)、主機(jī)域名、等互聯(lián)網(wǎng)信息服務(wù),為各行業(yè)提供服務(wù)。在技術(shù)實(shí)力的保障下,我們?yōu)榭蛻舫兄Z穩(wěn)定,放心的服務(wù),根據(jù)網(wǎng)站的內(nèi)容與功能再?zèng)Q定采用什么樣的設(shè)計(jì)。最后,要實(shí)現(xiàn)符合網(wǎng)站需求的內(nèi)容、功能與設(shè)計(jì),我們還會(huì)規(guī)劃穩(wěn)定安全的技術(shù)方案做保障。
指針是一個(gè)代表著某個(gè)內(nèi)存地址的值,這個(gè)內(nèi)存地址往往是在內(nèi)存中存儲(chǔ)的另一個(gè)變量的值的起始位置。
Go語言保留了指針, 但是與C語言指針有所不同. 主要體現(xiàn)在:
默認(rèn)值:nil
操作符 &
取變量地址, *
通過指針訪問目標(biāo)對(duì)象。
不支持指針運(yùn)算,不支持 ->
運(yùn)算符,直接用 .
訪問目標(biāo)成員。
先來看一段代碼:
package main import "fmt" func main(){ var x int = 99 var p *int = &x fmt.Println(p) }
當(dāng)我們運(yùn)行到 var x int = 99
時(shí),在內(nèi)存中就會(huì)生成一個(gè)空間,這個(gè)空間我們給它起了個(gè)名字叫 x
,同時(shí), 它也有一個(gè)地址,例如: 0xc00000a0c8
,當(dāng)我們想要使用這個(gè)空間時(shí),我們可以用地址去訪問,也可以用我們給它起的名字x
去訪問.
繼續(xù)運(yùn)行到 var p *int = &x
時(shí),我們定義了一個(gè)指針變量p
,這個(gè) p
就存儲(chǔ)了變量 x
的地址.
所以,指針就是地址,指針變量就是存儲(chǔ)地址的變量。
接著,我們更改 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
對(duì)應(yīng)的空間。
不管是x
還是*p
, 我們操作的都是同一個(gè)空間。
首先, 先來看一下內(nèi)存布局圖, 以 32位
為例.
其中, 數(shù)據(jù)區(qū)保存的是初始化后的數(shù)據(jù).
上面的代碼都存儲(chǔ)在棧區(qū). 一般 make()
或者 new()
出來的都存儲(chǔ)在堆區(qū)
接下來, 我們來了解一個(gè)新的概念: 棧幀.
棧幀: 用來給函數(shù)運(yùn)行提供內(nèi)存空間, 取內(nèi)存于 stack
上.
當(dāng)函數(shù)調(diào)用時(shí), 產(chǎn)生棧幀; 函數(shù)調(diào)用結(jié)束, 釋放棧幀.
那么棧幀用來存放什么?
局部變量
形參
內(nèi)存字段描述值
其中, 形參與局部變量存儲(chǔ)地位等同
當(dāng)我們的程序運(yùn)行時(shí), 首先運(yùn)行 main()
, 這時(shí)就產(chǎn)生了一個(gè)棧幀.
當(dāng)運(yùn)行到 var x int = 99
時(shí), 就會(huì)在棧幀里面產(chǎn)生一個(gè)空間.
同理, 運(yùn)行到 var p *int = &x
時(shí)也會(huì)在棧幀里產(chǎn)生一個(gè)空間.
如下圖所示:
我們?cè)黾右粋€(gè)函數(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)}
如下圖所示, 當(dāng)運(yùn)行到 test(11)
時(shí), 會(huì)繼續(xù)產(chǎn)生一個(gè)棧幀, 這時(shí) main()
產(chǎn)生的棧幀還沒有結(jié)束.
當(dāng) test()
運(yùn)行完畢時(shí), 就會(huì)釋放掉這個(gè)棧幀.
空指針: 未被初始化的指針.
var p *int
這時(shí)如果我們想要對(duì)其取值操作 *p
, 會(huì)報(bào)錯(cuò).
野指針: 被一片無效的地址空間初始化.
var p *int = 0xc00000a0c8
表達(dá)式 new(T)
將創(chuàng)建一個(gè) T
類型的匿名變量, 所做的是為 T
類型的新值分配并清零一塊內(nèi)存空間, 然后將這塊內(nèi)存空間的地址作為結(jié)果返回, 而這個(gè)結(jié)果就是指向這個(gè)新的 T
類型值的指針值, 返回的指針類型為 *T
.
new()
創(chuàng)建的內(nèi)存空間位于heap上, 空間的默認(rèn)值為數(shù)據(jù)類型的默認(rèn)值. 如: p := new(int)
則 *p
為 0
.
package mainimport "fmt"func main(){ p := new(int) fmt.Println(p) fmt.Println(*p)}
這時(shí) p
就不再是空指針或者野指針.
我們只需使用 new()
函數(shù), 無需擔(dān)心其內(nèi)存的生命周期或者怎樣將其刪除, 因?yàn)镚o語言的內(nèi)存管理系統(tǒng)會(huì)幫我們打理一切.
接著我們改一下*p
的值:
package mainimport "fmt"func main(){ p := new(int) *p = 1000 fmt.Println(p) fmt.Println(*p)}
這個(gè)時(shí)候注意了, *p = 1000
中的 *p
與 fmt.Println(*p)
中的 *p
是一樣的嗎?
大家先思考一下, 然后先來看一個(gè)簡(jiǎn)單的例子:
var x int = 10var y int = 20x = y
好, 大家思考一下上面代碼中, var y int = 20
中的 y
與 x = y
中的 y
一樣不一樣?
結(jié)論: 不一樣
var y int = 20
中的 y
代表的是內(nèi)存空間, 我們一般把這樣的稱之為左值; 而 x = y
中的 y
代表的是內(nèi)存空間中的內(nèi)容, 我們一般稱之為右值.
x = y
表示的是把y
對(duì)應(yīng)的內(nèi)存空間的內(nèi)容寫到x內(nèi)存空間中.
等號(hào)左邊的變量代表變量所指向的內(nèi)存空間, 相當(dāng)于寫操作.
等號(hào)右邊的變量代表變量?jī)?nèi)存空間存儲(chǔ)的數(shù)據(jù)值, 相當(dāng)于讀操作.
在了解了這個(gè)之后, 我們?cè)賮砜匆幌轮暗拇a.
p := new(int)*p = 1000fmt.Println(*p)
所以, *p = 1000
的意思是把1000寫到 *p
的內(nèi)存中去;
fmt.Println(*p)
是把 *p
的內(nèi)存空間中存儲(chǔ)的數(shù)據(jù)值打印出來.
所以這兩者是不一樣的.
如果我們不在main()創(chuàng)建會(huì)怎樣?
func foo() { p := new(int) *p = 1000}
我們上面已經(jīng)說過了, 當(dāng)運(yùn)行 foo()
時(shí)會(huì)產(chǎn)生一個(gè)棧幀, 運(yùn)行結(jié)束, 釋放棧幀.
那么這個(gè)時(shí)候, p
還在不在?
p
在哪? 棧幀是在棧上, 而 p
因?yàn)槭?new()
生成的, 所以在 堆
上. 所以, p
沒有消失, p
對(duì)應(yīng)的內(nèi)存值也沒有消失, 所以利用這個(gè)我們可以實(shí)現(xiàn)傳地址.
對(duì)于堆區(qū), 我們通常認(rèn)為它是無限的. 但是無限的前提是必須申請(qǐng)完使用, 使用完后立即釋放.
明白了上面的內(nèi)容, 我們?cè)偃チ私?strong>指針作為函數(shù)參數(shù)就會(huì)容易很多.
傳地址(引用): 將地址值作為函數(shù)參數(shù)傳遞.
傳值(數(shù)據(jù)): 將實(shí)參的值拷貝一份給形參.
無論是傳地址還是傳值, 都是實(shí)參將自己的值拷貝一份給形參.只不過這個(gè)值有可能是地址, 有可能是數(shù)據(jù).
所以, 函數(shù)傳參永遠(yuǎn)都是值傳遞.
了解了概念之后, 我們來看一個(gè)經(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
我們先來簡(jiǎn)單分析一下為什么不一樣.
首先當(dāng)運(yùn)行 main()
時(shí), 系統(tǒng)在棧區(qū)產(chǎn)生一個(gè)棧幀, 該棧幀里有 x
和 y
兩個(gè)變量.
當(dāng)運(yùn)行 swap()
時(shí), 系統(tǒng)在棧區(qū)產(chǎn)生一個(gè)棧幀, 該棧幀里面有 x
和 y
兩個(gè)變量.
運(yùn)行 x, y = y, x
后, 交換 swap()
產(chǎn)生的棧幀里的 xy
值. 這時(shí) main()
里的 xy
沒有變.
swap()
運(yùn)行完畢后, 對(duì)應(yīng)的棧幀釋放, 棧幀里的x
y
值也隨之消失.
所以, 當(dāng)運(yùn)行 fmt.Println("main x: ", x, "y: ", y)
這句話時(shí), 其值依然沒有變.
接下來我們看一下參數(shù)為地址值時(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ù)傳參永遠(yuǎn)都是值傳遞
這句話, 只不過這個(gè)時(shí)候這個(gè)值為地址值.
這個(gè)時(shí)候, x
與 y
的值就完成了交換.
我們來分析一下這個(gè)過程.
首先運(yùn)行 main()
后創(chuàng)建一個(gè)棧幀, 里面有 x
y
兩個(gè)變量.
運(yùn)行 swap2()
時(shí), 同樣創(chuàng)建一個(gè)棧幀, 里面有 a
b
兩個(gè)變量.
注意這個(gè)時(shí)候,a
和b
中存儲(chǔ)的值是x
和y
的地址.
當(dāng)運(yùn)行到 *a, *b = *b, *a
時(shí), 左邊的 *a
代表的是 x
的內(nèi)存地址, 右邊的 *b
代表的是 y
的內(nèi)存地址中的內(nèi)容. 所以這個(gè)時(shí)候, main()
中的 x
就被替換掉了.
所以, 這是在swap2()
中操作main()
里的變量值.
現(xiàn)在 swap2()
再釋放也沒有關(guān)系了, 因?yàn)?main()
里的值已經(jīng)被改了.
關(guān)于golang中是否有指針就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。