24|方法:理解“方法”的本质
24|方法:理解“方法”的本质
讲述:Tony Bai
时长14:52大小13.57M
认识 Go 方法
方法的本质是什么?
巧解难题
小结
思考题
赞 28
提建议
精选留言(37)
- ddh2021-12-08思考题解答: 由 *field 改为 field结果正确的原因是, *field的方法的第一个参数是*field, 这个对于[]*field数组直接传入成员就可以了, 而对于[]field数组, 则是要取地址,也就是指针。 但是这个指针指的是for range 循环的局部变量的地址, 这个地址在for 循环中是不变的, 在for循环结束后这个地址就指向了最后一个元素, goroutine真正实行打印的解引用的地址是局部变量的地址, 自然只会打印最后一个元素了 field 的方法, 不涉及引用, 传参都是拷贝复制展开
作者回复: 👍
共 3 条评论42 - Calvin2021-12-08思考题,reciever是 field 值类型非 *field 指针类型,转换后的方法表达式如下: 1) field.print(*v) 2) field.print(v) 打印的都是切片的元素的值。
作者回复: 👍
共 2 条评论12 - 罗杰2021-12-08老师在我心目中就是 “Go 语言百科全书”。
作者回复: 这.... :)
11 - 每天晒白牙2022-07-15go方法的本质是一个以方法的 receiver 参数作为第一个参数的普通函数 函数是第一等公民,那大家都写函数就行了,方法存在的意义是啥呢?
作者回复: 你这个问题很好👍。 我可以将其转换为另外一个几乎等价的问题:我们知道c++的方法(成员函数)本质就是以编译器插入的一个this指针作为首个参数的普通函数。那么大家为什么不直接用c的函数,非要用面向对象的c++呢? 其实你的问题本质上是一个编程范式演进的过程。Go类型+方法(类比于c++的类+方法)和oo范式一样,是一种“封装”概念的实现,即隐藏自身状态,仅提供方法供调用者对其状态进行正确改变操作,防止其他事物对其进行错误的状态改变操作。
共 2 条评论11 - 左耳朵东2021-12-11如果 print 方法的 receiver 类型为 field: 首先,两个 for range 循环中的 go v.print() 分别等同于 go field.print(*v) 和 go field.print(v), 然后,第一个 for range 循环,用 *field 去调用 print 方法时,编译器检测到 print 方法只接受 field 值类型参数,所以自动做了隐式类型转换,转成 *v 后传入 print 方法 可以看到两个 for range 中实际传到 print 的实参都是 field 值类型而非指针类型,所以就得到了预期结果展开
作者回复: 👍
共 2 条评论10 - 进化菌2021-12-08*field 改为 field,由指针类型变成普通类型。goroutine在编译的时候就初始化了变量吧,那么指针类型的自然会随着变化而变化,普通类型被值拷贝而不会发生变化。 * 和 & 都是值得花时间学习和理解的东西,不知道老师后面会不会特别的说一下呢?
作者回复: 指针在Go中被弱化,所以我在设计大纲时没有特意为之留出章节,如果大家有这方面的想法,我和编辑老师看看是否可以在加餐中补充一下。但可能要放在后面了
共 2 条评论7 - return2021-12-08老师不仅把原理讲透,每篇还罗列了各种坑,讲的太好了。 有个疑问, data2 := []field{{"four"}, {"five"}, {"six"}} for _, v := range data2 { go (*field).print(&v) } 关于这一段, 按道理 goroutine 注册的时候 就会对参数求值, receiver也是参数, 我自己打印了一下, &v的值 确实是 &{four} &{five} &{six}, 但是 goroutine打印出来就变成了 3个six。 而且 尝试很多次后发现, 少数情况 会出现 2个six 另一个事 five。 很懵!!!展开
作者回复: 最后的少数情况的结果也正常,看goroutine调度的时机。
共 8 条评论4 - aoe2021-12-10一直以为 func 开头的就是方法,原来还分函数和方法!我对方法的理解: 1. 提供了良好的封装,receiver 限定了使用对象,方法名可以表使用达对象可以提供的行为 2. 使用起来更方便简洁,因为可以少传一个参数 3. Go 语言设计者的思维真是缜密啊,“方法声明必须与 receiver 参数的基类型在同一个包中”这个规则解决了无数可能出现的奇奇怪怪的情况 4. 可以促进包中代码功能的高内聚,因为你出了包,定义方法时会受到限制,可以及时发现:哎呀,有问题展开
作者回复: 👍
共 2 条评论4 - Roway2022-07-07*T &T T _ 这四个分别是什么意思?还有哪些基本的概念
作者回复: T泛指一个go类型 *T 是T类型的指针类型 &T{} 返回一个T类型实例的指针 _ 是go语法中的空标识符
2 - Geek_7254f22022-02-17建议老师把data1 := []*field{{"one"}, {"two"}, {"three"}}和data2 := []field{{"four"}, {"five"}, {"six"}}其中data1和date2中[]*、[]类型的区别讲一下,就好理解了。特别是还有*[]类型,这三个类型很像,很容易混淆
作者回复: 好建议👍,感谢。
共 2 条评论2 - 不说话装糕手2022-11-11 来自北京白老师您好,关于文章中“没有用到 receiver 参数,我们也可以省略 receiver 的参数名”情况,如果把方法看作是第一个参数为receiver的函数,那么这个没有形参名字的receiver类型参数,实际上是否传入了函数,并且该如何设计代码验证呢?
作者回复: 实际上当然会传入,来个例子证明一下吧。 type Foo struct { } func (Foo) M1(a int, b int) int { return a + b } func main() { m1 := Foo.M1 fmt.Printf("%T\n", m1) } 运行这个例子输出:func(main.Foo, int, int) int
1 - Untitled2022-02-10receiver 参数的基类型本身不能为指针类型或接口类型?? *T不是指针类型吗?不理解
作者回复: T是基类型,说的是T本身不能为指针类型。 type T *int func (T) M1() {} // invalid receiver type T (pointer or interface type)
1 - Geek_1621b62021-12-10我觉得第二种情况会打印三个six是因为go (*field).print(&v)中&v是不变的,在循环结束后指向six。而第一种情况go (*field).print(v)中v的值是在变化的。2
- Geralt2021-12-09*field 改为 field 之后,每次调用v.print()时v的值都是不一样的。
作者回复: 👍
共 3 条评论1 - witt2021-12-09这算不算一种解决方法,迭代 data2的时候在 for 内遮蔽 v 的值 v:=v 😀
作者回复: 应该可以。这样每个&v就是一块独立的地址👍。你可以写代码试一下。
共 2 条评论1 - Geek_1194892021-12-08*int算指针类型,但是*T不算指针类型吗?
作者回复: 约束说的:是基类型T不能为指针类型。*T是指针类型。
共 5 条评论1 - DullBird2022-12-30 来自北京由指针修改成非指针后,方法调用的时候,是拷贝入参值,不是一个指针地址,所以没问题
作者回复: ✅
- 徐小虾2022-11-14 来自辽宁为什么不是 four four four 呀
作者回复: 再刷一遍,或者把这一讲中的comment看一遍吧:)。
- laah2022-08-24 来自北京请教一个问题: go方法本质上是一个普通函数,那两者是否是等价的? 比如我直接定义一个普通函数的形式,这个函数会被识别为第一个参数对应类型的一个方法吗?我理解应该不是吧~
作者回复: 这里提到的本质是函数,是为了帮助大家理解方法(method)。 “我直接定义一个普通函数的形式,这个函数会被识别为第一个参数对应类型的一个方法吗?” - 肯定不会的。
- 菠萝吹雪—Code2022-08-18 来自辽宁思考题:由 *field 改为 field 就可以输出预期的结果了呢 原理解释: 这样for _, v := range data2 { go v.print() } 每次传递的不是局部变量的指针了,而是传递的[]field{{"four"}, {"five"}, {"six"}} 中的每一个值的拷贝,go v.print()方法其实转换后为: for _,v := rang data2 { go print(v) }展开
作者回复: 👍