雨痕 Go语言学习笔记-方法

方法定义

  1. ⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver)
  2. 函数和方法有区别,方法和实例绑定,会改变实例状态,方法不会
  3. 不⽀持⽅法重载,receiver 只是参数签名的组成部分。(重载,参数和返回值不同的同名函数,实际是函数签名不同)
  4. 可⽤实例 value 或 pointer 调⽤全部⽅法,编译器⾃动转换
  5. 参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名
  6. 从 1.4 开始,不再⽀持多级指针查找⽅法成员。经笔者测试,在Go1.7.4中正常运行
type X struct{}
func (*X) test() {
 println("X.test")
}
func main() {
 p := &X{}
 p.test()
 // Error: calling method with receiver &p (type **X) requires explicit dereference
 // (&p).test()
}
  1. 通过匿名字段,可获得和继承类似的复⽤能⼒。依据编译器查找次序,只需在外层定义同

    名⽅法,就可以实现 "override"。

方法集

  1. 每个类型都有与之关联的⽅法集,这会影响到接⼝实现规则。

    • 类型 T ⽅法集包含全部 receiver T ⽅法。

    • 类型 *T ⽅法集包含全部 receiver T + *T ⽅法。

    • 如类型 S 包含匿名字段 T,则 S ⽅法集包含 T ⽅法。

    • 如类型 S 包含匿名字段 *T,则 S ⽅法集包含 T + *T ⽅法。

    • 不管嵌⼊ T 或 *T,*S ⽅法集总是包含 T + *T ⽅法。
  2. 方法集在实现接口是很重要,有时候某类型没有实现接口就是因为方法集没有正确判断
  3. ⽤实例 value 和 pointer 调⽤⽅法 (含匿名字段) 不受⽅法集约束,编译器总是查找全部

    ⽅法,并⾃动转换 receiver 实参。
t := NewT()
t.Hello()

表达式

  1. 根据调⽤者不同,⽅法分为两种表现形式,前者称为 method value,后者 method expression:
instance.method(args...) ---> <type>.func(instance, args...)
  1. 两者都可像普通函数那样赋值和传参,区别在于 method value 绑定实例,⽽ method

    expression 则须显式传参:
type User struct {
 id int
 name string
}
func (self *User) Test() {
 fmt.Printf("%p, %v\n", self, self)
}

func main() {
 u := User{1, "Tom"}
 u.Test()
 mValue := u.Test
 mValue() // 隐式传递 receiver
 mExpression := (*User).Test
 mExpression(&u) // 显式传递 receiver
}

# output
0x210230000, &{1 Tom}
0x210230000, &{1 Tom}
0x210230000, &{1 Tom}
  1. 需要注意,method value 会复制 receiver
func main() {
 u := User{1, "Tom"}
 mValue := u.Test // ⽴即复制 receiver,因为不是指针类型,不受后续修改影响。
 u.id, u.name = 2, "Jack"
 u.Test()
 mValue()
}
输出:
{2 Jack}
{1 Tom}
  1. 在汇编层⾯,method value 和闭包的实现⽅式相同,实际返回 FuncVal 类型对象。
FuncVal { method_address, receiver_copy }
  1. 可依据⽅法集转换 method expression,注意 receiver 类型的差异
type User struct {
 id int
 name string
}
func (self *User) TestPointer() {
 fmt.Printf("TestPointer: %p, %v\n", self, self)
}
func (self User) TestValue() {
 fmt.Printf("TestValue: %p, %v\n", &self, self)
}
func main() {
 u := User{1, "Tom"}
 fmt.Printf("User: %p, %v\n", &u, u)
 mv := User.TestValue
 mv(u)
 mp := (*User).TestPointer
 mp(&u)
 mp2 := (*User).TestValue // *User ⽅法集包含 TestValue。
 mp2(&u)                  // 签名变为 func TestValue(self *User)。
}                         // 实际依然是 receiver value copy。
输出:
User : 0x210231000, {1 Tom}
TestValue : 0x210231060, {1 Tom}
TestPointer: 0x210231000, &{1 Tom}
TestValue : 0x2102310c0, {1 Tom}

总结

  1. 函数表达式中关于方法的两种调用方式,以及复制receiver
  2. 可依据⽅法集转换 method expression,注意 receiver 类型的差异。
type User struct {
 id int
 name string
}
func (self *User) TestPointer() {
 fmt.Printf("TestPointer: %p, %v\n", self, self)
}
func (self User) TestValue() {
 fmt.Printf("TestValue: %p, %v\n", &self, self)
}
func main() {
 u := User{1, "Tom"}
 fmt.Printf("User: %p, %v\n", &u, u)
 mv := User.TestValue
 mv(u)
 mp := (*User).TestPointer
 mp(&u)
 mp2 := (*User).TestValue // *User ⽅法集包含 TestValue。
 mp2(&u) // 签名变为 func TestValue(self *User)。
} // 实际依然是 receiver value copy。
输出:
User : 0x210231000, {1 Tom}
TestValue : 0x210231060, {1 Tom}
TestPointer: 0x210231000, &{1 Tom}
TestValue : 0x2102310c0, {1 Tom}
  1. 将⽅法 "还原" 成函数,就容易理解下⾯的代码了。
type Data struct{}
func (Data) TestValue() {}
func (*Data) TestPointer() {}
func main() {
 var p *Data = nil
 p.TestPointer()
 (*Data)(nil).TestPointer() // method value
 (*Data).TestPointer(nil) // method expression
 // p.TestValue() // invalid memory address or nil pointer dereference
 // (Data)(nil).TestValue() // cannot convert nil to type Data
 // Data.TestValue(nil) // cannot use nil as type Data in function argument
}
  1. 2 3 没有理解
comments powered by Disqus