go receive 与方法本质
引用:https://zhuanlan.zhihu.com/p/570714405
GO语言虽然不支持面向对像语法元素,比如类,对像,继承等,但Go语言也有方法.和函数相比,Go语言中的方法在声明形式上仅仅多了一个参数,称之为receiver参数.
receiver参数是方法与类型之间的纽带.
方法声明如下:
func (receiver T/*T) MethodName(参数列表)(返回值列表){
...
}
上面方法声明中的T称为receiver 的基类型.通过receiver,上述方法被绑定到类型T上,换句话说,上述方法是类型T的一个方法,通过类型T或*T的实实例调用该方法.
举个栗子:
var t T
t.MethodName(参数列表)
var pt *T = &t
pt.MethodName(参数列表)
方法的特点:
1 方法名的首字母是否大写决定了该方法是不是导出方法.
2 方法定义要与类型定义放在同一个包内.不能为原生类型,如int,float64,map等添加方法,只能为自定义类型定义方法
举个栗子:
//错误
func (i int ) String() string{
return fmt.Sprintf("%d",i)
}
//正确
type MyInt int
func (i MyInt) String() string{
return fmt.Sprintf("%d",int(i))
}
不能模跨Go包为其它包内的自定义类型定义方法
3 每个方法只能有一个receiver参数,不支持多receiver参数列表或变长receiver参数.一个方法只能绑定一个基类型,Go语言不支持同时绑定多个类型的方法.
4 receiver 参数的基类型本身不能是指针类型或接口类型
举个栗子
type MyInt *int //编译错误
func (r MyInt) String() string{
return fmt.Sprintf("%d", *(*int)(r))
}
type MyReader io.Reader
//错误
func (r MyReader) Read(p []byte) (int,error){
...
}
1. 方法的本质
type T struct{
a int
}
func (t T) Get() int{
return t.a
}
func (t *T) Set() int{
t.a = a
return t.a
}
将receiver作为第一个参数传入方法,上面的方法可以等价转换为下面的普通函数:
func Get(t T) int{
return t.a
}
func Set(t *T, a int){
t.a =a
return t.a
}
转换后的函数是方法的原型,只不过在GO语言中,这种等价转换是由go编译器在编译和生成代码时自动完成的,Go语言规范中提供了一个新概念,更充分理解等价转换.
一般使用方法如下:
var t T
t.Get()
t.Set(1)
等价替换
var t T
T.Get(t)
(*T).Set(&t,1)
这种直接以类型名T调用方法的表达方式称为方法表达式. 这也是Go方法的本质,一个以方法所绑定类型实例为第一个参数的普通函数.
2. 选择recever的类型
了解:类型为T时,选择值类型的receiver ,类型为*T时,选择指针类型的receiver
package main
type T struct{
a int
}
func (t T) M1(){
}
func (t *T) M2(){
t.a = 11
}
func main(){
var t T
t.M1()
t.M2()
var pt = &T{}
pt.M1()
pt.M2()
}
以上程序无法t 调用*T的M2方法,还是*T类型实例pt调用T的M1方法都是可以的.
由此得出receiver类型选用:
1 如果要对类型实例进行修改,为recerver选择*T类型
2如果没有对类型实例修改的需求,那么为receiver选择T类型或*T类型均可.
3. 基于对GO方法本质的代码理解
分析代码:
package main
import (
"fmt"
"time"
)
type f struct {
name string
}
func (p *f) print() {
fmt.Println(p.name)
}
func main() {
d1 := []*f{{"a"}, {"b"}, {"c"}}
for _, v := range d1 {
go v.print()
}
d2 := []f{{"d"}, {"e"}, {"f"}}
for _, v := range d2 {
go v.print()
}
time.Sleep(3 * time.Second)
}
运行结果:
$ go run main.go
a
b
c
f
f
f
为什么d2输出结果是3个 "f",而不是"d","e","f"?
根据方法的本质[一个以方法所绑定类型实例为第一个参数的普通函数,对这个程序做个等价变换]
func main() {
d1 := []*f{{"a"}, {"b"}, {"c"}}
for _, v := range d1 {
go (*f).print(v)
}
d2 := []f{{"d"}, {"e"}, {"f"}}
for _, v := range d2 {
go (*f).print(&v)
}
time.Sleep(3 * time.Second)
}
1 迭代d1 时 由于d1中的元素类型是f指针,因此赋值后v就是元素地址
2 迭代d2时,由于d2中的元素类型是非指针,需要将其地址后再传入.这样每次传入的&v实际上是变量v的地址,for range 循环变量复用,v是元素f的副本.至此拨开乌云见天日
原程序按期望输出 将f类型的方法的 receiver类型由 *f 改为 f
参考:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
2023-05-09 海康 大华 公司上班比较