认识方法
# 1. 认识方法
以 Go 标准库 net/http 包中 *Server 类型的方法 ListenAndServeTLS 为例,讲解一下 Go 方法的一般形式:
# 1.1 方法与函数的异同
相同点:
Go 的方法是以 func 关键字修饰的,和函数一样,包含方法名(对应函数名)、参数列表、返回值列表与方法体(对应函数体);
方法中的这几个部分和函数声明中对应的部分,在形式与语义方面都是一致的,比如:方法名字首字母大小写决定该方法是否是导出方法;方法参数列表支持变长参数;方法的返回值列表也支持具名返回值等。
不同点:
- 和由五个部分组成的函数声明不同,Go 方法的声明有六个组成部分,多的一个就是图中的 receiver 部分。在 receiver 部分声明的参数,Go 称之为 receiver 参数,这个 receiver 参数也是方法与类型之间的纽带,也是方法与函数的最大不同。
# 1.2 receiver 参数
Go 中的方法必须是归属于一个类型的,而 receiver 参数的类型就是这个方法归属的类型,或者说这个方法就是这个类型的一个方法。
以上图中的 ListenAndServeTLS 为例,这里的 receiver 参数 srv 的类型为 *Server,那么就可以说,这个方法就是 *Server 类型的方法。
将上面例子中的方法声明,转换为一个方法的一般声明形式:
func (t *T或T) MethodName(参数列表) (返回值列表) {
// 方法体
}
2
3
无论 receiver 参数的类型为 *T 还是 T,都把一般声明形式中的 T 叫做 receiver 参数 t 的基类型。
如果 t 的类型为 T,那么说这个方法是类型 T 的一个方法;如果 t 的类型为 *T,那么就说这个方法是类型 *T 的一个方法。
而且,要注意的是,每个方法只能有一个 receiver 参数,Go 不支持在方法的 receiver 部分放置包含多个 receiver 参数的参数列表,或者变长 receiver 参数。
**receiver 参数作用域:**方法接收器(receiver)参数、函数 / 方法参数,以及返回值变量对应的作用域范围,都是==函数 / 方法体对应的显式代码块==。
**receiver 部分的参数名具有唯一性:**不能与方法参数列表中的形参名,以及具名返回值中的变量名存在冲突,必须在这个方法的作用域中具有唯一性。
如果这个不唯一不存在,Go 编译器就会报错:
type T struct{}
func (t T) M(t string) { // 编译器报错:duplicate argument t (重复声明参数t)
... ...
}
2
3
4
5
如果在方法体中,没有用到 receiver 参数,也可以省略 receiver 的参数名(这一情况很少使用,了解一下就好):
type T struct{}
func (T) M(t string) {
... ...
}
2
3
4
5
**receiver 参数的基类型约束:**receiver 参数的基类型本身不能为指针类型或接口类型。
基类型为指针类型和接口类型时,Go 编译器会报错:
type MyInt *int
func (r MyInt) String() string { // r的基类型为MyInt,编译器报错:invalid receiver type MyInt (MyInt is a pointer type)
return fmt.Sprintf("%d", *(*int)(r))
}
type MyReader io.Reader
func (r MyReader) Read(p []byte) (int, error) { // r的基类型为MyReader,编译器报错:invalid receiver type MyReader (MyReader is an interface type)
return r.Read(p)
}
2
3
4
5
6
7
8
9
Go 对方法声明的位置也是有约束的,Go 要求,方法声明要与 receiver 参数的基类型声明放在同一个包内。
不能为原生类型(诸如 int、float64、map 等)添加方法;
// 为 Go 原生类型 int 增加新方法 Foo func (i int) Foo() string { // 编译器报错:cannot define new methods on non-local type int return fmt.Sprintf("%d", i) }
1
2
3
4不能跨越 Go 包为其他包的类型声明新方法。
// 跨越包边界,为 Go 标准库中的 http.Server 类型添加新方法 Foo import "net/http" func (s http.Server) Foo() { // 编译器报错:cannot define new methods on non-local type http.Server }
1
2
3
4
5
和其他主流编程语言相比,Go 语言的方法,只比函数多出了一个 receiver 参数,这就大大降低了 Gopher 们学习方法这一语法元素的门槛。
type T struct{}
func (t T) M(n int) {
}
func main() {
var t T
t.M(1) // 通过类型T的变量实例调用方法M
p := &T{}
p.M(2) // 通过类型*T的变量实例调用方法M
}
2
3
4
5
6
7
8
9
10
11
12
13