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

【Go語言入門系列】前面的文章:算法

本文介紹Go語言的方法的使用。數據結構

1. 聲明

若是你用過面向對象的語言,好比Java,那你確定對類、對象、成員變量、方法等很熟悉。app

簡單地來講,類是對一類事物的抽象,成員變量是該事物的屬性,方法是該事物具備的行爲,對象則是該事物所對應的具體個體。數據結構和算法

好比說,狗(類),名字(屬性),叫(方法),哮天犬(對象)。函數

可是Go語言中並無類,天然也沒有面向對象中的成員變量和成員方法。可是Go語言中有相似的概念——結構體,結構體中的字段能夠看作類中成員屬性。指針

Go中也有相似於面向對象中方法的概念,也叫方法(method),這種方法實際上是一種特殊的函數(function)——帶有接收者(receiver)的函數。code

方法的聲明方式以下:對象

func (接受者) funcName(參數們) (返回值們)

能夠看出方法的聲明方式和函數的聲明方式差很少,可是多了一個接收者,該接收者是一個結構體類型。下面是一個實例:get

package main

import "fmt"

type dog struct {
	name string
}

func (d dog) say() {//方法
	fmt.Println(d.name + " 汪汪汪。。。方法")
}

func main() {
	d := dog{"哮天犬"}
	d.watchDoor()
}

運行:string

哮天犬 汪汪汪。。。方法

say()是一個方法,d是接收者,是一個結構體類型參數,方法裏能夠訪問接收者的字段:

fmt.Println(d.name + " 汪汪汪。。。方法")

經過.能夠調用方法:

d.say()

2. 方法和函數

方法method是具備接收者receiver的特殊函數function。下面的例子展現了methodfunction之間的區別。

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

運行:

哮天犬 汪汪汪。。。方法
哮天犬 汪汪汪。。。函數

你可能會問,在這個例子中,既然方法和函數的運行結果同樣,那使用方法豈不是畫蛇添足,爲什麼不繼續使用函數?

換一個場景:如今有狗、貓、兔子等動物,他們都會叫,只是叫聲不一樣:

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

運行:

哮天犬 汪汪汪。。。函數
加菲貓 喵喵喵。。。函數
玉兔 吱吱吱。。。函數

上面的三個函數有什麼不妥之處呢?

首先,這三個函數都是用來表示這一行爲,通常來講函數名都會叫say(),但由於不一樣的動物,函數名不能相同,爲了作區別而作出了改變。

其次,這個行爲應該屬於動物,兩者在概念上不能分開。好比,說話這個行爲是每一個人都具備的,可是說話並不能離開人而獨自存在。

此時,方法method的優勢就體現了出來:

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()
}

運行:

哮天犬 汪汪汪。。。方法
加菲貓 喵喵喵。。。方法
玉兔 吱吱吱。。。方法

三個方法的方法名都同樣,每一個方法都有一個接受者receiver,這個receiver使方法在概念上屬於結構體,就像結構體的字段同樣,可是沒有寫在結構體內。

從這三個方法中能夠看出:只要方法的接收者不一樣,即便方法名相同,方法也不相同

3. 指針和接收者

接收者可使用指針,和函數的參數使用指針同樣(參考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)
}

renamerename1都是改變名字的方法,一個傳引用,一個傳值。只有rename能真正改變名字。

func main() {
	d := dog{"哮天犬"}
	d.rename("小黑黑")
	fmt.Println(d.name)
}

運行:

方法內:小黑黑
小黑黑

rename把「哮天犬」改成了「小黑黑」。

func main() {
	d := dog{"哮天犬"}
	d.rename1("小紅紅")
	fmt.Println(d.name)
}

運行:

方法內:小紅紅
哮天犬

rename1只在方法內改變了名字,並無真正改變「哮天犬」。由於rename1接收的是d的一個拷貝。

方法的指針接收者能夠進行重定向,什麼意思呢?下面用四段代碼來講明。

若是函數的參數是一個指針參數,那麼該函數就必須接收一個指針才行,若是是值則報錯

package main

import "fmt"

func double(x *int) {
	*x = *x * 2
}

func main() {
	i := 2
	double(&i) //編譯正確
	double(i) //報錯
	fmt.Println(i)
}

而若是方法的接收者是一個指針,那麼該方法被調用時,接收者既能夠是指針,又能夠是值

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會進行「重定向」。

同理,反過來也能夠。

若是函數的參數是值,而不是指針,那麼該函數必須接受值,不然會報錯

package main

import "fmt"

func double(x int) {
	x = x * 2
}

func main() {
	i := 2
    p := &i
	double(*p) //參數是值,編譯正確
	//double(p) //參數是指針,報錯
	fmt.Println(i)
}

而若是方法的接收者是一個值,那麼該方法被調用時,接收者既能夠是值,又能夠是指針

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會進行「重定向」。

做者簡介

我是行小觀,我會在公衆號『行人觀學』中持續更新Java、Go、數據結構和算法、計算機基礎等相關文章。


本文章屬於系列文章「Go語言入門系列」,本系列從Go語言基礎開始介紹,適合從零開始的初學者。


歡迎關注,咱們一塊兒踏上行程。

若有錯誤,還請指正。

相關文章
相關標籤/搜索