溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

golang中是否有指針

發布時間:2021-03-18 17:05:41 來源:億速云 閱讀:194 作者:Leah 欄目:編程語言

這篇文章將為大家詳細講解有關golang中是否有指針,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

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

指針地址和變量空間

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 , 我們操作的都是同一個空間。

棧幀的內存布局

首先, 先來看一下內存布局圖, 以 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() 里的值已經被改了.

關于golang中是否有指針就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女