Go 方法(第二部分)

栏目: Go · 发布时间: 6年前

内容简介:这篇文章介绍了关于 Go 语言中方法的剩余部分。强烈建议先阅读第一部分 的介绍部分。如果有这样一个类型 T,它的方法集中包含方法 M,则 T.M 会生成一个与方法 M 几乎相同且带有签名的方法,这称为输出:

这篇文章介绍了关于 Go 语言中方法的剩余部分。强烈建议先阅读第一部分 的介绍部分。

方法表达式

如果有这样一个类型 T,它的方法集中包含方法 M,则 T.M 会生成一个与方法 M 几乎相同且带有签名的方法,这称为 方法表达式 。不同之处在于,它额外附带的第一个参数与 M 的接收者类型相等。

package main

import (
	"fmt"
	"reflect"
)

func PrintFunction(val interface{}) {
	t := reflect.TypeOf(val)
	fmt.Printf("Is variadic: %v\n", t.IsVariadic())
	for i := 0; i < t.NumIn(); i++ {
		fmt.Printf("Parameter #%v: %v\n", i, t.In(i))
	}
}

type T struct{}

func (t T) M(text string, number int) {}
func (t *T) N(map[string]int)         {}
func main() {
	PrintFunction(T.M)
	PrintFunction((*T).M)
	PrintFunction((*T).N)
}

输出:

Is variadic: false
Parameter #0: main.T
Parameter #1: string
Parameter #2: int
Is variadic: false
Parameter #0: *main.T
Parameter #1: string
Parameter #2: int
Is variadic: false
Parameter #0: *main.T
Parameter #1: map[string]int

如果方法 M 不在类型 T 的方法集中,使用表达式 T.M 会导致错误 invalid method expression T.N (needs pointer receiver: (*T).N)

在上面的片段中,有一个有趣的案例 PrintFunction((*T).M) ,即使方法 M 拥有的是值接收器,它仍然使用 *main.T 的第一个参数创建方法。Go 的运行时会在后台传递指针,创建副本并传递给方法。使用这种方式,方法无法访问原始值。

type T struct {
	name string
}

func (t T) M() {
	t.name = "changed"
}
func (t *T) N() {
	t.name = "changed"
}
func main() {
	t := T{name: "Michał"}
	(*T).M(&t)
	fmt.Println(t.name)
	(*T).N(&t)
	fmt.Println(t.name)
}

输出:

Michał
changed

可以从接口类型创建方法表达式:

package main

import "fmt"

type T struct {
	name string
}

func (t T) M() {
	fmt.Println(t.name)
}

type I interface {
	M()
}

func main() {
	t1 := T{name: "Michał"}
	t2 := T{name: "Tom"}
	m := I.M
	m(t1)
	m(t2)
}

输出:

Michał
Tom

方法值

与类型和 方法表达式 类似,使用表达式可以得到一个带有接收器的方法,这就是 方法值 。如果有表达式 x ,则 x.M 和方法 M 一样可以使用同样的参数调用。当然,方法 M 需要在类型 x 的方法集中,如果 x 是可寻址类型,M 应该在类型 &x 的方法集中。

type T struct {
	name string
}

func (t *T) M(string) {}
func (t T) N(float64) {}
func main() {
	t := T{name: "Michał"}
	m := t.M
	n := t.N
	m("foo")
	n(1.1)
}

提升方法

如果一个结构包含内嵌(匿名)的属性,那么这个属性的方法也处于该结构类型的方法集中。

package main

import "fmt"

type T struct {
	name string
}

func (t T) M() string {
	return t.name
}

type U struct {
	T
}

func main() {
	u := U{T{name: "Michał"}}
	fmt.Println(u.M())
}

上面的 Go 程序输出 Michał 是完全正确的。说嵌入到结构类型中属性的方法属于该类型的方法集是有确切原因的:

#1

如果结构类型 U 包含了内嵌属性 T,那么方法集 S 和 *S 包含带有接收器 T 的提升方法。另外,方法集 *S 包含的是带有接收器 *T 的提升方法。

package main

import (
	"fmt"
	"reflect"
)

func PrintMethodSet(val interface{}) {
	t := reflect.TypeOf(val)
	fmt.Printf("Method set count: %d\n", t.NumMethod())
	for i := 0; i < t.NumMethod(); i++ {
		m := t.Method(i)
		fmt.Printf("Method: %s\n", m.Name)
	}
}

type T struct {
	name string
}

func (t T) M()  {}
func (t *T) N() {}

type U struct {
	T
}

func main() {
	u := U{T{name: "Michał"}}
	PrintMethodSet(u)
	PrintMethodSet(&u)
}

上面的程序输出:

Method set count: 1
Method: M
Method set count: 2
Method: M
Method: N

从本文介绍的第一部分,我们应当知晓的是语言规范中的附加调用规则:

如果 x 是可寻址的,并且 &x 的方法集中包含 m,(&x).m() 可以简写为 x.m()。

所以尽管方法 N 不是类型 U 的方法集的一部分,我们仍可以使用 u.N() 这样的调用。

#2

如果结构类型 U 包含内嵌属性 *T ,那么方法集 S 和 *S 中包带有接收器 T 和 *T 的提升方法。

type T struct {
	name string
}

func (t T) M()  {}
func (t *T) N() {}

type U struct {
	*T
}

func main() {
	u := U{&T{name: "Michał"}}
	PrintMethodSet(u)
	PrintMethodSet(&u)
}

打印:

Method set count: 2
Method: M
Method: N
Method set count: 2
Method: M
Method: N

以上所述就是小编给大家介绍的《Go 方法(第二部分)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

RESTful Web Services Cookbook

RESTful Web Services Cookbook

Subbu Allamaraju / Yahoo Press / 2010-3-11 / USD 39.99

While the REST design philosophy has captured the imagination of web and enterprise developers alike, using this approach to develop real web services is no picnic. This cookbook includes more than 10......一起来看看 《RESTful Web Services Cookbook》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

MD5 加密
MD5 加密

MD5 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试