方法其实在之前结构体那边简单讲过, 方法 其实就是一个函数,在 func
这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型。接收器是可以在方法的内部访问的。
func (t Type) methodName(parameter list) {
}
上面的代码片段创建了一个接收器类型为 Type
的方法 methodName
。
摘抄当时给结构体定义方法的例子,你就可能更加明白了。
package main
import "fmt"
// Person 定义一个名为 Person 的结构体
type Person struct {
name string
age int
}
// PrintPersonInfo 定义一个与 Person 的绑定的方法
func (person Person) PrintPersonInfo() {
fmt.Println("name:", person.name)
fmt.Println("age:", person.age)
}
func main() {
per13 := Person{
name: "John",
age: 30,
}
per13.PrintPersonInfo()
}
上面的程序中定义了一个与结构体 Person
绑定的方法 PrintPersonInfo()
,其中 PrintPersonInfo
是方法名, (person Person)
表示将此方法与 Person
的实例绑定,这里我们把 Person
称为方法的接收者,而 person
表示实例本身,相当于 Python 中的 self
,在方法内可以使用 person.attribute_name
来访问实例属性。运行该程序输出如下:
name: John
age: 30
当然,你可以把上面程序的方法改成一个函数,如下:
package main
import "fmt"
type Person struct {
name string
age int
}
func PrintPersonInfo(person Person) {
fmt.Println("name:", person.name)
fmt.Println("age:", person.age)
}
func main() {
per13 := Person{
name: "John",
age: 30,
}
PrintPersonInfo(per13)
}
运行这个程序,也同样会输出上面一样的答案,那么我们为什么还要用方法呢?因为在 Go 中,相同的名字的方法可以定义在不同的类型上,而相同名字的函数是不被允许的。如果你在上面这个程序添加一个同名函数,就会报错。但是在不同的结构体上面定义同名的方法就是可行的。
package main
import "fmt"
type Person struct {
name string
age int
}
func (person Person) PrintInfo() {
fmt.Println("person name:", person.name)
fmt.Println("person age:", person.age)
}
type City struct {
name string
}
func (city City) PrintInfo() {
fmt.Println("city name:", city.name)
}
func main() {
person := Person{name: "John", age: 30}
person.PrintInfo()
city := City{"Beijing"}
city.PrintInfo()
}
运行该程序输出如下:
person name: John
person age: 30
city name: Beijing
指针接收器与值接收器
值接收器和指针接收器之间的区别在于,在指针接收器的方法内部的改变对于调用者是可见的,然而值接收器的方法内部的改变对于调用者是不可见的,所以若要改变实例的属性时,必须使用指针作为方法的接收者。看看下面的例子就知道了:
package main
import "fmt"
// Person 定义一个名为 Person 的结构体
type Person struct {
name string
age int
}
// PrintPersonInfo 定义一个与 Person 的绑定的方法
func (person Person) PrintPersonInfo() {
fmt.Println("name:", person.name)
fmt.Println("age:", person.age)
}
func (person Person) ChangePersonName(name string) {
person.name = name
}
// AddPersonAge 定义一个与 Person 的绑定的方法,使 age 值加 n
func (person *Person) AddPersonAge(n int) {
person.age = person.age + n
}
func main() {
person := Person{
name: "John",
age: 30,
}
fmt.Println("before change")
person.PrintPersonInfo()
fmt.Println("after change")
person.AddPersonAge(5)
person.ChangePersonName("Mary")
person.PrintPersonInfo()
}
在上面的程序中, AddPersonAge
使用指针接收器最终能改变实例的 age
值,然而使用值接收器的 ChangePersonName
最终没有改变实例 name
的值。运行该程序输出如下:
before change
name: John
age: 30
after change
name: John
age: 35
在方法中使用值接收器 与 在函数中使用值参数
当一个函数有一个值参数,它只能接受一个值参数。当一个方法有一个值接收器,它可以接受值接收器和指针接收器。
package main
import "fmt"
type Person struct {
name string
}
func (person Person) PrintInfo() {
fmt.Println(person.name)
}
func PrintInfo(person Person) {
fmt.Println(person.name)
}
func main() {
person := Person{"John"}
PrintInfo(person)
person.PrintInfo()
p := &person
//PrintInfo(p) // error
p.PrintInfo()
}
在上面的程序中,使用值参数 PrintInfo(person)
来调用这个函数是合法的,使用值接收器来调用 person.PrintInfo()
也是合法的。
然后在程序中我们创建了一个指向 person
的指针 p
,通过使用指针接收器来调用 p.PrintInfo()
是合法的,但使用值参数调用 PrintInfo(p)
是非法的。
在非结构体上的方法
不仅可以在结构体类型上定义方法,也可以在非结构体类型上定义方法,但是有一个问题。为了在一个类型上定义一个方法,方法的接收器类型定义和方法的定义应该在同一个包中。例如:
package main
import "fmt"
type myInt int
func (a myInt) add(b myInt) myInt {
return a + b
}
func main() {
var x myInt = 10
var y myInt = 20
fmt.Println(x.add(y)) // 30
}
参考文献:
[1] Alan A. A. Donovan; Brian W. Kernighan, Go 程序设计语言, Translated by 李道兵, 高博, 庞向才, 金鑫鑫 and 林齐斌, 机械工业出版社, 2017.