在Go中,有多种方法可以返回其struct值或片.对于我见过的个人:
type MyStruct struct {
Val int
}
func myfunc() MyStruct {
return MyStruct{Val: 1}
}
func myfunc() *MyStruct {
return &MyStruct{}
}
func myfunc(s *MyStruct) {
s.Val = 1
}
Run Code Online (Sandbox Code Playgroud)
我理解这些之间的差异.第一个返回结构的副本,第二个返回指向函数内创建的结构值的指针,第三个期望传入现有结构并覆盖该值.
我已经看到所有这些模式都在各种环境中使用,我想知道关于这些模式的最佳实践是什么.你什么时候用哪个?例如,第一个可能适用于小结构(因为开销很小),第二个适用于较大结构.第三个是你想要非常高效的内存,因为你可以在调用之间轻松地重用一个struct实例.有什么时候使用哪种最佳做法?
同样,关于切片的相同问题:
func myfunc() []MyStruct {
return []MyStruct{ MyStruct{Val: 1} }
}
func myfunc() []*MyStruct {
return []MyStruct{ &MyStruct{Val: 1} }
}
func myfunc(s *[]MyStruct) {
*s = []MyStruct{ MyStruct{Val: 1} }
}
func myfunc(s *[]*MyStruct) {
*s = []MyStruct{ &MyStruct{Val: 1} }
}
Run Code Online (Sandbox Code Playgroud)
再说一次:这里的最佳做法是什么.我知道切片总是指针,所以返回指向切片的指针是没用的.但是,如果我返回一片struct值,一块指向结构的指针,我应该将指向切片的指针作为参数传入(Go App Engine API中使用的模式)吗?
two*_*two 359
tl;博士:
您应该经常使用指针的一种情况:
在某些情况下,您不需要指针:
代码审查指南建议通过小型结构类似type Point struct { latitude, longitude float64 },甚至可能的事情有点大,作为值,除非你调用需要的功能,能够修改他们在的地方.
对于切片,您不需要传递指针来更改数组的元素.例如,io.Reader.Read(p []byte)更改字节数p.它可以说是一种"处理像结构一样的小结构"的特殊情况,因为在内部你会传递一个叫做切片标题的小结构(参见Russ Cox(rsc)的解释).同样,您不需要指针来修改地图或在通道上进行通信.
对于切片,您将重新定义(更改开始/长度/容量),内置函数,如append接受切片值并返回一个新值.我会模仿; 它避免了别名,返回一个新的切片有助于引起人们对可能分配新数组这一事实的注意,并且它对调用者来说很熟悉.
映射,通道,字符串以及函数和接口值(如切片)是内部引用或已包含引用的结构,因此如果您只是试图避免复制基础数据,则无需将指针传递给它们.(rsc 写了一篇关于如何存储接口值的单独帖子).
flag.StringVar采用a *string.你在哪里使用指针:
考虑您的函数是否应该是您需要指针的结构的方法.人们期望x修改很多方法x,因此将修改后的结构体作为接收器可能有助于最大限度地减少意外.有指导就当接收器应该是指针.
对其非接收器参数有影响的函数应该在godoc中更清楚,或者更好的是,godoc和名称(如reader.WriteTo(writer)).
你提到接受一个指针,通过允许重用来避免分配; 为了内存重用而更改API是一种优化我会延迟,直到明确分配具有非常重要的成本,然后我会寻找一种不会强制所有用户使用棘手的API的方法:
bytes.Buffer.Reset()将对象放回空白状态的方法,就像一些stdlib类型提供的那样.不关心或无法保存分配的用户不必调用它.existingUser.LoadFromJSON(json []byte) error可以包装NewUserFromJSON(json []byte) (*User, error).同样,它推动了懒惰和捏合分配到个人呼叫者之间的选择.sync.Pool处理一些细节.如果特定分配会产生很大的内存压力,那么您确信自己知道何时不再使用alloc,并且您没有更好的优化可用,sync.Pool可以提供帮助.(CloudFlare发布了sync.Pool一篇关于回收的有用(预)博客文章.)NewFoo() *Foo有时可以避免分配append.不是惯用的; 小心翼翼地在家里试一试.最后,关于切片是否应该是指针:值的切片可能很有用,并保存分配和缓存未命中.可能有阻碍者:
append而不是让Go初始化为零值.sync.Mutex在增长底层数组时复制项目.在append指向错误位置之前得到的指针,对于大型结构,复制可能较慢,并且例如type Point struct { latitude, longitude float64 }不允许复制.在中间插入/删除并类似地移动项目.从广义上讲,如果您将所有物品放在前面并且不移动它们(例如,bytes.Replace在初始设置后不再有s),或者如果您继续移动它们但是您确定它们是好的(没有/小心使用指向项目的指针,项目足够小以便有效复制等).有时您必须考虑或衡量您的具体情况,但这是一个粗略的指导.
Mar*_*rio 34
如果可以的话(例如不需要作为引用传递的非共享资源),请使用一个值。由于以下原因:
原因 1:您将在堆中分配较少的项目。从堆栈分配/释放是立即的,但是在堆上分配/释放可能非常昂贵(分配时间+垃圾收集)。您可以在这里看到一些基本数字:http://www.masias.info/entry/201802102230_go_values_vs_references.md
原因 2:特别是如果您将返回值存储在切片中,您的内存对象将在内存中更加紧凑:循环所有项目都是连续的切片比迭代所有项目都是指向内存其他部分的指针的切片要快得多。不是因为间接步骤,而是因为缓存未命中的增加。
打破神话:典型的 x86 缓存行为 64 字节。大多数结构都比这个小。在内存中复制高速缓存行的时间与复制指针类似。
仅当代码的关键部分很慢时,我才会尝试一些微优化,并检查使用指针是否会在一定程度上提高速度,但代价是可读性和可维护性较低。
小智 10
当您想要将方法接收器用作指针时,有三个主要原因:
"首先,最重要的是,该方法是否需要修改接收器?如果是,接收器必须是指针."
"其次是对效率的考虑.如果接收器很大,例如一个大的结构,使用指针接收器会便宜得多."
"接下来是一致性.如果该类型的某些方法必须具有指针接收器,其余方法也应如此,因此无论使用何种类型,方法集都是一致的"
参考:https://golang.org/doc/faq#methods_on_values_or_pointers
编辑:另一个重要的事情是要知道您要发送到函数的实际"类型".类型可以是"值类型"或"引用类型".见下图:
即使切片和贴图充当引用,我们也可能希望将它们作为指针传递给场景,例如更改函数中切片的长度.
通常需要返回指针的情况是在构造某些有状态或可共享资源的实例时。这通常是由前缀为 的函数完成的。New
因为它们代表某事物的特定实例,并且可能需要协调某些活动,所以生成代表同一资源的重复/复制结构没有多大意义——因此返回的指针充当资源本身的句柄。
一些例子:
func NewTLSServer(handler http.Handler) *Server-- 实例化一个Web服务器用于测试func Open(name string) (*File, error)-- 返回文件访问句柄在其他情况下,返回指针只是因为结构可能太大而无法默认复制:
func NewRGBA(r Rectangle) *RGBA-- 在内存中分配图像或者,可以通过返回内部包含指针的结构的副本来避免直接返回指针,但这可能不被认为是惯用的:
| 归档时间: |
|
| 查看次数: |
56144 次 |
| 最近记录: |