第六章(方法)[上]
-
方法是与对象实例绑定的特殊函数
-
方法和函数定义语法区别的在于前者有前置实例接收参数(receiver),编译器以此确定方法所属类型。
-
可以为当前包,以及除接口和指针以外的任何类型定义方法
type N int
func (n N) toString() string {
return fmt.Sprintf("%#x", n)
}
func main() {
var a N = 1
println(a.toString())//0x1
}
- 当定义一个类型为指针的变量时。接收者(
receiver
)为指针类型报错
type Student struct {
age int8
name string
}
type StudentPoint *Student
// Error: 接收者(receiver)为指针类型
func (s StudentPoint) showName2() {
fmt.Println(s.name)
}
- 方法同样不支持重载(overload)
方法重载是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数
- 当
receiver
的类型是指针时
type N int
func (n N) toString() string {
return fmt.Sprintf("%#x", n)
}
func (n N) value() { //值复制
n++
fmt.Printf("v: %p, %v\n", &n, n)
}
func (n *N) pointer() { //指针传递
*n++
fmt.Printf("p: %p, %v\n", n, *n)
}
func main() {
var a N = 1
a.value() //v: 0xc0000180c0, 2
fmt.Printf("a: %p, %v\n", &a, a) //a: 0xc0000180a8, 1
a.pointer() //p: 0xc0000180a8, 2
fmt.Printf("a: %p, %v\n", &a, a) //a: 0xc0000180a8, 2
}
- 使用实例值或指针调用方法时,编译器会根据方法
receiver
类型自动在基础类型和指针类型间自动转换
func main() {
var a N = 1
p := &a
a.value() //v: 0xc0000180c0, 2
a.pointer() //p: 0xc0000180a8, 2
p.value() //v: 0xc0000180f0, 3 //自动转换 (*p).value()
p.pointer() //p: 0xc0000180a8, 3 //自动转换 (*p).pointer()
}
-
不能使用多级指针调用方法
-
指针类型
的receiver
必须是合法指针(包括nil)或能获取实例地址
type X struct{}
func (x *X) test() {
println("Hi!", x)
}
func main() {
var a1 *X
a1.test() //Hi! 0x0 --- 0x0就是nil的地址
//X{}.test() //cannot call pointer method on X{}
}
-
如何选择方法的
receiver
类型- 要修改实例状态,用*T
- 无须需改状态的小对象或固定值,建议用T
- 大对象建议用*T,以减少复制成本
- 引用类型、字符串,函数等指针包装对象,直接用T
- 若包含
Mutex
等同步字段,用*T,避免因复制造成锁操作无效 - 其它无法确定的情况,都用*T
-
可以像访问匿名字段成员那样调用其方法,由编译器负责查找
type data struct {
sync.Mutex
buf [1024]byte
}
func main() {
d := data{}
d.Lock() //编译器会处理成 sync.(*Mutex).Lock 调用
defer d.Unlock()
}
- 方法会有同名遮蔽问题,利用该特性可实现类似覆盖(override)操作
方法覆盖(Overriding): 如果在子类中定义一个方法,其名称、返回类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,那么可以说,子类的方法覆盖了父类的方法。
type user struct{}
type manager struct {
user
}
func (user) toString() string {
return "user"
}
func (m manager) toString() string {
return m.user.toString() + ";manager"
}
func main() {
var m manager
println(m.toString()) //user;manager
println(m.user.toString()) //user
}
注意:尽管能直接访问匿名字段的成员和方法,但不属于继承关系
- 方法集
- 类型T方法集包含所有
receiver
T 方法 - 类型*T方法集包含所有
receiver
T + *T 方法 - 匿名嵌入S,T方法集包含所有
receiver
S 方法 - 匿名嵌入*S,T方法集包含所有
receiver
S + *S方法 - 匿名嵌入S或*S,*T方法集包含所有
receiver
S + *S方法
- 类型T方法集包含所有
package main
import (
"fmt"
)
type Student struct {
age int8
name string
}
type StudentPoint *Student
func (Student) sayHello() {
fmt.Println("hello world")
}
func (s Student) showName() {
fmt.Println(s.name)
}
func (s *Student) sayHi() {
fmt.Println("Hi")
}
func (s *Student) setName(newName string) {
s.name = newName
}
// Error: 接收者(receiver)为指针类型
/*func (s StudentPoint) showName2() {
fmt.Println(s.name)*/
}
func main() {
s := Student{}
// go会自动转为(&s).setName("dq")
s.setName("dq")
s.sayHi() // s 可以调用*s 方法
var s2 = &s
// go会自动转为(*s2).showName()
s2.showName()
}
- 上面可以用 s 调用 *s 的方法?
实例value(普通值传递)或pointer(指针)可以调用全部的方法,编译器会自动转换。(方法集只对接口
interface
起作用)
- 方法集仅影响接口实现和方法表达式转换,与通过实例或实例指针调用方法无法。
面向对象三大特性:封装、继承、多态。go仅实现了部分特征,它更倾向于“组合优于继承”这种思想。
组合没有父子依赖,不会破坏封装。且整体和局部松耦合,可任意增加来实现扩展。各单元特有单一职责,互不关联,实现和维护更加简单