Go 方法(第二部分)

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

内容简介:这篇文章介绍了关于 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 方法(第二部分)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

复杂性思考

复杂性思考

Allen B. Downey / 张龙 / 机械工业出版社 / 2013-5 / 49.00元

本书的灵感来源于无聊与迷恋的感觉:对常规的数据结构与算法介绍的无聊,对复杂系统的迷恋。数据结构的问题在于教师在教授这门课程的时候通常不会调动起学生的积极性;复杂性科学的问题在于学校通常不会教授这门课程。 2005年,我在欧林学院讲授了一门新课程,学生要阅读关于复杂性的主题,使用Python进行实验,并学习算法与数据结构。当我在2008年再次讲授这门课程时,我写了本书的初稿。 在2011......一起来看看 《复杂性思考》 这本书的介绍吧!

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

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

SHA 加密
SHA 加密

SHA 加密工具