Go語言的指針與C或C++的指針類似灰蛙,但是Go語言的指針不支持指針運(yùn)算祟剔,這樣就消除了在C或C++程序中一些潛在的問題。由于Go語言有自己的垃圾回收器缕允,并且會(huì)自動(dòng)管理內(nèi)存峡扩,所以Go語言也不需要像C或C++一樣使用free函數(shù)或者delete操作符。
Go語言的指針創(chuàng)建后可以像Java和Python中對象的引用一樣使用障本。
在Go語言中教届,對于布爾變量或數(shù)值類型或字符串類型或數(shù)組都是按照值傳遞的:值在傳遞給函數(shù)或者方法時(shí)會(huì)被復(fù)制一份,然后方法或函數(shù)使用的是復(fù)制的這份值驾霜,也就不會(huì)對原值產(chǎn)生什么影響案训。一般情況下,對于布爾變量或數(shù)值類型或字符串類型的按值傳遞是非常廉價(jià)的粪糙,Go語言編譯器會(huì)在傳遞過程中進(jìn)行安全優(yōu)化强霎。
但是在Go語言中,字符串是不可變的蓉冈,因此在進(jìn)行修改字符串時(shí)(例如使用+=操作)城舞,Go語言必須創(chuàng)建一個(gè)新的字符串,然后復(fù)制原始的字符串并將其添加到新字符串之后寞酿,對于大字符串來說家夺,操作的代價(jià)可能會(huì)比較大。
對于大字符串是這樣伐弹,對于數(shù)組進(jìn)行值傳遞也是如此拉馋。為了解決可能產(chǎn)生的巨大代價(jià),Go語言使用數(shù)組切片來代替數(shù)組的使用。傳遞一個(gè)切片的代價(jià)跟傳遞字符串差不多煌茴,無論該切片的長度或容量是多大随闺。對切片進(jìn)行復(fù)制修改操作也不會(huì)像字符串那樣需要?jiǎng)?chuàng)建新的切片,因?yàn)榍衅强勺兊穆瑢儆谝妙愋汀?/p>
func main() {
a := 3
b := 4
c := "abc"
d := [3]int{1,2,3}
fmt.Printf("main方法:a的值為 %v,b的值為 %v,c的值為 %v,d的值為 %v \n",a,b,c,d)
demo(a,b,c,d)
fmt.Printf("main方法:a的值為 %v,b的值為 %v,c的值為 %v,d的值為 %v \n",a,b,c,d)
}
func demo(a,b int,c string,d [3]int) {
a = 5
b = 6
c = "efg"
d[0] = 0
fmt.Printf("demo函數(shù):a的值為 %v,b的值為 %v,c的值為 %v,d的值為 %v\n",a,b,c,d)
}
----output----
main方法: a的值為 3,b的值為 4,c的值為 abc,d的值為 [1 2 3]
demo函數(shù): a的值為 5,b的值為 6,c的值為 efg,d的值為 [0 2 3]
main方法: a的值為 3,b的值為 4,c的值為 abc,d的值為 [1 2 3]
Go語言中的引用類型有:映射(map)矩乐,數(shù)組切片(slice),通道(channel)合住,方法與函數(shù)绰精。
由于Go語言存在垃圾回收器,因此在一個(gè)本地變量不再被使用時(shí)(不再被引用或者不在作用于范圍)就會(huì)被垃圾回收器回收掉透葛,這時(shí)本地變量的生命周期由它們的作用域決定。那如果我們想要管理本地變量的生命周期呢卿樱?這時(shí)就需要使用指針來管理本地變量僚害,只要該變量至少存在一個(gè)指針,那么該變量的生命周期就可以獨(dú)立于作用域繁调。
使用指針能讓我們控制變量的生命周期萨蚕,不受作用域的影響,另外變量在傳遞過程中成本最小化蹄胰,且可以輕易的修改變量的內(nèi)容岳遥,而不是對復(fù)制的值進(jìn)行操作。指針是一個(gè)變量裕寨,這個(gè)變量實(shí)際上是保存了另一個(gè)變量的內(nèi)存地址浩蓉,任何被指針保存了內(nèi)存地址的變量都可以通過指針來修改內(nèi)容。指針的傳遞非常廉價(jià)宾袜。
在使用指針前捻艳,我們需要明白兩個(gè)操作符的含義
①操作符& : 當(dāng)作二元操作符時(shí),是按位與操作庆猫;當(dāng)作一元操作符時(shí)认轨,是返回該變量的內(nèi)存地址。
②操作符* : 當(dāng)作二元操作符時(shí)月培,是相乘的操作嘁字;當(dāng)作一元操作符(解引用操作符)時(shí),是返回該指針指向的變量的值杉畜,其實(shí)就是解除變量的指針引用纪蜒,返回該變量的值。
指針的創(chuàng)建與使用寻行,可以看下面的代碼實(shí)例
func main() {
a := 3
p := &a //這里是獲取變量a的內(nèi)存地址霍掺,并將其賦值給變量p
fmt.Printf("a的值為 %v, a的指針是 %v ,p指向的變量的值為 %v\n",a,p,*p)
}
-----output-----
a的值為 3, a的指針是 0xc042060080 ,p指向的變量的值為 3
其實(shí)*p和變量a的值是相等的杆烁,兩者可以交換著使用牙丽,兩者都與同一塊內(nèi)存地址相關(guān)聯(lián),任意一個(gè)變量進(jìn)行修改操作都會(huì)影響到另一個(gè)變量的值兔魂,但是若變量p被賦值其他變量的指針就不行了烤芦。
關(guān)于指針的綜合運(yùn)用,我們看以下的代碼實(shí)例
func main() {
a := 3
b := 4
p1 := &a //獲取變量a的內(nèi)存地址析校,并將其賦值給變量p1
p2 := &b //獲取變量b的內(nèi)存地址构罗,并將其賦值給變量p2
fmt.Printf("a的值為 %v, a的指針是 %v ,p1指向的變量的值為 %v\n",a,p1,*p1)
fmt.Printf("b的值為 %v, b的指針是 %v 智玻,p2指向的變量的值為 %v\n",b,p2,*p2)
fmt.Println(demo(p1,p2))
fmt.Printf("a的值為 %v, a的指針是 %v 遂唧,p1指向的變量的值為 %v\n",a,p1,*p1)
fmt.Printf("b的值為 %v, b的指針是 %v ,p2指向的變量的值為 %v\n",b,p2,*p2)
}
func demo(a,b *int)int {
*a = 5
*b = 6
return *a * *b //這里出現(xiàn)連續(xù)的兩個(gè)*吊奢,Go編譯器會(huì)根據(jù)上下文自動(dòng)識別乘法與兩個(gè)引用
}
-----output-----
a的值為 3, a的指針是 0xc042060080 盖彭,p1指向的變量的值為 3
b的值為 4, b的指針是 0xc042060088 ,p2指向的變量的值為 4
30
a的值為 5, a的指針是 0xc042060080 页滚,p1指向的變量的值為 5
b的值為 6, b的指針是 0xc042060088 召边,p2指向的變量的值為 6
根據(jù)上面代碼,我們可以看到使用指針后裹驰,本來是值傳遞的整數(shù)類型在函數(shù)或方法中修改會(huì)影響到原變量的值隧熙。
空指針
當(dāng)一個(gè)指針被定義后沒有分配到任何變量時(shí),它的值為 nil幻林。
nil 指針也稱為空指針贞盯。
nil在概念上和其它語言的null、None滋将、nil邻悬、NULL一樣,都指代零值或空值随闽。
查看以下實(shí)例:
package main
import "fmt"
func main() {
var ptr *int
fmt.Printf("ptr 的值為 : %x\n", ptr )
}
以上實(shí)例輸出結(jié)果為:
ptr 的值為 : 0
空指針判斷:
if(ptr != nil) /* ptr 不是空指針 */
if(ptr == nil) /* ptr 是空指針 */
多重間接引用
以上面代碼為例父丰, 在a := 3, p1 := &a中掘宪,p1是指向a的內(nèi)存地址蛾扇,這種叫做間接引用,若還有一個(gè)p2是指向p1的內(nèi)存地址魏滚,p1指向a的內(nèi)存地址镀首,這種就叫做多重間接引用,不管哪種引用鼠次,若其中一個(gè)變量進(jìn)行修改內(nèi)容操作更哄,均會(huì)影響到其他所有變量的內(nèi)容芋齿。
func main() {
a := 3
p1 := &a //p1是指向變量a內(nèi)存地址的指針
p2 := &p1 //p2是指向變量p1內(nèi)存地址的指針
fmt.Printf("a:%v, p1:%v, *p1:%v, p2:%v, **p2:%v\n",a,p1,*p1,p2,**p2)
a = 4
fmt.Printf("a:%v, p1:%v, *p1:%v, p2:%v, **p2:%v\n",a,p1,*p1,p2,**p2)
}
-----output-----
a:3, p1:0xc0420080b8, *p1:3, p2:0xc042004028, **p2:3
a:4, p1:0xc0420080b8, *p1:4, p2:0xc042004028, **p2:4
new函數(shù)與&操作符
Go語言中提供兩種創(chuàng)建變量的方式,同時(shí)可以獲得指向它們的指針:new函數(shù)與&操作符成翩。這兩種的使用方式如下面代碼所示
type Person struct {
name string
sex string
age int
}
func main() {
person1 := Person{"zhangsan","man",25} //創(chuàng)建一個(gè)person1對象
person2 := new(Person)//使用new創(chuàng)建一個(gè)person2對象觅捆,同時(shí)獲得person的指針
person2.name,person2.sex,person2.age = "wangwu","man",25
person3 := &Person{"lisi","man",25}//使用&創(chuàng)建一個(gè)person3對象,同時(shí)獲得person的指針
fmt.Printf("person1:%v, person2:%v, person3:%v\n",person1,person2,person3)
}
-----output-----
person1:{zhangsan man 25}, person2:&{wangwu man 25}, person3:&{lisi man 25}
從輸出結(jié)果來看麻敌,new函數(shù)與&操作符兩種方式區(qū)別不大栅炒,&操作符創(chuàng)建起來更加的簡潔,并且隨時(shí)可以指定屬性初始值术羔。Go語言打印指向person的指針時(shí)赢赊,會(huì)打印person屬性的具體內(nèi)容,并且在前綴上加上&表示該變量是一個(gè)指針级历。
接下來我們來傳遞一個(gè)結(jié)構(gòu)體指針释移,并修改結(jié)構(gòu)體的屬性,看看Go語言是如何操作的寥殖。
type Person struct {
name string
sex string
age int
}
func main() {
person1 := Person{"zhangsan","man",25} //創(chuàng)建一個(gè)person1對象
fmt.Printf("person1:%v\n",person1)
demo(&person1)
fmt.Printf("person1:%v\n",person1)
}
func demo(person *Person) {
(*person).age = 18 //顯示的解引用
person.name = "GoLang" //隱式的解引用
}
main函數(shù)中創(chuàng)建一個(gè)pserson1對象秀鞭,設(shè)置初始化屬性后將它的指針傳遞到demo函數(shù)中,大家可以看到demo函數(shù)中有兩種解引用(就是將一個(gè)指針轉(zhuǎn)化為原對象)的方式扛禽,第一種: (*person).age是顯示的解引用,第二種是使用 "." 操作符自動(dòng)的將指針解引用皱坛,使用上這兩種沒什么區(qū)別编曼,但第二種更簡單。