# 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 的位址 ``` ![注:僅為示意圖,並不代表這些變數的記憶體位址一定連續](https://i.imgur.com/gXWlxTB.png) 注:僅為示意圖,並不代表這些變數的記憶體位址一定連續 ## 指針的使用 指針的使用流程: 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 } ```