站長資訊網
        最全最豐富的資訊網站

        golang有指針嗎

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

        golang有指針嗎

        本教程操作環境:windows10系統、GO 1.11.2、thinkpad t480電腦。

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

        指針地址和變量空間

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

        • 默認值:nil

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

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

        先來看一段代碼:

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

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

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

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

        接著,我們更改 x 的內容:

        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) }

        可以發現, x*p 的結果一樣的。

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

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

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

        推薦學習:Golang教程

        棧幀的內存布局

        首先, 先來看一下內存布局圖, 以 32位 為例.

        golang有指針嗎

        其中, 數據區保存的是初始化后的數據.

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

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

        棧幀: 用來給函數運行提供內存空間, 取內存于 stack 上.

        當函數調用時, 產生棧幀; 函數調用結束, 釋放棧幀.

        那么棧幀用來存放什么?

        • 局部變量
        • 形參
        • 內存字段描述值

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

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

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

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

        如下圖所示:

        golang有指針嗎

        我們增加一個函數, 再來研究一下.

        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) 時, 會繼續產生一個棧幀, 這時 main() 產生的棧幀還沒有結束.

        golang有指針嗎

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

        golang有指針嗎

        空指針與野指針

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

        var p *int

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

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

        var p *int = 0xc00000a0c8

        指針變量的內存存儲

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

        new() 創建的內存空間位于heap上, 空間的默認值為數據類型的默認值. 如: p := new(int)*p0.

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

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

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

        接著我們改一下*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 一樣不一樣?

        結論: 不一樣

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

        x = y 表示的是把 y 對應的內存空間的內容寫到x內存空間中.

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

        等號右邊的變量代表變量內存空間存儲的數據值, 相當于操作.

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

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

        所以, *p = 1000 的意思是把1000寫到 *p 的內存中去;

        fmt.Println(*p) 是把 *p的內存空間中存儲的數據值打印出來.

        所以這兩者是不一樣的.

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

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

        我們上面已經說過了, 當運行 foo() 時會產生一個棧幀, 運行結束, 釋放棧幀.

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

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

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

        函數的傳參

        明白了上面的內容, 我們再去了解指針作為函數參數就會容易很多.

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

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

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

        所以, 函數傳參永遠都是值傳遞.

        了解了概念之后, 我們來看一個經典的例子:

        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)}

        結果:

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

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

        首先當運行 main() 時, 系統在棧區產生一個棧幀, 該棧幀里有 xy 兩個變量.

        當運行 swap() 時, 系統在棧區產生一個棧幀, 該棧幀里面有 xy 兩個變量.

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

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

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

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

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

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

        注意理解其中的差別.

        繼續看以下這段代碼:

        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)}

        結果:

        main  x:  20 y:  10

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

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

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

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

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

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

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

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

        現在 swap2() 再釋放也沒有關系了, 因為 main() 里的值已經被改了.

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 国产精品国产三级国产潘金莲 | 欧美精品欧美人与动人物牲交| 免费视频精品一区二区三区| 亚洲精品白浆高清久久久久久| 国产精品免费看久久久香蕉| 精品国产福利在线观看| 日韩福利视频精品专区| 成人国产精品日本在线观看| 国产成人精品免费视| 精品亚洲国产成AV人片传媒| 亚洲精品第一国产综合境外资源 | 乱精品一区字幕二区| 完整观看高清秒播国内外精品资源| 国产成人1024精品免费| 国产cosplay精品视频| 久久精品中文无码资源站| 伊人精品视频在线| 久久夜色精品国产www| 黑人巨大精品欧美一区二区| 成人午夜精品网站在线观看| 麻豆亚洲AV永久无码精品久久| 亚洲午夜精品久久久久久app| 精品一区二区三区免费视频| 国产69精品久久久久9999| 天天爽夜夜爽精品视频app| 黑人精品videos亚洲人| 国产精品亚洲片在线va| 国产精品亚洲专区在线观看| 99国产欧美久久久精品蜜芽 | 中文字幕日韩精品有码视频| 中日精品无码一本二本三本| 亚洲色精品aⅴ一区区三区 | 国产精品无码不卡一区二区三区| 国产精品内射久久久久欢欢 | 91精品观看91久久久久久| 老司机69精品成免费视频| 精品国产日产一区二区三区| 国产精品国产三级专区第1集| 国产精品99久久精品| 午夜精品视频在线| 成人免费精品网站在线观看影片|