# 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)