1. 程式人生 > >【Go語言入門系列】(七)如何使用Go的方法?

【Go語言入門系列】(七)如何使用Go的方法?

[【Go語言入門系列】](https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1441283546689404928)前面的文章: - [【Go語言入門系列】(四)之map的使用](https://mp.weixin.qq.com/s/XZQWTUhFj8nVDZR8p3cg8g) - [【Go語言入門系列】(五)之指標和結構體的使用](https://mp.weixin.qq.com/s/GFagQ1USAMoaMB8IAokIpw) - [【Go語言入門系列】(六)之再探函式](https://mp.weixin.qq.com/s/EgBGvrFmnn_oLanWIAnDgQ) 本文介紹Go語言的方法的使用。 # 1. 宣告 如果你用過面向物件的語言,比如Java,那你肯定對類、物件、成員變數、方法等很熟悉。 > 簡單地來說,類是對一類事物的抽象,成員變數是該事物的屬性,方法是該事物具有的行為,物件則是該事物所對應的具體個體。 > > 比如說,狗(類),名字(屬性),叫(方法),哮天犬(物件)。 但是Go語言中並沒有類,自然也沒有面向物件中的成員變數和成員方法。但是Go語言中有類似的概念——結構體,結構體中的欄位可以看做類中成員屬性。 Go中也有類似於面向物件中方法的概念,也叫方法(`method`),這種方法其實是一種特殊的函式(`function`)——帶有接收者(`receiver`)的函式。 方法的宣告方式如下: ```go func (接受者) funcName(引數們) (返回值們) ``` 可以看出方法的宣告方式和函式的宣告方式差不多,但是多了一個接收者,該接收者是一個結構體型別。下面是一個例項: ```go package main import "fmt" type dog struct { name string } func (d dog) say() {//方法 fmt.Println(d.name + " 汪汪汪。。。方法") } func main() { d := dog{"哮天犬"} d.watchDoor() } ``` 執行: ```go 哮天犬 汪汪汪。。。方法 ``` `say()`是一個方法,`d`是接收者,是一個結構體型別引數,方法裡可以訪問接收者的欄位: ```go fmt.Println(d.name + " 汪汪汪。。。方法") ``` 通過`.`可以呼叫方法: ```go d.say() ``` # 2. 方法和函式 方法`method`是具有接收者`receiver`的特殊函式`function`。下面的例子展示了`method`和`function`之間的區別。 ```go package main import "fmt" type dog struct { name string } func (d dog) say() { fmt.Println(d.name + " 汪汪汪。。。方法") } func say(d dog) { fmt.Println(d.name + " 汪汪汪。。。函式") } func main() { d := dog{"哮天犬"} d.watchDoor() watchDoor(d) } ``` 執行: ```go 哮天犬 汪汪汪。。。方法 哮天犬 汪汪汪。。。函式 ``` 你可能會問,在這個例子中,既然方法和函式的執行結果一樣,那使用方法豈不是多此一舉,為何不繼續使用函式? 換一個場景:現在有狗、貓、兔子等動物,他們都會叫,只是叫聲不同: ```go package main import "fmt" type dog struct { name string } type cat struct { name string } type rabbit struct { name string } func dogSay(d dog) { fmt.Println(d.name + " 汪汪汪。。。函式") } func catSay(c cat) { fmt.Println(c.name + " 喵喵喵。。。函式") } func rabbitSay(r rabbit) { fmt.Println(r.name + " 吱吱吱。。。函式") } func main() { d := dog{"哮天犬"} c := cat{"加菲貓"} r := rabbit{"玉兔"} dogSay(d) catSay(c) rabbitSay(r) } ``` 執行: ```go 哮天犬 汪汪汪。。。函式 加菲貓 喵喵喵。。。函式 玉兔 吱吱吱。。。函式 ``` 上面的三個函式有什麼不妥之處呢? 首先,這三個函式都是用來表示`叫`這一行為,一般來說函式名都會叫`say()`,但因為不同的動物,函式名不能相同,為了做區別而做出了改變。 其次,`叫`這個行為應該屬於動物,二者在概念上不能分開。比如,說話這個行為是每個人都具有的,但是說話並不能離開人而獨自存在。 此時,方法`method`的優點就體現了出來: ```go package main import "fmt" type dog struct { name string } type cat struct { name string } type rabbit struct { name string } func (d dog) say() { fmt.Println(d.name + " 汪汪汪。。。方法") } func (c cat) say() { fmt.Println(c.name + " 喵喵喵。。。方法") } func (r rabbit) say() { fmt.Println(r.name + " 吱吱吱。。。方法") } func main() { d := dog{"哮天犬"} c := cat{"加菲貓"} r := rabbit{"玉兔"} d.say() //呼叫 c.say() r.say() } ``` 執行: ```go 哮天犬 汪汪汪。。。方法 加菲貓 喵喵喵。。。方法 玉兔 吱吱吱。。。方法 ``` 三個方法的方法名都一樣,每個方法都有一個接受者`receiver`,這個`receiver`使方法在概念上屬於結構體,就像結構體的欄位一樣,但是沒有寫在結構體內。 從這三個方法中可以看出:**只要方法的接收者不同,即使方法名相同,方法也不相同**。 # 3. 指標和接收者 接收者可以使用指標,和函式的引數使用指標一樣(參考[Go語言入門系列(六)之再探函式](https://mp.weixin.qq.com/s/EgBGvrFmnn_oLanWIAnDgQ)),接收者使用指標傳的是引用,不使用指標傳的是值拷貝。看下面一個例子: ```go package main import "fmt" type dog struct { name string } func (d *dog) rename(name string) { d.name = name fmt.Println("方法內:" + d.name) } func (d dog) rename1(name string) { d.name = name fmt.Println("方法內:" + d.name) } ``` `rename`和`rename1`都是改變名字的方法,一個傳引用,一個傳值。只有`rename`能真正改變名字。 ```go func main() { d := dog{"哮天犬"} d.rename("小黑黑") fmt.Println(d.name) } ``` 執行: ```go 方法內:小黑黑 小黑黑 ``` `rename`把“哮天犬”改為了“小黑黑”。 ```go func main() { d := dog{"哮天犬"} d.rename1("小紅紅") fmt.Println(d.name) } ``` 執行: ```go 方法內:小紅紅 哮天犬 ``` `rename1`只在方法內改變了名字,並沒有真正改變“哮天犬”。因為`rename1`接收的是`d`的一個拷貝。 **方法的指標接收者可以進行重定向,什麼意思呢?下面用四段程式碼來說明。** **如果函式的引數是一個指標引數,那麼該函式就必須接收一個指標才行,如果是值則報錯**: ```go package main import "fmt" func double(x *int) { *x = *x * 2 } func main() { i := 2 double(&i) //編譯正確 double(i) //報錯 fmt.Println(i) } ``` **而如果方法的接收者是一個指標,那麼該方法被呼叫時,接收者既可以是指標,又可以是值**: ```go package main import "fmt" func (d *dog) rename(name string) { d.name = name fmt.Println("方法內:" + d.name) } func main() { d := dog{"哮天犬"} d.rename("小黑黑") //接收者是值,編譯正確 //(&d).rename("小黑黑") //接收者是指標,編譯正確 fmt.Println(d.name) } ``` 對於指標接收者來說,`d.rename("小黑黑")`被解釋為`(&d).rename("小黑黑")`,如此一來,我們就不需要在意呼叫方法的接收者是否為指標型別,因為Go會進行“重定向”。 同理,反過來也可以。 **如果函式的引數是值,而不是指標,那麼該函式必須接受值,否則會報錯**: ```go package main import "fmt" func double(x int) { x = x * 2 } func main() { i := 2 p := &i double(*p) //引數是值,編譯正確 //double(p) //引數是指標,報錯 fmt.Println(i) } ``` **而如果方法的接收者是一個值,那麼該方法被呼叫時,接收者既可以是值,又可以是指標**: ```go package main import "fmt" func (d dog) rename1(name string) { d.name = name fmt.Println("方法內:" + d.name) } func main() { d := dog{"哮天犬"} p := &d p.rename1("小紅紅") //接收者是指標,編譯正確 //(*p).rename1("小紅紅") //接收者是值,編譯正確 fmt.Println(d.name) } ``` 對於值接收者來說,`p.rename1("小紅紅")`被解釋為`(*p).rename1("小紅紅")`,如此一來,我們就不需要在意呼叫方法的接收者是否為值,因為Go會進行“重定向”。 # [作者簡介](https://mp.weixin.qq.com/s/PF7srGAwzd_w5pU6eOEZow) > 我是[行小觀](https://mp.weixin.qq.com/s/PF7srGAwzd_w5pU6eOEZow),我會在公眾號『[行人觀學](https://mp.weixin.qq.com/s/PF7srGAwzd_w5pU6eOEZow)』中持續更新Java、Go、資料結構和演算法、計算機基礎等相關文章。 > > --- > > 本文章屬於系列文章「[Go語言入門系列](https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1441283546689404928)」,本系列從Go語言基礎開始介紹,適合從零開始的初學者。 > > --- > > 歡迎關注,我們一起踏上行程。 > **如有錯誤,還請指