起因: 今天在用回溯做leetcode题的时候,用切片作为函数的传参。结果输出的结果和我预想的不符,于是乎想研究一下Slice里面作为函数传参的时候是怎样的。文章主要分为两部分:
- 使用数组作为传参会产生的陷阱
- 使用Slice作为传参会产生的陷阱
一、使用数组作为传参会产生的陷阱
首先先看下面代码段:
func foo(a [2]int) {
a[0] = 10
}
func main() {
a := [2]int{1, 2}
foo(a)
fmt.Println(a)
}
输出的结果竟然是[1,2]
,数组a
并没发生改变
原因:
-
在go语言中,数组是一个值类型。并且不同长度的数组属于不同的类型。例如[3]int和[10]int属于不同的类型
-
当值类型作为参数传递时,参数是该值的一个拷贝,因此更改拷贝值并不会更改原值。
解决方案:
1、使用数组指针进行传值
func foo(a *[2]int) {
(*a)[0] = 10
}
func main() {
a := [2]int{1, 2}
foo(&a)
fmt.Println(a)
}
2、使用切片进行传值
func foo(a []int) {
a[0] = 10
}
func main() {
a := []int{1, 2}
foo(a)
fmt.Println(a)
}
重点介绍一下为什么使用切片传参就可以:
1、切片是由三个值构成的:
-
*ptr
:指向底层数组的指针 -
len
:确定切片的长度 -
cap
:确定切片的容量
2、当切片作为传参的时候,相当于重新拷贝了一个新的切片,新切片拷贝的原切片的三个值。所以当我们对新切片的值进行修改时候,相当于修改了底层数组中的值,因此会影响到原切片
二、使用Slice作为传参会产生的陷阱
首先先看下面代码:
func foo(a []int) {
a = append(a, 1, 2, 3, 4)
a[0] = 10
}
func main() {
a := []int{1, 2}
foo(a)
fmt.Println(a)
}
输出的结果仍然是[1,2]
,数组a
并没发生改变
原因:
- 传参时虽然拷贝的新的切片,但是因为新切片的长度发生了改变。导致原切片的长度不会发生改变(长度不同的切片对应的类型也不同)
- 其次,新拷贝的切片长度不足以放置4个元素,因此会发生扩容(会产生新的空间)。扩容之后的底层数组就不是原来的底层数组了,所以修改新切片的值不会影响到原切片
解决方案:
1、切片也使用指针方式传递参数
func foo(a *[]int) {
*a = append(*a, 1, 2, 3, 4)
(*a)[0] = 10
}
func main() {
a := []int{1, 2}
foo(&a)
fmt.Println(a)
}
2、函数设置切片为返回值
func foo(a []int) []int {
a = append(a, 1, 2, 3, 4)
a[0] = 10
return a
}
func main() {
a := []int{1, 2}
a = foo(a)
fmt.Println(a)
}
3、函数内创建一个零时切片再传递到函数外
result := [][]int{}
func foo(a []int){
a = append(a, 1, 2, 3, 4)
a[0] = 10
temp := make([]int, len(a))
copy(temp, a)
result = append(result,temp)
}
func main() {
a := []int{1, 2}
foo(a)
fmt.Println(result[0])
}