本节重点:
- 学会方法的基本使用
方法主要源于 OOP 语言,在传统面向对象语言中 (例如 C++), 我们会用一个“类”来封装属于自己的数据和函数,这些类的函数就叫做方法。
虽然 Go 不是经典意义上的面向对象语言,但是我们可以在一些接收者(自定义类型,结构体)上定义函数,同理这些接收者的函数在 Go 里面也叫做方法。
方法的声明
方法(method)的声明和函数很相似, 只不过它必须指定接收者:
func (t T) F() {}
注意:
- 接收者的类型只能为用关键字
type
定义的类型,例如自定义类型,结构体。 - 同一个接收者的方法名不能重复 (没有重载),如果是结构体,方法名还不能和字段名重复。
- 值作为接收者无法修改其值,如果有更改需求,需要使用指针类型。
简单例子
现在我们写一个简单的程序,在结构类型上创建一个方法并调用它。
package main
import (
"fmt"
)
type Employee struct {
name string
salary int
currency string
}
func (e Employee) displaySalary() {
fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
func main() {
emp1 := Employee {
name: "Sam Adolf",
salary: 5000,
currency: "$",
}
emp1.displaySalary()
}
在上面程序的第 13 行中,我们在 Employee
结构体类型上创建了一个方法 displaySalary
。displaySalary()
方法可以访问其中的接收者 e
。我们使用收款人 e
,打印员工的 name
、currency
和 salary
。
我们在第23行使用该方法调用了 emp1.displaySalary()
上面程序的输出是 :Salary of Sam Adolf is $5000
指针接收器与值接收器型
前面都是带有值接收器的方法。可以使用指针接收器创建方法。值和指针接收器之间的区别在于,在具有指针接收器的方法内部所做的更改对调用者是可见的,而在值接收器中则不然。让我们在程序的帮助下理解这一点。
package main
import (
"fmt"
)
type Employee struct {
name string
age int
}
/*
Method with value receiver
*/
func (e Employee) changeName(newName string) {
e.name = newName
}
/*
Method with pointer receiver
*/
func (e *Employee) changeAge(newAge int) {
e.age = newAge
}
func main() {
e := Employee{
name: "Mark Andrew",
age: 50,
}
fmt.Printf("Employee name before change: %s", e.name)
e.changeName("Michael Andrew")
fmt.Printf("\nEmployee name after change: %s", e.name)
fmt.Printf("\n\nEmployee age before change: %d", e.age)
(&e).changeAge(51)
fmt.Printf("\nEmployee age after change: %d", e.age)
}
在上面的程序中,changeName
方法有一个值接收器 (e Employee)
,而 changeAge
方法有一个指针接收器 (e *Employee)
。在 changeName
中对 Employee
结构体的 name
字段所做的更改对调用者是不可见的,因此程序会在方法 e
之前和之后打印相同的 name
。因为 changeAge
方法有一个指针接收器 (e *Employee)
,所以在方法调用 (&e)
之后对 age
字段所做的更改 (51)
将对调用者可见。这个程序打印:
Employee name before change: Mark Andrew
Employee name after change: Mark Andrew
Employee age before change: 50
Employee age after change: 51
何时使用指针接收与值接收
通常,当调用者应该可以看到方法内对接收器所做的更改时,可以使用指针接收器。
指针接收器也可用于复制数据结构成本高昂的地方。考虑一个具有许多字段的结构。将此结构用作方法中的值接收器将需要复制整个结构,这将是昂贵的。在这种情况下,如果使用指针接收器,则不会复制结构,而在方法中只会使用指向它的指针。
在所有其他情况下,可以使用值接收器。
命名冲突
a. 接收者定义的方法名不能重复, 例如:
package main
type T struct{}
func (T) F() {}
func (T) F(a string) {}
func main() {
t := T{}
t.F()
}
运行代码我们会得到 method redeclared: T.F
类似错误。
b. 结构体方法名不能和字段重复,例如:
package main
type T struct{
F string
}
func (T) F(){}
func main() {
t := T{}
t.F()
}
运行代码我们会得到 : type T has both field and method named F
类似错误。
同一个接收者的方法名不能重复 (没有重载);如果是结构体,方法名不能和字段重复。
接收者可以同时为值和指针
在 Go 语言中,方法的接收者可以同时为值或者指针,例如:
package main
type T struct{}
func (T) F() {}
func (*T) N() {}
func main() {
t := T{}
t.F()
t.N()
t1 := &T{} // 指针类型
t1.F()
t1.N()
}
可以看到无论值类型 T
还是指针类型 &T
都可以同时访问 F
和 N
方法。
值和指针作为接收者的区别
同样我们先看一段代码:
package main
import "fmt"
type T struct {
value int
}
func (m T) StayTheSame() {
m.value = 3
}
func (m *T) Update() {
m.value = 3
}
func main() {
m := T{0}
fmt.Println(m) // {0}
m.StayTheSame()
fmt.Println(m) // {0}
m.Update()
fmt.Println(m) // {3}
}
运行代码输出结果为:
{0}
{0}
{3}
值作为接收者(T
) 不会修改结构体值,而指针 *T
可以修改。