golang 方法

100018 · · 853 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

golang语言中的方法是与对象实例绑定的特殊函数,用于维护和展示对象的自身状态。

与函数的区别是方法有前置实例接收参数(receiver),编译器根据receiver来判断该方法属于哪个实例。receiver可以是基础类型,也可以是指针类型,这会关系到是否需要有可以修改对象实例的能力。

在调用方法时,可以使用对象实例值或指针,编译器会根据receiver类型自动在基础类型和指针类型之间转换,比如:

type聽rect聽struct聽{
聽聽聽聽width,聽height,聽area聽int
}

func聽(r聽*rect)聽pointer()聽{
聽聽聽聽r.width聽+=聽2
聽聽聽聽r.area聽=聽r.width聽*聽r.height
}
func聽(r聽rect)聽value()聽{
聽聽聽聽r.width聽+=聽4
聽聽聽聽r.area聽=聽r.width聽*聽r.height
}

func聽main()聽{
聽聽聽聽r聽:=聽rect{width:聽10,聽height:聽5}
聽聽聽聽r.value()
聽聽聽聽fmt.Println(r)
聽聽聽聽r.pointer()
聽聽聽聽fmt.Println(r)
/*
聽聽聽聽r聽:=聽&rect{width:聽10,聽height:聽5}
聽聽聽聽r.value()
聽聽聽聽fmt.Println(r)
聽聽聽聽r.pointer()
聽聽聽聽fmt.Println(r)
*/聽
}
输出:
{10聽5聽0}
{12聽5聽60}

如果使用指针调用方法时,需要注意不能使用多级指针,且必须使用合法的指针(包括nil)或能取实例地址,比如:

type聽X聽struct{}

func聽(x聽*X)test(){
聽聽聽聽fmt.Println("hello聽gopher")
}

func聽main(){
聽聽聽聽var聽x聽*X
聽聽聽聽fmt.Println(x)
聽聽聽聽x.test()
聽聽聽聽&X{}.test()聽//cannot聽take聽the聽address聽of聽X聽literal
}


如何选择方法的receiver类型?

- 如果要修改实例状态,用*T

- 如果不需要修改实例状态的小对象或固定值,建议用T

- 如果是大对象,建议用*T,可以减少复制成本

- 引用类型、字符串、函数等指针包装对象,用T

- 如果对象实例中包含Mutex等同步字段,用*T, 以免因复制造成锁无效

- 无法确定需求的情况,都用*T

通过匿名字段的方法访问:

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

type聽person聽聽struct{}

type聽Man聽struct{
聽聽聽聽person
}
func聽(p聽person)toWork()string{
聽聽聽聽return聽"Tom"
}
func聽main(){
聽聽聽聽var聽m聽Man聽
聽聽聽聽fmt.Println(m.toWork())
}
输出:
Tom

如果Man结构体也有个同名的toWork方法,此时调用逻辑如下,比如:

type聽person聽聽struct{}

type聽Man聽struct{
聽聽聽聽person
}

func聽(p聽person)toWork()string{
聽聽聽聽return聽"Tom聽to聽work"
}

func聽(m聽Man)toWork()string聽{
聽聽聽聽return聽"me聽to聽work"
}
func聽main(){
聽聽聽聽var聽m聽Man
聽聽聽聽fmt.Println(m.toWork())聽聽聽聽聽聽聽聽聽//me聽to聽work
聽聽聽聽fmt.Println(m.person.toWork())聽聽//Tom聽to聽work
}

方法集:

GoLang规范中提到了一个与类型相关的方法集(method set),这决定了它是否实现了某个接口。

- 类型T方法集包含所有receiver T方法

- 类型*T方法集包含所有receiver T + *T的方法

- 匿名嵌入S,T方法集包含所有receiver S方法

- 匿名嵌入*S,T方法集包含所有receiver S + receiver *S方法

- 匿名嵌入*S或匿名嵌入S,*T方法集包含所有receiver S + receiver *S方法

type聽S聽struct{}

type聽T聽struct{
聽聽聽聽S聽聽聽
}

func聽(S)聽SVal()聽聽{}聽聽
func聽(*S)聽SPtr()聽{}
func聽(T)聽TVal()聽聽{}聽聽
func聽(*T)聽TPtr()聽{}

func聽methodSet(a聽interface{})聽{
聽聽聽聽t聽:=聽reflect.TypeOf(a)
聽聽聽聽for聽i,聽n聽:=聽0,聽t.NumMethod();聽i聽<聽n;聽i++聽{
聽聽聽聽聽聽聽聽m聽:=聽t.Method(i)
聽聽聽聽聽聽聽聽fmt.Println(m.Name,聽m.Type)
聽聽聽聽}聽聽聽
}
聽
func聽main()聽{
聽聽聽聽var聽t聽T聽
聽聽聽聽methodSet(t)
聽聽聽聽println("--------------")
聽聽聽聽methodSet(&t)
}
输出:
SVal聽func(main.T)
TVal聽func(main.T)
--------------
SPtr聽func(*main.T)
SVal聽func(*main.T)
TPtr聽func(*main.T)
TVal聽func(*main.T)

很显然, 匿名字段就是为扩展方法集准备的。否则没有必要少写个字段。这种组合没有父子依赖关系,

整体与局部松耦合,可以任意增加来实现扩展。各单元互无关联,实现与维护更加简单。


方法表达式:

方法是一种特殊的函数,除了可以直接调用之外,还可以进行赋值或当作参数传递,下面是Go语言的方法定义格式,比如:

func聽(p聽mytype)聽funcname(q聽type)聽(r,s聽type)聽{聽return聽0,0}

本质上这就是一种语法糖,方法调用如下:

instance.method(args)聽->聽(type).func(instance,聽args)

instance 就是Reciever,左边的称为Method Value;右边则是Method Expression,Go推荐使用左边形式。

Method Value是包装后的状态对象,总是与特定的对象实例关联在一起(类似闭包),

而Method Expression会被还原成普通的函数,将Receiver作为第一个显式参数,调用时需额外传递。

二者本质上没有区别,只是Method Value 看起来更像面向对象的格式,且编译器会自动进行类型转换;

Method Expression更直观,更底层,编译器不会进行类型转换,会按照实际表达意义去执行,更易于理解。

Method Expression:

type聽Person聽struct聽{
聽聽聽聽Age聽int聽
聽聽聽聽Name聽string
}

func聽(p聽Person)聽GetAge()聽int{
聽聽聽聽return聽聽p.Age
}

func聽(p聽*Person)聽SetAge(i聽int){
聽聽聽聽p.Age聽=聽聽i
}

func聽main(){
聽聽聽聽p聽:=聽Person{20,聽"Tom"}
聽聽聽聽setAge聽:=聽(*Person).SetAge聽聽聽//(\*Person)必须用括号,整体相当于func(p聽*Person)
聽聽聽聽setAge(&p,50)聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽//编译器不会进行类型转换
聽聽聽聽getAge聽:=聽Person.GetAge
聽聽聽聽fmt.Println(getAge(p))
}
输出:
50

Method Value:

func聽main(){
聽聽聽聽p聽:=聽Person{20,聽"Tom"}
聽聽聽聽setAge聽:=聽p.SetAge聽聽聽聽聽聽//编译器会自动进行类型转换
聽聽聽聽setAge(50)聽
聽聽聽聽getAge聽:=聽p.GetAge
聽聽聽聽fmt.Println(getAge())
}

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

type聽N聽int聽

func聽(n聽N)聽value(){
聽聽聽聽println(n)
}
func聽(n聽*N)聽pointer(){
聽聽聽聽println(n)
}

func聽main(){
聽聽聽聽var聽n聽*N
聽聽聽聽n.pointer()
聽聽聽聽(*N)(nil).pointer()
聽聽聽聽(*N).pointer(nil)
}

这样写程序并没有什么意义,只是希望你能理解并安全使用Method Value和Method Expression

本文出自 “博学于文,约之于礼” 博客,转载请与作者联系!


有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:100018

查看原文:golang 方法

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

853 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传