【摘要】 问题描述
最初想这个问题是因为官方称 slice, map, 函数, 结构体为引用类型… 当时就在想 引用类型 是指C++中 变量引用 一样的意思么,如果一样, 那不就是slice作为函数参数传递时就是像C++中的引用传递一样么, 和Python 也是一样的传递概念么。结果,经过试验,搜索相关的信息, 我发现, 官方的说什么引用类型简直就是坑知道C++的人, 而…
问题描述
最初想这个问题是因为官方称 slice, map, 函数, 结构体为引用类型…
当时就在想 引用类型 是指C++中 变量引用 一样的意思么,如果一样, 那不就是slice作为函数参数传递时就是像C++中的引用传递一样么, 和Python 也是一样的传递概念么。结果,经过试验,搜索相关的信息, 我发现, 官方的说什么引用类型简直就是坑知道C++的人, 而且我想吐槽下 Go 的设计者在设计这门语言时是不是满脑子想着我一定要与C/C++与众不同!!!!ε=(´ο`*)))唉, 学Go给我一种奇怪的感觉,很别扭,但是要用,还是得学。下面说明下Go slice在函数中传递的问题。
Go语言中slice作为函数参数传递的真相
// 这么一个函数
package main
var slice_out = []int{1,3}
func test_func(slice_in []int) {
// 此处代码忽略, 随便是啥
// 我们讨论slice传入函数时做了什么 !!!
// 明白了这个我们就全明白了
}
func main() {
test_func(slice_out)
}
分析一下, 如上一个流程, 我们将全局变量slice_b 传入函数test_func执行到底发生了什么。首先我们得知道slice的真实结构:
// slice数据结构定义
type slice struct {
array unsafe.Pointer // 指向底层数组的指针
len int // slice里面有效元素的个数
cap int // slice最大容量 在某些场景可以理解为底层数组的长度大小, 但某些场景不能这么理解.这里不做讨论
}
开始说明, slice传入test_func中时做了什么事情,
第一步,复制
了一个slice_out 的结构体
并赋值
给slice_in 变量,
也就是说slice_in
与slice_out
并不是同一个结构体
, 它们只不过是结构体元素的值相同的不同结构体
,
也就是slice_in.array
与slice_out.array
这两个指针变量的值
是同一个底层数组的地址
!!!
是不是瞬间明白了,因为指针值一致
slice_in[i] = 123
实际上是通过slice_in.array这个指针执行类似于*(slice_in.array+i) = 123
的操作去修改了slice_in.array 这个指针指向的底层数组元素
。又因为slice_in 与 slice_out 的array指针
指向同一数组
,所以可以影响
到外面slice_out的值
,所以看起来像是C++的引用传递。但是有个操作就打破了这种假象, 那就是append函数
。
// 这里说明下append函数干了什么
var slice_out = []int{1,3}
slice_out = append(slice_out, 1)
解释下上面的步骤发生了什么
首先, 最初slice_out
的结构体的元素
len = cap = 2
,array
指向的是一个长度为2的数组
append函数在执行时会判断当前指向的数组容量
(也即是cap的值), 如果数组容量已经满了
,那么我们想往里面再append元素就需要进行扩容
(也就是申请一个更大的数组
,并将slice_out.array
这个指针变量的值
改为新生成的数组的地址
, 同时,len和cap两个元素的值都会更新
), 这就是为什么我们要用=
符号为slice_out重新赋值(Python程序员是不是感觉非常别扭,一定是这样。。。顺便从C/C++的角度吐槽下, 我靠, 每次append都得对slice_out的结构体内部的值重新赋值,这也太不迅速了不是).
这里我们分析下append是怎么打破上面看起来像引用的假象的
// 假如函数内部是进行的append操作
func test_func(slice_in []int) {
slice_in = append(slice_in, 1)
}
假如我们函数内部有如上的操作, 你就会发现 slice_out 并未随着slice_in的变化而变化, 至于原因, 就是因为Go中就只有值传递, 没有C++中的引用传递! ! !
一定要记住这一点, 知道这一点, 疑惑瞬间解开
再吐槽下, Go说slice是引用类型这个称呼建议改一改,不然真很容易让人误解
总结slice作为形参的函数设计场景
// 非并发场景
// 第一个场景:
// 我们不需要append操作, 但需要更改slice中的值
func test_func(slice_in []int) {} // 直接值传递即可
// 第二个场景:
// 我想在函数里对slice为所欲为
// ε=(´ο`*)))唉
func test_func(slice_out_ptr *[]int) {} // 请传递slice的指针并且对指针指向的slice为所欲为
// 或者来慢一点的或者说效率低一些的
func test_func(slice_in []int) []int {
// 这里是对slice_in为所欲为的代码 // 返回 slice_in 换掉 slice_out内部的值!!!
return slice_in
}
// 函数外部则这么干
slice_out = test_func(slice_out)
// 多赋值过程 且 slice 拷贝过程需要拷贝 24bytes 而指针只要 8Bytes还不用赋值
补充
记住Go只有值传递哈!!!!这是重点,就如同Python里面万物皆对象!!!
C/C++ 嗯~~~,我啥操作都有,想秀你就来
文章来源: blog.csdn.net,作者:nohysiwe,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/qq_31445217/article/details/116263377