Golang 什么时候使用指针(Pointer
)?什么时候使用值(Value
)?对于go
开发者来说是一件头疼的事情, 而且这个问题似乎没有绝对的答案,那是否代表我们可以随意使用呢?答案当然是否定的。本文我将试图总结什么场景使用指针更合理。 在开始阅读前,建议读者先能够清晰理解 Golang 指针、类型和值等概念。
本文并不是标准更不是唯一答案,而是自己根据使用经验和社区的一些讨论而总结的实践
有下几种情形,我们是否需要考虑使用指针:
type SliceHeader struct { Data uintptr Len int Cap int }
结构体定义的字段
方法中接受者
函数传参
函数和方法返回值
这里我先给出使用一般准则,后面详细介绍不同场景的细节。
方法通常使用指针作为接受者, 官方文档的建议是:如果犹豫,请使用指针;
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 类型,通常也不需要使用指针。
对于slices you'll reslice
(更改其起始元素位置/长度/容量),如内置函数func append(slice []Type, elems ...Type) []Type
, 接受一个切片,返回新的切片。返回一个新数组会让调用者更清晰地理解函数的语义。
对于以下场景,使用指针是必须的:
如果结构体中包含sync.Mutex
获取类似其他同步字段时,由于这类字段类型是禁止拷贝的,所以无论其方法的接受者, 还是其作为参数和返回值都应该使用指针:
type FIFO struct { lock sync.RWMutex cond sync.Cond items map[string]interface{} queue []string populated bool initialPopulationCount int keyFunc KeyFunc closed bool } // Close the queue. func (f *FIFO) Close() { f.lock.Lock() defer f.lock.Unlock() f.closed = true f.cond.Broadcast() } // NewFIFO returns a Store which can be used to queue up items to // process. func NewFIFO(keyFunc KeyFunc) *FIFO { f := &FIFO{ items: map[string]interface{}{}, queue: []string{}, keyFunc: keyFunc, } f.cond.L = &f.lock return f }
结构体中,除了需要考虑是否内存的占用之外,还需要考虑结构体的用途,一般主要分为工具结构体
和资源结构体
, 资源结构体
很容易理解,主要包括VO,DAO,Entity
等,这类结构体一般用于模块或分层之间的通信。对于这类结构体, 如用于序列化的结构体,根据序列化协议和库可能有所区别,如Golang
默认的Json
序列化协议对于是否显示字段, 定义为指针可以可好解决。下面是kubernetes IngressSpec
的定义。
// IngressSpec describes the Ingress the user wishes to exist. type IngressSpec struct { IngressClassName *string `json:"ingressClassName,omitempty" protobuf:"bytes,4,opt,name=ingressClassName"` Backend *IngressBackend `json:"backend,omitempty" protobuf:"bytes,1,opt,name=backend"` TLS []IngressTLS `json:"tls,omitempty" protobuf:"bytes,2,rep,name=tls"` Rules []IngressRule `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"` }
我们可以看到字段IngressClassName
定义为指针类型,就是为了序列化时更方便处理。 工具结构体
通常指非资源结构体
,主要是controller,config,factory
和自定义数据结构类型, 这些结构体往往更需要考虑内存占用。
使用指针并不是总能提升性能。使用指针可以避免值拷贝,减少栈内存的占用,由于堆内存的分配会导致GC
频繁地执行,从而降低性能, 而值传递则不会。我们通过下面的实例Demo验证。
以下两个函数分别通过值拷贝和指针共享结构体:
分别进行进行基准测试:
执行基准测试,benchstat
工具需要下载,链接为perf
:
可以看出,这时候通过值传递的方式执行更快,内存占用也更少。关于如何分析Golang
程序和代码段性能, 后续我会总结一篇博客多带带介绍。
本篇博文,简单介绍怎样如何使用指针更合理,其实很多场景都没有标准答案,更多的是性能
和语义
两者的权衡。 掌握Golang
中类型,值,指针类型等基础概念才能优雅地使用指针。遇到疑惑的参考标准库中的实现和借鉴一些成熟的项目中的实践;
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/127929.html
摘要:小白前端一枚,最近在研究,记录自己学习过程中的一些笔记,以及自己的理解。此外,结构体也支持嵌套。在函数声明时,在函数名前放上一个变量,这个变量称为方法的接收器,一般是结构体类型的。 小白前端一枚,最近在研究golang,记录自己学习过程中的一些笔记,以及自己的理解。 go中包的依赖管理 go中的切片 byte 和 string go中的Map go中的struct结构体 go中的方...
摘要:和都是目前在各自领域最流行的开发语言之一。在机器学习数据分析领域成为必学语言。 showImg(https://segmentfault.com/img/remote/1460000019167290); Golang和Python都是目前在各自领域最流行的开发语言之一。 Golang其高效而又友好的语法,赢得了很多后端开发人员的青睐,最适用于高并发网络编程的语言之一。 Python不...
摘要:在机器学习数据分析领域成为必学语言。不定长参数,支持不定长参数,用定义参数名,调用时多个参数将作为一个元祖传递到函数内返回函数结果。showImg(https://user-gold-cdn.xitu.io/2019/5/13/16ab0b937e7329d4); Golang和Python都是目前在各自领域最流行的开发语言之一。 Golang其高效而又友好的语法,赢得了很多后端开发人员的青...
阅读 130091·2024-02-01 10:43
阅读 758·2024-01-31 14:58
阅读 733·2024-01-31 14:54
阅读 82539·2024-01-29 17:11
阅读 2821·2024-01-25 14:55
阅读 1896·2023-06-02 13:36
阅读 2771·2023-05-23 10:26
阅读 835·2023-05-23 10:25