# Day13 Go怎麼傳不過去-指標的小用處(Pointer) ## 指標 #### 【Pointer指標】 在Go語言中的 `pointer`:指標、指針, 可以指向`var 變數` 的記憶體位置,或指到`struct 物件`本身 基本上用法跟C語言大同小異 `&` 取變數的**位址** `*` 取變數的**數值** 宣告指標的方式 > **var Variable \*Type** >> var a int = 10 >> var b *int = &a https://play.golang.org/p/grTH_rokhvO ```go 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 ```go 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 */ ``` #### 【Swap Function交換數值】 Go是直接對`Value`值做操作的, 除非傳入`func`的參數是`Pointer`指標位址,才會對位址的`Value`值進行操作。 這邊以不用`return`回傳值的方式,來做交換值範例: ```go 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中宣告的值` 實際上若要交換值返回,需要把位址傳入、直接對位址內的值進行操作 ```go // 實際上真正有用的 Swap function func Swap(a *int, b *int) { //fmt.Println(a, b) //0xc00000a108 0xc00000a120 temp := *a *a = *b *b = temp } ``` 這範例雖然簡短, 但建議還是要自己寫過一遍、從頭思考Try一次, 對什麼時候該放`*`、什麼時候該放`&`,比較不會感到那麼困惑。 #### 【Struct物件上的 Pointer】 在物件結構`struct`上也會遇到類似的問題, 這邊以昨天的笨貓肥貓當作範例, 笨貓二號今天難得變聰明了,想要主人讓他改名: https://play.golang.org/p/q_9Hg41ZA1Y ```go 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,不玩了 究竟什麼樣的原因導致傳不過去的問題發生事實擺在眼前呢? 經過一番辛苦努力和輕鬆課金之後, 笨貓二號成了課長... ```go 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`,將程式碼一掃而過都會覺得沒問題。 ```go func (c Cat) rename(newName string) { c.catName = newName } ``` 但是當物件呼叫了方法,輸出卻不是預期的結果,這種情況也不失為一個【小坑】。 為了以防這種狀況,通常在寫物件的方法時 會把`物件的位址`傳入。 有一種說法是在`func`內傳遞`Pointer指針`的效率會比傳入物件來的高, 因為`func`不用`copy`整個物件結構的值。 但實際上的情況詳見[**評論**](https://github.com/golang/go/wiki/CodeReviewComments#receiver-type) --- #### 【New Func】 透過`Struct物件Pointer`,我們可以自製`New Ojb Func`,這個`func`回傳被實體化物件的指標 相當於用`New`產生一個物件。 https://play.golang.org/p/49BcBvE2Cgg ```go 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 */ ``` 看到這,對**什麼時候該用指針**還有些疑惑嗎, 推薦看看這篇[**文件指導**](https://golang.org/doc/faq#Pointers)