问题
- 第一段代码
go
func main() {
s := make([]int, 0, 5)
s = append(s, 1)
fmt.Println("append slice:", s, len(s), cap(s))
}
这段代码中为长度为0
,容量为5
的切片append了一个1
,切片未发生扩容,所以代码运行结果为切片第一个元素为1
,长度为1
,容量为5
的切片。
运行结果:
go
append slice: [1] 1 5
- 第二段代码
go
func main() {
s := make([]int, 0, 5)
s = append(s, 1)
fmt.Println("append slice:", s, len(s), cap(s))
appendFunc(s)
fmt.Println("appendFuncMain slice:", s, len(s), cap(s))
}
func appendFunc(s []int) {
s = append(s, 1, 2, 3)
}
这段代码添加了一个appendFunc
函数,函数功能为切片增加1,2,3
三个元素。在主函数中调用了appendFunc
函数并把切片s
作为参数传递。
运行结果:
go
append slice: [1] 1 5
appendFuncMain slice: [1] 1 5
为什么切片s
的长度和值都没有变呢?
- 第三段代码
go
func main() {
s := make([]int, 0, 5)
s = append(s, 1)
fmt.Println("append slice:", s, len(s), cap(s))
appendFunc(s)
fmt.Println("appendFuncMain slice:", s, len(s), cap(s))
}
func appendFunc(s []int) {
s = append(s, 1, 2, 3)
fmt.Println("appendFunc slice:", s, len(s), cap(s))
}
这段代码新加了一个输出在appendFunc
函数中,我们来看运行结果:
go
append slice: [1] 1 5
appendFunc slice: [1 1 2 3] 4 5
appendFuncMain slice: [1] 1 5
可以看到,在appendFunc
函数中打印传递的切片s
是正常变化了的,这是怎么回事呢?切片不是引用类型吗?
解答
切片在go语言中属于引用类型,但是在go中都是值传递(拷贝副本),那切片是传递的什么呢?
切片的底层其实是一个结构体类型,在src/runtime/slice.go
下可以看到。
go
type slice struct {
array unsafe.Pointer
len int
cap int
}
array
是指向底层数组,len
为切片长度,cap
为切片容量。在函数传递中就是把这个结构体拷贝了一份副本进行传递。

所以上面问题就得到了解答,因为拷贝的是slice结构体的副本,而append
实际上是增加了副本的len
,而传递的原slice结构体是没有发生改变的。
我们可以加个指针继续验证一下:
go
func main() {
s := make([]int, 0, 5)
s = append(s, 1)
fmt.Println("append slice:", s, len(s), cap(s))
appendFunc(&s)
fmt.Println("appendFuncMain slice:", s, len(s), cap(s))
}
func appendFunc(s *[]int) {
*s = append(*s, 1, 2, 3, 5, 6, 7, 5)
fmt.Println("appendFunc slice:", s, len(*s), cap(*s))
}
输出结果:
go
append slice: [1] 1 5
appendFunc slice: &[1 1 2 3 5 6 7 5] 8 10
appendFuncMain slice: [1 1 2 3 5 6 7 5] 8 10
加上指针后,函数传递就是slice
切片指针地址值,作append增加或修改操作都是对一个slice
对象修改的,所以在不同作用域下修改的对象都是一致的。