首頁  >  文章  >  後端開發  >  golang有指針嗎

golang有指針嗎

青灯夜游
青灯夜游原創
2021-03-18 16:24:283328瀏覽

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

堆疊幀的記憶體佈局

首先, 先來看記憶體佈局圖, 以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教程

以上是golang有指針嗎的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn