我不清楚在哪种情况下我会使用值接收器而不是总是使用指针接收器。
从文档中总结:
type T struct {
a int
}
func (tv T) Mv(a int) int { return 0 } // value receiver
func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver
docs还说“对于基本类型,切片和小型结构体等类型,值接收器非常便宜,因此除非方法的语义需要指针,否则值接收器是高效和清晰的。
第一点他们说值接收器“非常便宜”,但问题是它是否比指针接收器便宜。所以我做了一个小的基准测试(code on gist),它向我展示了,即使对于只有一个字符串字段的结构体,指针接收器也更快。以下是结果:
// Struct one empty string property
BenchmarkChangePointerReceiver 2000000000 0.36 ns/op
BenchmarkChangeItValueReceiver 500000000 3.62 ns/op
// Struct one zero int property
BenchmarkChangePointerReceiver 2000000000 0.36 ns/op
BenchmarkChangeItValueReceiver 2000000000 0.36 ns/op
- (编辑:请注意,第二点在较新的go版本中无效,请参阅评论。
第二点文档说,一个值接收器是“高效和清晰”的,这更像是一个品味问题,不是吗?就我个人而言,我更喜欢一致性,在任何地方都使用相同的东西。什么意义上的效率?从性能上看,指针似乎总是更有效。少数具有一个int属性的测试运行显示值接收器的最小优势(范围为0.01-0.1 ns/op)
谁能告诉我一个例子,值接收器显然比指针接收器更有意义?还是我在基准测试中做错了什么?我是否忽略了其他因素?
3条答案
按热度按时间qzwqbdag1#
请注意,FAQ中确实提到了一致性
其次是一致性。如果类型的某些方法必须有指针接收器,那么其余的也应该有,所以无论类型如何使用,方法集都是一致的。有关详细信息,请参见方法集部分。
如in this thread所述:
关于指针的规则接收器的值的一个特点是值方法可以在指针和值上调用,但指针方法只能在指针上调用
这是不正确的,正如Sart Simha所评论的那样。
值接收器和指针接收器方法都可以在正确类型的指针或非指针上调用。
无论调用什么方法,在方法体中,当使用值接收器时,接收器的标识符引用by-copy值,当使用指针接收器时,接收器的标识符引用指针:example。
现在:
有人能告诉我一个例子,值接收器比指针接收器更有意义吗?
Code Review注解可以帮助:
sync.Mutex
或类似同步字段的结构体,则接收器必须是一个指针以避免复制。*如果接收器是一个小数组或结构体,它自然是一个值类型(例如,类似
time.Time
类型),没有可变字段和指针,或者只是一个简单的基本类型,如int或string,值接收器是有意义的。值接收器可以减少可以生成的垃圾量;如果一个值被传递给一个值方法,那么可以使用一个栈上的副本来代替在堆上分配。(编译器试图聪明地避免这种分配,但它并不总是成功的。)不要因为这个原因而在没有分析的情况下选择一个值接收器类型。
注意“如果接收器是一个切片,并且方法不重新切片或重新分配切片,则不要使用指向它的指针。
该语句建议,如果您有一个重新切片或重新分配切片的方法,那么您应该使用指针接收器。
换句话说,如果在方法中修改切片,比如追加元素或更改切片的长度/容量,建议使用指针接收器。
在为切片类型实现删除和插入方法的情况下,您可能会修改切片(更改其长度,添加或删除元素)。因此,您应该为这些方法使用指针接收器。
示例(playground):
在本例中,
Insert
和Delete
方法通过添加和删除元素来修改切片。因此,使用指针接收器来确保修改在方法外部可见。
例如,在
net/http/server.go#Write()
中可以找到粗体部分:注意:irbull在评论中指出了接口方法的警告:
按照接收器类型应该一致的建议,如果你有一个指针接收器,那么你的
(p *type) String() string
方法也应该使用指针接收器。但是这并没有实现
Stringer
接口,除非你的API的调用者也使用指向你的类型的指针,这可能是你的API的可用性问题。我不知道是否一致性击败可用性在这里。
指出:
你可以混合和匹配带有值接收器的方法和带有指针接收器的方法,并将它们与包含值和指针的变量一起使用,而不用担心哪个是哪个。
两者都可以工作,语法是相同的。
然而,如果需要具有指针接收器的方法来满足接口,则只有指针可分配给接口-值将无效。
通过接口调用值接收方方法总是会创建值的额外副本。
接口值基本上是指针,而你的值接收器方法需要值;因此每次调用都需要Go创建一个新的值副本,用它调用你的方法,然后扔掉这个值。
只要使用值接收器方法并通过接口值调用它们,就没有办法避免这种情况;这是围棋的基本要求
不可寻址值的概念,与可寻址值相反。详细的技术版本在Go语言规范中的Address操作符中,但总结的版本是大多数匿名值都是不可寻址的(一个很大的例外是复合文字)
bxgwgixi2#
另外添加到@VonC伟大的,信息丰富的答案。
我很惊讶,没有人真正提到维护成本,一旦项目变得更大,旧的开发人员离开,新的来了。Go是一门年轻的语言。
一般来说,我尽量避免指针时,我可以,但他们确实有自己的地方和美丽。
我在以下情况下使用指针:
例如:
避免使用指针的原因:
*no NIL if conditions(NIL可以传递到需要指针的地方)
我的经验法则是,尽可能多地编写封装的方法,例如:
更新:
这个问题启发了我对这个主题进行更多的研究,并就此写了一篇博客文章https://medium.com/gophersland/gopher-vs-object-oriented-golang-4fa62b88c701
ie3xauqp3#
这是一个语义学的问题。假设你写了一个函数,以两个数字作为参数。你不想突然发现这些数字中的任何一个被调用函数改变了。如果你把它们作为指针传递,这是可能的。很多事情应该表现得像数字一样。像点,2D矢量,日期,矩形,圆形等。这些东西没有身份。两个在同一位置且半径相同的圆不应相互区分。它们是值类型。
但是,像数据库连接或文件句柄这样的东西,GUI中的按钮是身份重要的东西。在这些情况下,你需要一个指向对象的指针。
当某个东西本质上是一个值类型时,比如矩形或点,最好能够在不使用指针的情况下传递它们。为什么?因为这意味着你肯定会避免改变对象。它向代码的读者阐明了语义和意图。很明显,接收对象的函数不能也不会改变对象。