指針是一個表明着某個內存地址的值, 這個內存地址每每是在內存中存儲的另外一個變量的值的起始位置.git
Go語言對指針的支持介於Java語言和 C/C++ 語言之間, 它既沒有像Java那樣取消了代碼對指針的直接操做的能力, 也避免了 C/C++ 中因爲對指針的濫用而形成的安全和可靠性問題.安全
Go語言保留了指針, 可是與C語言指針有所不一樣. 主要體如今:函數
&
取變量地址, *
經過指針訪問目標對象.->
運算符, 直接用 .
訪問目標成員.先來看一段代碼:佈局
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
的地址.指針
因此, 指針就是地址, 指針變量就是存儲地址的變量.code
接着, 咱們更改 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
, 咱們操做的都是同一個空間.
首先, 先來看一下內存佈局圖, 以 32位
爲例.
其中, 數據區保存的是初始化後的數據.
上面的代碼都存儲在棧區. 通常 make()
或者 new()
出來的都存儲在堆區
接下來, 咱們來了解一個新的概念: 棧幀.
棧幀: 用來給函數運行提供內存空間, 取內存於 stack
上.
當函數調用時, 產生棧幀; 函數調用結束, 釋放棧幀.
那麼棧幀用來存放什麼?
其中, 形參與局部變量存儲地位等同
當咱們的程序運行時, 首先運行 main()
, 這時就產生了一個棧幀.
當運行到 var x int = 99
時, 就會在棧幀裏面產生一個空間.
同理, 運行到 var p *int = &x
時也會在棧幀裏產生一個空間.
以下圖所示:
咱們增長一個函數, 再來研究一下.
package main import "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()
產生的棧幀尚未結束.
當 test()
運行完畢時, 就會釋放掉這個棧幀.
空指針: 未被初始化的指針.
var p *int
這時若是咱們想要對其取值操做 *p
, 會報錯.
野指針: 被一片無效的地址空間初始化.
var p *int = 0xc00000a0c8
表達式 new(T)
將建立一個 T
類型的匿名變量, 所作的是爲 T
類型的新值分配並清零一塊內存空間, 而後將這塊內存空間的地址做爲結果返回, 而這個結果就是指向這個新的 T
類型值的指針值, 返回的指針類型爲 *T
.
new()
建立的內存空間位於heap上, 空間的默認值爲數據類型的默認值. 如: p := new(int)
則 *p
爲 0
.
package main import "fmt" func main(){ p := new(int) fmt.Println(p) fmt.Println(*p) }
這時 p
就再也不是空指針或者野指針.
咱們只需使用 new()
函數, 無需擔憂其內存的生命週期或者怎樣將其刪除, 由於Go語言的內存管理系統會幫咱們打理一切.
接着咱們改一下*p
的值:
package main import "fmt" func main(){ p := new(int) *p = 1000 fmt.Println(p) fmt.Println(*p) }
這個時候注意了, *p = 1000
中的 *p
與 fmt.Println(*p)
中的 *p
是同樣的嗎?
你們先思考一下, 而後先來看一個簡單的例子:
var x int = 10 var y int = 20 x = y
好, 你們思考一下上面代碼中, var y int = 20
中的 y
與 x = y
中的 y
同樣不同?
結論: 不同
var y int = 20
中的 y
表明的是內存空間, 咱們通常把這樣的稱之爲左值; 而 x = y
中的 y
表明的是內存空間中的內容, 咱們通常稱之爲右值.
x = y
表示的是把 y
對應的內存空間的內容寫到x內存空間中.
等號左邊的變量表明變量所指向的內存空間, 至關於寫操做.
等號右邊的變量表明變量內存空間存儲的數據值, 至關於讀操做.
在瞭解了這個以後, 咱們再來看一下以前的代碼.
p := new(int) *p = 1000 fmt.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 main import "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: 10 main x: 10 y: 20
咱們先來簡單分析一下爲何不同.
首先當運行 main()
時, 系統在棧區產生一個棧幀, 該棧幀裏有 x
和 y
兩個變量.
當運行 swap()
時, 系統在棧區產生一個棧幀, 該棧幀裏面有 x
和 y
兩個變量.
運行 x, y = y, x
後, 交換 swap()
產生的棧幀裏的 xy
值. 這時 main()
裏的 xy
沒有變.
swap()
運行完畢後, 對應的棧幀釋放, 棧幀裏的x
y
值也隨之消失.
因此, 當運行 fmt.Println("main x: ", x, "y: ", y)
這句話時, 其值依然沒有變.
接下來咱們看一下參數爲地址值時的狀況.
傳地址的核心思想是: 在本身的棧幀空間中修改其它棧幀空間中的值.
而傳值的思想是: 在本身的棧幀空間中修改本身棧幀空間中的值.
注意理解其中的差異.
繼續看如下這段代碼:
package main import "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
這裏並無違反 函數傳參永遠都是值傳遞
這句話, 只不過這個時候這個值爲地址值.
這個時候, x
與 y
的值就完成了交換.
咱們來分析一下這個過程.
首先運行 main()
後建立一個棧幀, 裏面有 x
y
兩個變量.
運行 swap2()
時, 一樣建立一個棧幀, 裏面有 a
b
兩個變量.
注意這個時候, a
和 b
中存儲的值是 x
和 y
的地址.
當運行到 *a, *b = *b, *a
時, 左邊的 *a
表明的是 x
的內存地址, 右邊的 *b
表明的是 y
的內存地址中的內容. 因此這個時候, main()
中的 x
就被替換掉了.
因此, 這是在 swap2()
中操做 main()
裏的變量值.
如今 swap2()
再釋放也沒有關係了, 由於 main()
裏的值已經被改了.
歡迎訪問個人我的網站:
李培冠博客:lpgit.com