go語言基礎方法和接收器

 閱讀大約需要2分鐘

go語言基礎方法和接收器

在Go語言中有一個概念和函數極其相似叫做方法,Go語言的方法其實是作用在接收者(receiver)上的一個函數,接收者是某種非內置類型的變量,因此方法是一種特殊類型的函數。

方法和函數的不同點:
  • 函數和方法聲明的方式不同

  • 函數可以被當作參數傳遞,方法則不行

  • 函數可以匿名,方法則不行

  • 函數參數為值類型時,不能將指針類型的數據直接傳遞,反之亦然。

  • 方法接收者為值類型時,可以直接用指針類型的變量調用方法,反過來同樣也可以。

方法的聲明和普通函數的聲明類似,只是在函數名稱前面多了一個參數,這個參數把這個方法綁定到這個參數對應的類型上。

func (t Type) methodName(parameter list) {

}

上面的代碼片段創建了一個接收器類型為Type的方法methodName。

  • 接收器變量t:接收器中的參數變量名在命名時,官方建議使用接收器類型名的第一個小寫字母,而不是 self、this 之類的命名。 例如,Socket類型的接收器變量應該命名為s,Connector類型的接收器變量應該命名為c等。

  • 接收器類型Type:接收器類型和參數類似,可以是指針類型和非指針類型。

  • 方法名、參數列表、返回參數:格式與函數定義一致。

方法不僅僅可以隸屬於結構體類型,還可以隸屬於非接口、非指針類型的其它任何自定義類型。

我們可以用Go語言的type,來定義一個和int具有同樣功能的類型。這個類型不能看成是int類型的別名,它們屬於不同的類型,不能直接相互賦值。

package main

import "fmt"

// 將newInt定義為int類型
type newInt int

func (n newInt) Add(b newInt) newInt {
	return n + b
}

func main() {
	var a newInt
	a = 100
	fmt.Println(a)        // 100
	fmt.Printf("%T\n", a) // main.newInt
	fmt.Println(a.Add(10))
}

方法是特殊的函數,定義在某一特定的類型上,通過類型的實例來進行調用,這個實例叫接收者。

接收者必須有一個顯式的名字,這個名字必須在方法中被使用

接收者類型必須在和方法同樣的包中被聲明

同一個類型的方法名是不允許重複的

Go語言不允許為簡單的內置類型添加方法,下面定義的方法是非法的。

func (a int) Add(b int) {
	fmt.Println(a+b)
}

既然已經有函數,為什麼還要有方法呢?

  • Go不是純粹的面向對象編程語言,而且Go不支持類,因此基於類型的方法是一種實現和類相似行為的途徑。

  • 相同的名字的方法可以定義在不同的類型上,而相同名字的函數是不被允許的。

假設我們有一個Square和Circle結構體,可以在Square和Circle上分別定義一個Area方法,詳見下面的程序。

package main

import (
	"fmt"
	"math"
)

type Rectangle struct {
	length int
	width  int
}

type Circle struct {
	radius float64
}

func (r Rectangle) Area() int {
	return r.length * r.width
}

func (c Circle) Area() float64 {
	return math.Pi * c.radius * c.radius
}

func main() {
	r := Rectangle{
		length: 10,
		width:  10,
	}
	fmt.Printf("Area of rectangle %d\n", r.Area())
	c := Circle{
		radius: 10,
	}
	fmt.Printf("Area of circle %f", c.Area())
}
指針接收器與值接收器

值接收器和指針接收器之間的區別在於指針接收器的方法內部的改變對於調用者是可見的,而值接收器是不可見的。

package main

import "fmt"

type Person2 struct {
	Name string
	Age  int
}

func (p Person2) SetName(name string)  {
	p.Name=name
}

func (p *Person2) SetAge(age int)  {
	p.Age=age
}

func (p *Person2) getName() string {
	return p.Name
}

func (p *Person2) getAge() int {
	return p.Age
}


func main() {
	p1:=new(Person2)
	p1.SetName("John")
	p1.SetAge(18)
	fmt.Println(p1.getName())
	fmt.Println(p1.getAge())

	p2:=Person2{}
	p2.SetName("bruce")
	p2.SetAge(19)  //使用值類型來調用指針接收器
	fmt.Println(p2.getName())
	fmt.Println(p2.getAge())
}

運行結果如下:


18

19

上面分別創建了指針類型的實例p1和值類型的實例p2,但無論是p1還是p2,它們調用SetName()方法設置的name值都沒有影響原始實例中的name值,所以getName()都輸出空字符串, 而它們調用setAge()方法設置的age值都影響了原始實例中的age值。

我們通過p2來調用指針接收器的方法SetAge這是被允許的,為了方便Go語言把代碼p2.SetAge(19)解釋為(&p2).SetAge(19)

如何選擇接收器類型

在計算機中小對象由於值複製時的速度比較快,所以適合使用非指針接收器。

大對象因為複制性能低,適合使用指針接收器,在接收器和參數間傳遞時不進行複制,只傳遞指針。

有修改成員變量的需求,用指針類型的接收器。