go语言学习(第六章,方法)(go 语言学习笔记)

6.1 定义

方法是与对象实例绑定的特殊函数。
方法是面对对象编程的基本概念,用于维护和展示对象的自身状态。对象是内敛的,每个实例都有各自不同的独立特征,以属性和方法来暴露对外通信接口。普通函数则专注于算法流程,通过接收参数来完成特定逻辑运算,并返回最终结果。换句话说,方法是有关联状态的,而函数通常没有。
方法和函数定义语法区别的在于前者有前置实例接收参数(receiver),编译器以此确定方法所属类型。在某些语言里,尽管没有显示定义,但会在调用时隐式传递this实例参数。
可以为当前包,以及接口和指针以外的任何类型定义方法。

type N int

func (n N) toString() string {
	return fmt.Sprintf("%#x", n)
}
func main() {
	var a N = 25
	println(a.toString())		// 0x19
}

方法同样不支持重载(overload)receiver参数名没有限制,按惯例会选用简短有意义的名称(不推荐使用this、self)。如方法内部并不引用实例,可省略参数名,仅保留类型。

type N int

func (n N) test() {
	println("hi")
}
func main() {
	var a N = 25
	a.test()
}

方法可看作特殊的函数,那么receiver的类型自然可以是基础类型或指针类型。这会关系到调用时对象实例是否被复制。

type N int

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 = 25
	a.value()
	a.pointer()
	fmt.Printf("a:%p,%v\n", &a, a)
}
结果
v:0xc00004c070,26
p:0xc00004c058,26
a:0xc00004c058,26

可使用实例值或指针调用方法,编译器会根据方法receiver类型自动在基础类型和指针类型间转换。

type N int

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 = 25
	p := &a
	p.value()
	p.pointer()
}
结果
v:0xc00004c070,26
p:0xc00004c058,26

不能使用多级指针调用方法

func main() {
	var a N = 25
	p := &a
	q := &p
	q.value()	//calling method value with receiver q (type **N) requires explicit dereference
	q.pointer()	// calling method pointer with receiver q (type **N) requires explicit dereferenc
}

指针类型的ereceiver必须是合法指针(包括nil),或能获取实例地址

type X struct{}

func (x *X) test() {
	println("test")
}

func main() {
	var a *X
	a.test()
	X{}.test()	//cannot take the address of X literal
}

如何选择方法的receiver类型

  • 无需修改状态的小对象或固定值,建议用T
  • 引用类型、字符串、函数等指针包装类型,直接用T
  • 其他情况用*T

6.2 匿名字段

可以像访问匿名字段成员那样,直接调用其方法,由编译器负责查找。

type data struct {
	sync.Mutex
	buf [1024]byte
}

func main() {
	d := data{}
	d.Lock()
	defer d.Unlock()
}

方法也会有同名遮蔽问题。但利用这种特性,可实现类似覆盖操作。

type user struct{}
type manage struct {
	user
}

func (u *user) test() string {
	return "user test"
}
func (m *manage) test() string {
	return m.user.test() + " then manage test"
}

func main() {
	var m manage
	println(m.test())
	println(m.user.test())
}
结果
user test then manage test
user test

尽管能直接访问匿名字段的成员和方法,但他们依然不属于继承关系。

6.3 方法集

类型有一个与之相关的方法集(method set),这决定了它是否实现了某个接口。

  • 类型T方法集包含所有receiverT方法。
  • 类型*T方法集包含了所有receiverT + *T方法。
  • 匿名嵌入类型S,T方法集包含所有receiverS方法。
  • 匿名嵌入*S,T方法集包含所有receiverS+*S方法。
  • 匿名嵌入S或*S,*T方法集包含所有receiverS + *S方法。
    可利用反射(reflect)测试这些规则。

6.4 表达式

方法和函数一样,除直接调用外,还可复制给变量,或作为参数传递。依照具体引用方式的不同,可分为expression和value两种状态
Method Expression
通过类型引用的method expression会被还原成普通函数样式,receiver是第一参数,调用时须显示传参。至于类型,可以是T或*T,只要目标方法存在于该类型方法集中即可。

type N int

func (n N) test() {
	fmt.Printf("test.n:%p,%d\n", &n, n)
}
func main() {
	var n N = 25
	fmt.Printf("main.n:%p,%d\n", &n, n)
	f1 := N.test
	f1(n)
	f2 := (*N).test
	f2(&n)
}
结果
main.n:0xc00004c058,25
test.n:0xc00004c090,25
test.n:0xc00004c0a0,25

尽管*N方法集包装的test方法receiver类型不同,但编译器会保证按原定义类型拷贝传值。
当然,也可直接以表达方式调用。

func (n N) test() {
	fmt.Printf("test.n:%p,%d\n", &n, n)
}
func main() {
	var n N = 25
	N.test(n)
	(*N).test(&n)
}
结果
test.n:0xc00004c070,25
test.n:0xc00004c088,25

Method Value
基于实例或指针引用的method value,参数签名不变,依旧按正常方式调用。
但当method value 被赋值给变量或作为参数传递时,会立即计算并复制该方法执行所需要的receiver对象,与其绑定,以便在稍后执行时,能隐式传入receiver参数

type N int

func (n N) test() {
	fmt.Printf("test.n:%p,%d\n", &n, n)
}
func main() {
	var n N = 25
	p := &n
	n++
	f1 := n.test		// 因为test方法的receiver类型是N类型,复制n,等于26
	n++
	f2 := p.test		// 复制*p 等于27
	n++
	fmt.Printf("main.n:%p,%d\n", &n, n)
	f1()
	f2()
}
结果
main.n:0xc00004c058,28
test.n:0xc00004c090,26
test.n:0xc00004c0a0,27

编译器会为method value生成一个包装函数,实现间接调用。至于receiver复制,和闭包的实现方法基本相同,打包成funcval,经由Dx寄存器传递
当method value作为参数时,会复制含receiver在内的整个method value。

type N int

func (n N) test() {
	fmt.Printf("test.n:%p,%d\n", &n, n)
}
func call(m func()) {
	m()
}
func main() {
	var n N = 25
	p := &n
	n++
	call(n.test)
	n++
	call(p.test)
	n++
	fmt.Printf("main.n:%p,%d\n", &n, n)

}
结果
test.n:0xc00004c070,26
test.n:0xc00004c088,27
main.n:0xc00004c058,28

当然,如果目标方法的receiver类型是指针类型,那么复制的仅是指针。

func (n *N) test() {
	fmt.Printf("test.n:%p,%d\n", n, *n)
}

func main() {
	var n N = 25
	p := &n
	n++
	f1 := n.test	// 由于复制的是指针,所以调用时的值相同
	n++
	f2 := p.test
	n++
	fmt.Printf("main.n:%p,%d\n", &n, n)
	f1()
	f2()
}
结果
main.n:0xc00004c058,28
test.n:0xc00004c058,28
test.n:0xc00004c058,28

只要receiver参数类型正确,使用nil同样可以执行

type N int

func (N) value()    {}
func (*N) pointer() {}

func main() {
	var p *N
	fmt.Println(p == nil) //true
	p.pointer()
	//p.value()	//invalid memory address or nil pointer dereference(无法获取地址的数据,因为是nil)
	(*N).pointer(nil)
	(*N)(nil).pointer()
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值