Golang 什么时候使用指针(Pointer
)?什么时候使用值(Value
)?对于go
开发者来说是一件头疼的事情,
而且这个问题似乎没有绝对的答案,那是否代表我们可以随意使用呢?答案当然是否定的。本文我将试图总结什么场景使用指针更合理。
在开始阅读前,建议读者先能够清晰理解 Golang 指针、类型和值等概念。
本文并不是标准更不是唯一答案,而是自己根据使用经验和社区的一些讨论而总结的实践
有下几种情形,我们是否需要考虑使用指针:
- 结构体定义的字段
- 方法中接受者
- 函数传参
- 函数和方法返回值
这里我先给出使用一般准则,后面详细介绍不同场景的细节。
- 方法通常使用指针作为接受者, 官方文档的建议是:如果犹豫,请使用指针;
- Slices,maps,channels,strings,function value and interface value 这些类型内部通过指针实现,再定一个指针指向这些类型的变量是多余的;
- 当一个结构体很复杂或者需要修改结构体使用指针,其他情况使用值,因为滥用指针会出现一些不可预料的情况;
什么不需要使用指针
CodeReviewComments
中建议传输(pass
)小型结构体, 如type Point struct{ latitude,longtitude float64 }
,使用原始类型,除非需要修改它们,理由有以下几点
- 值语义可以避免歧义,因为指针类型的赋值;
- 牺牲干净的语义换取速度并不是
Golang
所推荐的做法,有时值传递性能更好,因为值传递能够避免缓存遗漏和队分配;
- 对于切片,不需要使用指针指向它,仍然可以改变其元素,这是因为切片内部是通过指针指向底层数组实现的,
标准库中
io.Reader.Read(p []byte)
函数中即通过传递值类型实现对切片 p 的修改。其实切片也可以当作是小型结构体,其定义如下, 在 64 位系统一个切片变量只占用 24 个字节。同样地,对于 map 和 channel 类型,通常也不需要使用指针。
1 | type SliceHeader struct { |
- 对于
slices you'll reslice
(更改其起始元素位置/长度/容量),如内置函数func append(slice []Type, elems ...Type) []Type
, 接受一个切片,返回新的切片。返回一个新数组会让调用者更清晰地理解函数的语义。
什么时候必须使用指针
对于以下场景,使用指针是必须的:
- 如果结构体中包含
sync.Mutex
获取类似其他同步字段时,由于这类字段类型是禁止拷贝的,所以无论其方法的接受者, 还是其作为参数和返回值都应该使用指针:
1 | type FIFO struct { |
结构中定义
结构体中,除了需要考虑是否内存的占用之外,还需要考虑结构体的用途,一般主要分为工具结构体
和资源结构体
,
资源结构体
很容易理解,主要包括VO,DAO,Entity
等,这类结构体一般用于模块或分层之间的通信。对于这类结构体,
如用于序列化的结构体,根据序列化协议和库可能有所区别,如Golang
默认的Json
序列化协议对于是否显示字段,
定义为指针可以可好解决。下面是kubernetes IngressSpec
的定义。
1 | // IngressSpec describes the Ingress the user wishes to exist. |
我们可以看到字段IngressClassName
定义为指针类型,就是为了序列化时更方便处理。
工具结构体
通常指非资源结构体
,主要是controller,config,factory
和自定义数据结构类型,
这些结构体往往更需要考虑内存占用。
性能
使用指针并不是总能提升性能。使用指针可以避免值拷贝,减少栈内存的占用,由于堆内存的分配会导致GC
频繁地执行,从而降低性能,
而值传递则不会。我们通过下面的实例Demo验证。
以下两个函数分别通过值拷贝和指针共享结构体:
分别进行进行基准测试:
执行基准测试,benchstat
工具需要下载,链接为perf
:
1 | go test ./... -bench=BenchmarkMemoryHeap -benchmem -run=^$ -count=10 > head.txt && benchstat head.txt |
测试结果:
1 | name time/op |
可以看出,这时候通过值传递的方式执行更快,内存占用也更少。关于如何分析Golang
程序和代码段性能,
后续我会总结一篇博客单独介绍。
总结
本篇博文,简单介绍怎样如何使用指针更合理,其实很多场景都没有标准答案,更多的是性能
和语义
两者的权衡。
掌握Golang
中类型,值,指针类型等基础概念才能优雅地使用指针。遇到疑惑的参考标准库中的实现和借鉴一些成熟的项目中的实践;