Go语言中slice在函数传递中的问题

【摘要】 问题描述

最初想这个问题是因为官方称 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_inslice_out不是同一个结构体, 它们只不过是结构体元素的值相同的不同结构体,
也就是slice_in.arrayslice_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

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享