在Go語言中的 pointer
:指標、指針,
可以指向var 變數
的記憶體位置,或指到struct 物件
本身
基本上用法跟C語言大同小異
&
取變數的位址
*
取變數的數值
宣告指標的方式
var Variable *Type
var a int = 10
var b *int = &a
https://play.golang.org/p/grTH_rokhvO
func main() {
var a int = 10
var b *int
b = &a
fmt.Println(a, b)
var c string = "hi"
var d *string
d = &c
*d = "who" // 透過`*向址取值`的方式來改變變數裡面的內容值。
fmt.Println(c, d)
}
/* result:
10 0xc00000a108
who 0xc0000401f0
*/
變數若為int型態
就需要用int指標
來指
反之亦然
以下是一連串的取值、取址練習
https://play.golang.org/p/LnxluzPOPPL
func main() {
x := 1
p := &x //p(type: *int)指向x
fmt.Println(x) //1
fmt.Println(p) //p指向的位址
fmt.Println(*p) //p指向的位址的值,意即變數x
fmt.Println(&p) //p本身的位址
y := &p //y存放了 p本身的位址
fmt.Println(**y) //到y取值(到p本身的位址取值) 再取值,意即變數x
**y = 100
fmt.Println(x)
}
/* result:
1
0xc00011e090
1
0xc000148018
1
100
*/
Go是直接對Value
值做操作的,
除非傳入func
的參數是Pointer
指標位址,才會對位址的Value
值進行操作。
這邊以不用return
回傳值的方式,來做交換值範例:
func main() {
a, b := 10, 20
swap(a, b)
fmt.Println(a, b)
}
// 我以為有用的 swap function
func swap(a int, b int) {
temp := a
a = b
b = temp
}
/*
10 20
*/
swap func
可沒有偷懶,確實有把交換的動作做到定位(可以印出來看),
但這邊交換值不成,是因為交換到了func中宣告的值
實際上若要交換值返回,需要把位址傳入、直接對位址內的值進行操作
// 實際上真正有用的 Swap function
func Swap(a *int, b *int) {
//fmt.Println(a, b) //0xc00000a108 0xc00000a120
temp := *a
*a = *b
*b = temp
}
這範例雖然簡短,
但建議還是要自己寫過一遍、從頭思考Try一次,
對什麼時候該放*
、什麼時候該放&
,比較不會感到那麼困惑。
在物件結構struct
上也會遇到類似的問題,
這邊以昨天的笨貓肥貓當作範例,
笨貓二號今天難得變聰明了,想要主人讓他改名:
https://play.golang.org/p/q_9Hg41ZA1Y
type Cat struct {
catName string
}
func (c Cat) eat() {
fmt.Println("貓貓", c.catName, "開動哩")
}
func (c Cat) rename(newName string) {
c.catName = newName
}
func main() {
var cat1 = Cat{"肥貓一號"}
cat1.eat()
var cat2 = Cat{"笨貓二號"}
cat2.rename("聰明貓三號") // 奇怪,怎麼改名失敗了
cat2.eat()
}
/* result:
貓貓 肥貓一號 開動哩
貓貓 笨貓二號 開動哩
*/
笨貓二號心想:
疑?奇怪,怎麼改名失敗了
難道是因為我沒有克金?糞game,不玩了
究竟什麼樣的原因導致傳不過去的問題發生事實擺在眼前呢?
經過一番辛苦努力和輕鬆課金之後,
笨貓二號成了課長...
func (c *Cat) rename(newName string) {
c.catName = newName
}
func main() {
var cat1 = &Cat{"肥貓一號"}
cat1.eat()
var cat2 = &Cat{"笨貓二號"}
cat2.rename("課長貓")
cat2.eat()
}
/* result:
貓貓 肥貓一號 開動哩
貓貓 課長貓 開動哩
*/
https://play.golang.org/p/_sP-s9q9knj
這邊改名成功是因為傳了物件的位址進去,讓func rename
對該物件改位址上的值,
如果不是傳位址的話,func rename
會將值copy一份到自己的範圍底下開心的改值,
改完但還沒沒有傳出來就消失了。
當我們看到cat2.rename("聰明貓三號")
的呼叫函式並傳入引數、
再往回看func rename
,將程式碼一掃而過都會覺得沒問題。
func (c Cat) rename(newName string) {
c.catName = newName
}
但是當物件呼叫了方法,輸出卻不是預期的結果,這種情況也不失為一個【小坑】。
為了以防這種狀況,通常在寫物件的方法時 會把物件的位址
傳入。
有一種說法是在func
內傳遞Pointer指針
的效率會比傳入物件來的高,
因為func
不用copy
整個物件結構的值。
但實際上的情況詳見評論
透過Struct物件Pointer
,我們可以自製New Ojb Func
,這個func
回傳被實體化物件的指標
相當於用New
產生一個物件。
https://play.golang.org/p/49BcBvE2Cgg
type cat struct {
name string
}
func main() {
var c = &cat{name: "始祖貓"}
fmt.Println(c, &c)
n1 := newCat("")
n2 := newCat("複製貓三號")
fmt.Println(n1, &n1)
fmt.Println(n2, &n2)
var c2 = new(cat) // 內建的new方法
fmt.Println(c2, &c2)
}
func newCat(n string) *cat {
return &cat{name: n}
}
/* result:
&{始祖貓} 0xc0000ca018
&{} 0xc0000ca028
&{複製貓三號} 0xc0000ca030
&{} 0xc0000ca038
*/
看到這,對什麼時候該用指針還有些疑惑嗎,
推薦看看這篇文件指導