Go方法
方法與函數的區別在於,方法擁有接收者,而函數沒有,且只有自定義類型能夠擁有方法。先來看一個例子。
type IntSlice []int
func (i IntSlice) Get(index int) int {
return i[index]
}
func (i IntSlice) Set(index, val int) {
i[index] = val
}
func (i IntSlice) Len() int {
return len(i)
}先聲明了一個類型IntSlice,其底層類型為[]int,再聲明了三個方法Get,Set和Len,方法的長相與函數並無太大的區別,只是多了一小段(i IntSlice) 。i就是接收者,IntSlice就是接收者的類型,接收者就類似於其他語言中的this或self,只不過在 Go 中需要顯式的指明。
func main() {
var intSlice IntSlice
intSlice = []int{1, 2, 3, 4, 5}
fmt.Println(intSlice.Get(0))
intSlice.Set(0, 2)
fmt.Println(intSlice)
fmt.Println(intSlice.Len())
}方法的使用就類似於調用一個類的成員方法,先聲明,再初始化,再調用。
值接收者
接收者也分兩種類型,值接收者和指針接收者,先看一個例子
type MyInt int
func (i MyInt) Set(val int) {
i = MyInt(val) // 修改了,但是不會造成任何影響
}
func main() {
myInt := MyInt(1)
myInt.Set(2)
fmt.Println(myInt)
}上述代碼運行過後,會發現myInt的值依舊是 1,並沒有被修改成 2。方法在被調用時,會將接收者的值傳入方法中,上例的接收者就是一個值接收者,可以簡單的看成一個形參,而修改一個形參的值,並不會對方法外的值造成任何影響,那麼如果通過指針調用會如何呢?
func main() {
myInt := MyInt(1)
(&myInt).Set(2)
fmt.Println(myInt)
}遺憾的是,這樣的代碼依舊不能修改內部的值,為了能夠匹配上接收者的類型,Go 會將其解引用,解釋為(*(&myInt)).Set(2)。
指針接收者
稍微修改了一下,就能正常修改myInt的值。
type MyInt int
func (i *MyInt) Set(val int) {
*i = MyInt(val)
}
func main() {
myInt := MyInt(1)
myInt.Set(2)
fmt.Println(myInt)
}現在的接收者就是一個指針接收者,雖然myInt是一個值類型,在通過值類型調用指針接收者的方法時,Go 會將其解釋為(&myint).Set(2)。所以方法的接收者為指針時,不管調用者是不是指針,都可以修改內部的值。
函數的參數傳遞過程中,是值拷貝的,如果傳遞的是一個整型,那就拷貝這個整型,如果是一個切片,那就拷貝這個切片,但如果是一個指針,就只需要拷貝這個指針,顯然傳遞一個指針比起傳遞一個切片所消耗的資源更小,接收者也不例外,值接收者和指針接收者也是同樣的道理。在大多數情況下,都推薦使用指針接收者,不過兩者並不應該混合使用,要麼都用,要麼就都不用,看下面一個例子。
TIP
需要先了解接口
type Animal interface {
Run()
}
type Dog struct {
}
func (d *Dog) Run() {
fmt.Println("Run")
}
func main() {
var an Animal
an = Dog{}
// an = &Dog{} 正確方式
an.Run()
}這一段代碼將會無法通過編譯,編譯器將會輸出如下錯誤
cannot use Dog{} (value of type Dog) as type Animal in assignment:
Dog does not implement Animal (Run method has pointer receiver)翻譯過來就是,無法使用Dog{}初始化Animal類型的變量,因為Dog沒有實現Animal ,解決辦法有兩種,一是將指針接收者改為值接收者,二是將Dog{}改為&Dog{},接下來逐個講解。
type Dog struct {
}
func (d Dog) Run() { // 改為了值接收者
fmt.Println("Run")
}
func main() { // 可以正常運行
var an Animal
an = Dog{}
// an = &Dog{} 同樣可以
an.Run()
}在原來的代碼中,Run 方法的接收者是*Dog ,自然而然實現Animal接口的就是Dog指針,而不是Dog結構體,這是兩個不同的類型,所以編譯器就會認為Dog{}並不是Animal的實現,因此無法賦值給變量an,所以第二種解決辦法就是賦值Dog指針給變量an。不過在使用值接收者時,Dog指針依然可以正常賦值給animal,這是因為 Go 會在適當情況下對指針進行解引用,因為通過指針可以找到Dog結構體,但是反過來的話,無法通過Dog結構體找到Dog指針。如果單純的在結構體中混用值接收者和指針接收者的話無傷大雅,但是和接口一起使用後,就會出現錯誤,倒不如無論何時要麼都用值接收者,要麼就都用指針接收者,形成一個良好的規范,也可以減少後續維護的負擔。
還有一種情況,就是當值接收者是可尋址的時候,Go 會自動的插入指針運算符來進行調用,例如切片是可尋址,依舊可以通過值接收者來修改其內部值。比如下面這個代碼
type Slice []int
func (s Slice) Set(i int, v int) {
s[i] = v
}
func main() {
s := make(Slice, 1)
s.Set(0, 1)
fmt.Println(s)
}輸出
[1]但這樣會引發另一個問題,如果對其添加元素的話,情況就不同了。看下面的例子
type Slice []int
func (s Slice) Set(i int, v int) {
s[i] = v
}
func (s Slice) Append(a int) {
s = append(s, a)
}
func main() {
s := make(Slice, 1, 2)
s.Set(0, 1)
s.Append(2)
fmt.Println(s)
}[1]它的輸出還是和之前一樣,append函數是有返回值的,向切片添加完元素後必須覆蓋原切片,尤其是在擴容後,在方法中對值接收者修改並不會產生任何影響,這也就導致了例子中的結果,改成指針接收者就正常了。
type Slice []int
func (s *Slice) Set(i int, v int) {
(*s)[i] = v
}
func (s *Slice) Append(a int) {
*s = append(*s, a)
}
func main() {
s := make(Slice, 1, 2)
s.Set(0, 1)
s.Append(2)
fmt.Println(s)
}輸出
[1 2]