Everything in Go, ever, always, is passed by value
与 C 语言家族的所有语言一样,Go 语言中的所有内容都是通过值传递的。也就是说,函数总是获得被传递内容的副本,就像有一条赋值语句将值赋给参数一样。例如,向函数传递一个 int 值会得到 int 的副本,传递一个指针值会得到指针的副本,但不会得到指针指向的数据。(关于这对方法接收器的影响,请参阅后面章节的讨论)。
映射和切片值的行为类似指针:它们是描述符,包含指向底层映射或切片数据的指针。复制映射或切片值并不会复制其指向的数据。复制接口值则是复制接口值中存储的内容。如果接口值包含一个结构体,复制接口值就会复制该结构体。如果接口值保存的是指针,复制接口值就会复制指针,但不会复制指针指向的数据。
参考代码测试结果: colobu.com/2016/10/28/...
Slices vs pointers to slices (如何选择)
从逻辑上讲,Slices 是一个由三个字段组成的结构体:
- 一个指向片段后备数组的指针(该数组实际包含片段的元素)和两个 ints:
- Slices 的容量
- Slices 的长度
在传递和分配 Slices 值时,这些结构值会被复制:一个指针和两个整数。 正如你所看到的,当你传递一个 Slices 值时,后备数组不会被复制,只有一个指向它的指针。
也就是说,在 Slices 值传值过程的设计上,本身就是很轻量的,只复制了一个指针(的值),并没有复制全部数据。
现在,让我们考虑一下什么时候需要使用普通 Slices 或指向 Slices 的指针。
如果您担心性能(复制内存所需的内存分配和/或CPU周期),那么这些担忧是没有根据的: 在传递切片时复制三个整数在当今的硬件上非常便宜。使用指向 slice 的指针会使复制速度加快一点点------复制单个整数而不是三个整数------但是这些节省很容易被两个事实所抵消:
-
几乎可以肯定,slice 的值最终会在堆上分配,这样编译器就可以确保它的值在跨越函数调用边界时仍然存在------这样您将为使用内存管理器付费,而垃圾收集器将承担更多的工作。
-
Using a level of indirection reduces data locality: 访问RAM的速度很慢,所以cpu有缓存,可以在读取数据的地址后面预取数据。如果控制流立即在另一个位置读取内存,那么预取的数据将被丢弃:缓存垃圾。
那么有没有一种情况下你想要一个指向切片的指针?是的。例如, built-in append
函数被定义为:
go
func append(*[]T, T...)
而不是
scss
func append([]T, T...) []T
(注意:这里的"T"实际上意味着"任何类型",因为"append"不是一个库函数,不能在普通 Go 中合理地定义;这是一种伪代码。)
也就是说,它可以接受指向切片的指针,并可能 替换 指针指向的切片,因此您可以将其定义为为 append(&slice, element)
而不是 slice = append(slice, element)
。
当你需要直接操作增删一个 slice ,并且用操作后的 slice,替换原来的 slice,这个时候可以像 append 一样,最好是使用指针。
其他的情况,不用指针。
但老实说,在我处理过的真实Go项目中,我记得使用指针指向片的唯一情况是为了池化大量重用的片,以节省内存重新分配。那个唯一的例子只是因为 sync.Pool
keeping elements of type interface{}
这在使用指针时可能更有效。