# pointer、struct
Go語言並沒有像C或者JAVA的Class類,它只有結構體,利用結構體和指針等特性完成一個類的功能。
## 指針
每一個變數都會被分配到一塊記憶體空間,而這個空間會有一個地址稱之為記憶體位址,這個位址就如同家裡的門牌,通過門牌才能找到你家,我們也需要通過記憶體位址來找到這塊空間內所儲存的數據。
而指針就是用來保存記憶體位址的變數。
也就是說指針本身是一個變數,其值為另一個變數的位址。
下圖為這段程式碼在記憶體中保存的數據示意圖
```go=
var a = 2
var b = 5
var pa *int = &a // pa指針儲存 a 的位址
var pb *int = &b // pb指針儲存 b 的位址
```

注:僅為示意圖,並不代表這些變數的記憶體位址一定連續
## 指針的使用
指針的使用流程:
1.定義指針變數
2.為指針變數賦值
3.訪問指針變數中指向位址的值
### 定義指針
一般的指針變數聲明方式為:
```go=
var pa *type //pa是指向整數的指針型別
```
這個type可以是int, string, float32…等。
#### 為指針變數賦值
在Go語言中,我們可以在變數前面加上 `&` 來取得該變數的記憶體位址
```go=
var a = 3
var b *int = &a
fmt.Println(a,b) //3 0xc0000140c0
```
b所打印出來的內容為a的記憶體位址,可以稱b為a的指針變數。
:::warning
當一個指針被定義後沒有分配到任何變數時,其值為`nil`。
`nil`指針也被稱為空指針,其實就等同 `null`, `None`,都代表零值或空值。
:::
#### 訪問指針變數中指向位址的值
除了獲得變數的記憶體位址之外,我們還可以使用 `*` 取得指針變數指向的記憶體位址的值
一樣用剛才的例子
```go=
var a = 3
var b *int = &a
fmt.Println(*b) //取得b指向的a變數的記憶體位址的值 , 3
```
## 指針應用
請觀察下方的程式碼,我們應該如何做到修改 p 的值進而改變 a 的值
```go=
func changeValue(p int){
p = 10
}
func main(){
var a int = 1
changeValue(a)
fmt.Println("a = ", a) //1
}
```
沒錯,就是指針。
我們可以利用指針,將程式碼改為這樣:
```go=
func changeValue(p *int){ //p是指向整數的指針類型
*p = 10 //改變當前p所指向空間的值
}
func main(){
var a int = 1
changeValue(&a) //將 a的位址傳給 p 作為值 , 現在p裡的值存的是a的位址
fmt.Println("a = ", a)
}
```
我們使用 `changeValue(&a)` 將 a 的位址傳給changeValue函式,而 p 指針變數中存的就是 a 的記憶體位址,當我修改 `*p` 的值即改變當前 p 所指向位址的值,也就是將 a 改為 10。
### 交換兩個變數的值
a = 2 , b = 5 交換 a,b的值
```go=
package main
import "fmt"
func swap(pa *int, pb *int) {
var temp int
temp = *pa // temp = a位址的值 = 2
*pa = *pb // *pa = b的位址的值 = 5
*pb = temp // *pb = temp的值 = 2
}
func main() {
a := 2
b := 5
swap(&a, &b)
fmt.Println("a = ", a, " b = ", b) // 5 2
}
```
## 結構體
在不同程式語言有不同的OOP實現方式,例如JAVA支持extends也支持interface,但是Go並沒有像C或者JAVA的Class類,它只有結構體,用結構體和指針等特性完成一個類的功能。其實class的底層實現就是結構體,物件的引用就是指針,Go語言只是少了class的語法糖而已。
在Go中我們可以定義一個新的型別,該型別由一系列屬性(欄位)組成,每個屬性都有自己的型別和值,這樣的型別我們就稱之為**結構體(struct)**。
### 定義新的型別
在說如何定義結構體之前我們要先說一個概念,在Go中可以使用關鍵字 `type` 來定義一個新的型別:
```go=
type myint int
func main(){
var a myint = 10
fmt.Println("a = ",a) //a = 10
fmt.Printf("type of a = %T\n",a) //type of a = main.myint
}
```
有了這個概念之後,我們可以通過 type 定義一個結構體。
### 定義結構體
使用關鍵字 `type` 和 `struct` 來定義結構體,把多種資料型態組合在一起變成複雜的型態:
```go=
type Book struct {
title string // string 型別的屬性 title
auth string // string 型別的屬性 auth
}
```
像上述例子的 title 和 auth 都是該結構體中的屬性,這些屬性可以是任何資料型態,包括常見的string,int..等,甚至結構體本身,也可以是函式或者介面。
## 使用結構體
定義一個為 `Book` 型別的變數,可以透過下面這種方式來給屬性賦值:
```go=
var book1 Book
book1.title = "Golang學習筆記"
book1.auth = "pluto"
fmt.Printf("%v\n",book1) //{Golang學習筆記 pluto}
```
也可以使用這種方式給屬性賦值:
```go=
book1 := Book{"Golang學習筆記", "pluto"}
book1 := Book{title:"Golang學習筆記", auth:"pluto"} //此種方式屬性可以任意調整順序
```
還可以使用 new()函式分配一個指標,此時 book1 為 `*Book` 型別
```go=
book1 := new(Book)
fmt.Printf("%T\n",book1) // *main.Book
```
結合之前學的指針,結構體的 **屬性** 一樣是可以通過 **指針** 去修改:
```go=
type Book struct {
title string
auth string
}
func changAuthor(book *Book){
book1.auth = "abc"
}
func main(){
var book1 Book
book1.title = "Golang學習筆記"
book1.auth = "pluto"
fmt.Printf("%v\n",book1) //{Golang學習筆記 pluto}
changeAuthor(&book1)
fmt.Printf("%v\n",book1) //{Golang學習筆記 abc}
}
```
### 匿名欄位
只寫型別不寫欄位名的方式就稱為匿名欄位,也稱為嵌入欄位。
當匿名欄位是結構體的時候,這個結構體所擁有的全部欄位都會被引入當前定義的這個結構體,和物件導向中的繼承概念有點相似,例如下面這個例子:
```go=
type Human struct {
name string
sex string
}
type SuperMan struct {
Human //匿名欄位,代表 superMan 引入了Human 的所有欄位
level int
}
func main(){
s := SuperMan{ Human{"小明","男"}, 100}
fmt.Println("name = ", s.name) // 小明
fmt.Println("sex = ", s.sex) // 男
fmt.Println("level = ", s.level) // 100
}
```