原文:https://www.jianshu.com/p/af42cb368cef
----------------------------------------------------
Go語言的指針與C或C++的指針類似,但是Go語言的指針不支持指針運算,這樣就消除了在C或C++程序中一些潛在的問題。由於Go語言有自己的垃圾回收器,並且會自動管理內存,所以Go語言也不需要像C或C++一樣使用free函數或者delete操作符。
Go語言的指針創建后可以像Java和Python中對象的引用一樣使用。
在Go語言中,對於布爾變量或數值類型或字符串類型或數組都是按照值傳遞的:值在傳遞給函數或者方法時會被復制一份,然后方法或函數使用的是復制的這份值,也就不會對原值產生什么影響。一般情況下,對於布爾變量或數值類型或字符串類型的按值傳遞是非常廉價的,Go語言編譯器會在傳遞過程中進行安全優化。
但是在Go語言中,字符串是不可變的,因此在進行修改字符串時(例如使用+=操作),Go語言必須創建一個新的字符串,然后復制原始的字符串並將其添加到新字符串之后,對於大字符串來說,操作的代價可能會比較大。
對於大字符串是這樣,對於數組進行值傳遞也是如此。為了解決可能產生的巨大代價,Go語言使用數組切片來代替數組的使用。傳遞一個切片的代價跟傳遞字符串差不多,無論該切片的長度或容量是多大。對切片進行復制修改操作也不會像字符串那樣需要創建新的切片,因為切片是可變的,屬於引用類型。
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函數: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函數: a的值為 5,b的值為 6,c的值為 efg,d的值為 [0 2 3] main方法: a的值為 3,b的值為 4,c的值為 abc,d的值為 [1 2 3]
Go語言中的引用類型有:映射(map),數組切片(slice),通道(channel),方法與函數。
由於Go語言存在垃圾回收器,因此在一個本地變量不再被使用時(不再被引用或者不在作用於范圍)就會被垃圾回收器回收掉,這時本地變量的生命周期由它們的作用域決定。那如果我們想要管理本地變量的生命周期呢?這時就需要使用指針來管理本地變量,只要該變量至少存在一個指針,那么該變量的生命周期就可以獨立於作用域。
使用指針能讓我們控制變量的生命周期,不受作用域的影響,另外變量在傳遞過程中成本最小化,且可以輕易的修改變量的內容,而不是對復制的值進行操作。指針是一個變量,這個變量實際上是保存了另一個變量的內存地址,任何被指針保存了內存地址的變量都可以通過指針來修改內容。指針的傳遞非常廉價。
在使用指針前,我們需要明白兩個操作符的含義
①操作符& : 當作二元操作符時,是按位與操作;當作一元操作符時,是返回該變量的內存地址。
②操作符* : 當作二元操作符時,是相乘的操作;當作一元操作符(解引用操作符)時,是返回該指針指向的變量的值,其實就是解除變量的指針引用,返回該變量的值。
指針的創建與使用,可以看下面的代碼實例
func main() { a := 3 p := &a //這里是獲取變量a的內存地址,並將其賦值給變量p fmt.Printf("a的值為 %v, a的指針是 %v ,p指向的變量的值為 %v\n",a,p,*p) } -----output----- a的值為 3, a的指針是 0xc042060080 ,p指向的變量的值為 3
其實*p和變量a的值是相等的,兩者可以交換着使用,兩者都與同一塊內存地址相關聯,任意一個變量進行修改操作都會影響到另一個變量的值,但是若變量p被賦值其他變量的指針就不行了。
關於指針的綜合運用,我們看以下的代碼實例
func main() { a := 3 b := 4 p1 := &a //獲取變量a的內存地址,並將其賦值給變量p1 p2 := &b //獲取變量b的內存地址,並將其賦值給變量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 //這里出現連續的兩個*,Go編譯器會根據上下文自動識別乘法與兩個引用 } -----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
根據上面代碼,我們可以看到使用指針后,本來是值傳遞的整數類型在函數或方法中修改會影響到原變量的值。
空指針
當一個指針被定義后沒有分配到任何變量時,它的值為 nil。
nil 指針也稱為空指針。
nil在概念上和其它語言的null、None、nil、NULL一樣,都指代零值或空值。
查看以下實例:
package main import "fmt" func main() { var ptr *int fmt.Printf("ptr 的值為 : %x\n", ptr ) }
以上實例輸出結果為:
ptr 的值為 : 0
空指針判斷:
if(ptr != nil) /* ptr 不是空指針 */ if(ptr == nil) /* ptr 是空指針 */
多重間接引用
以上面代碼為例, 在a := 3, p1 := &a中,p1是指向a的內存地址,這種叫做間接引用,若還有一個p2是指向p1的內存地址,p1指向a的內存地址,這種就叫做多重間接引用,不管哪種引用,若其中一個變量進行修改內容操作,均會影響到其他所有變量的內容。
func main() { a := 3 p1 := &a //p1是指向變量a內存地址的指針 p2 := &p1 //p2是指向變量p1內存地址的指針 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函數與&操作符
Go語言中提供兩種創建變量的方式,同時可以獲得指向它們的指針:new函數與&操作符。這兩種的使用方式如下面代碼所示
type Person struct { name string sex string age int } func main() { person1 := Person{"zhangsan","man",25} //創建一個person1對象 person2 := new(Person)//使用new創建一個person2對象,同時獲得person的指針 person2.name,person2.sex,person2.age = "wangwu","man",25 person3 := &Person{"lisi","man",25}//使用&創建一個person3對象,同時獲得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}
從輸出結果來看,new函數與&操作符兩種方式區別不大,&操作符創建起來更加的簡潔,並且隨時可以指定屬性初始值。Go語言打印指向person的指針時,會打印person屬性的具體內容,並且在前綴上加上&表示該變量是一個指針。
接下來我們來傳遞一個結構體指針,並修改結構體的屬性,看看Go語言是如何操作的。
type Person struct { name string sex string age int } func main() { person1 := Person{"zhangsan","man",25} //創建一個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函數中創建一個pserson1對象,設置初始化屬性后將它的指針傳遞到demo函數中,大家可以看到demo函數中有兩種解引用(就是將一個指針轉化為原對象)的方式,第一種: (*person).age是顯示的解引用,第二種是使用 "." 操作符自動的將指針解引用,使用上這兩種沒什么區別,但第二種更簡單。
作者:小傑的快樂時光
鏈接:https://www.jianshu.com/p/af42cb368cef
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。