原理
Go语言中的切片事实上是一个结构体,其运行时结构如下:
type slice struct { array unsafe.Pointer len int cap int }
这一点非常重要,这也就意味着,将切片作为函数参数时,其传递机制与结构体传递机制一样,都是值传递,也即传递的是原切片的拷贝。
另外一个非常重要的点就是,切片结构体中的array是一个指针,意味着array的值是底层数组的地址,通过函数传参后,这个值依然没有改变。
因此可以看到,当把切片作为函数参数传递时,在函数中对切片进行某些修改操作,会影响到函数外的原始切片。
看个栗子
先看一段代码:
func main() { var arr = []int{1, 2, 3, 4, 5} fmt.Printf("arr pointer: %p\n", &arr) test(arr) fmt.Printf("arr: %v\n", arr) } func test(data []int) { fmt.Printf("data pointer: %p\n", &data) data[0] = 100 }
问题:
请问上述代码中arr pointer:
与data pointer:
的输出是相同的吗?
请问上述代码中arr:
打印的结果是多少?也即函数中对切片的修改会不会影响到函数外的原切片?
答案:
输出中arr pointer:
与data pointer:
的值不相同,这很好理解,这是因为切片是值传递,传递给函数的时候,重新创建了一个新的切片结构体,那么二者的地址当然不一样了。
打印的结果是arr: [100 2 3 4 5]
,也就证明了对切片的修改会影响到原切片。也正是这个原因导致很多人误以为切片作为函数参数时是引用传递,其实这种理解是错误的。
再看个栗子
这个栗子与上面的栗子唯一不同的是,在函数体中对切片增加了一个append操作
func main() { var arr = []int{1, 2, 3, 4, 5} fmt.Printf("arr pointer: %p\n", &arr) test(arr) fmt.Printf("arr: %v\n", arr) } func test(data []int) { fmt.Printf("data pointer: %p\n", &data) data = append(data, 100) data[0] = 100 }
那么问题来了:
此时arr:
打印的结果是什么?
答案
:打印的是arr: [1 2 3 4 5]
,如果理解了上面阐述的原理,那么也很好理解这个答案。这是因为通过append函数扩充一个元素时,由于原切片的容量不足,导致底层数组需要扩容,而扩容后的底层数组的地址改变了,因此函数中的data的结构体中的array值改变了,而后的data[0] = 100
语句操作的已经是新的底层数组了,因此也就与函数外的原切片中指向的底层数组不是同一个了。
最后
最后,我想说的是,如果你确定编写的函数需要将切片的修改影响到函数外的原始切片,那么你的函数参数应该使用指针。
希望现在你可以清楚地回答下面的问题了
// 代码一 func main() { var arr = []int{1, 2, 3, 4, 5} fmt.Printf("arr pointer: %p\n", &arr) test(&arr) fmt.Printf("arr: %v\n", arr) } func test(data *[]int) { fmt.Printf("data pointer: %p\n", data) (*data)[0] = 100 }
// 代码二 func main() { var arr = []int{1, 2, 3, 4, 5} fmt.Printf("arr pointer: %p\n", &arr) test(&arr) fmt.Printf("arr: %v\n", arr) } func test(data *[]int) { fmt.Printf("data pointer: %p\n", data) *data = append(*data, 100) (*data)[0] = 100 }
问题:
请问上述代码中arr pointer:
与data pointer:
的输出是相同的吗?
请问上述代码中arr:
打印的结果是多少?
彩蛋
附加一个小问题:
var arr = []int{1, 2, 3, 4, 5} fmt.Printf("arr pointer: %p\n", arr) fmt.Printf("arr pointer: %p\n", &arr)
上面??代码中两行输出语句的区别是什么?
答案
:对于切片来说,使用%p
格式化输出时,如果前面不加取地址符,那么打印的是切片中第一个元素的地址;如果前面加上取地址符&,那么打印的是该切片的地址。
原文:https://juejin.cn/post/7096054655511658526