久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

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

      golang有指針嗎

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

      golang有指針嗎

      本教程操作環(huán)境:windows10系統(tǒng)、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 去訪問.

      繼續(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) }

      可以發(fā)現, x*p 的結果一樣的。

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

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

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

      推薦學習:Golang教程

      棧幀的內存布局

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

      golang有指針嗎

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

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

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

      棧幀: 用來給函數運行提供內存空間, 取內存于 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) 時, 會繼續(xù)產生一個棧幀, 這時 main() 產生的棧幀還沒有結束.

      golang有指針嗎

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

      golang有指針嗎

      空指針與野指針

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

      var p *int

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

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

      var p *int = 0xc00000a0c8

      指針變量的內存存儲

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

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

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

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

      我們只需使用 new() 函數, 無需擔心其內存的生命周期或者怎樣將其刪除, 因為Go語言的內存管理系統(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 一樣不一樣?

      結論: 不一樣

      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()創(chuàng)建會怎樣?

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

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

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

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

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

      函數的傳參

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

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

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

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

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

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

      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() 時, 系統(tǒng)在棧區(qū)產生一個棧幀, 該棧幀里有 xy 兩個變量.

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

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

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

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

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

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

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

      注意理解其中的差別.

      繼續(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)}

      結果:

      main  x:  20 y:  10

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

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

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

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

      運行 swap2() 時, 同樣創(chuàng)建一個棧幀, 里面有 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號