Go 语言中切片(slice)行为的问题
package main
import "fmt"
func main() {
case1()
}
func case1() {
s := make([]int, 3)
s[0] = 1
s[1] = 2
s[2] = 3
fmt.Printf("%p,%v\n", &s[0], s)
appendToSlice(s)
fmt.Println(s) // 输出 [1 2 3],未改变
}
func appendToSlice(s []int) {
s[0] = 100
fmt.Printf("%p,%v\n", &s[0], s)
s = append(s, 100) // 修改局部切片,不会影响原始切片
fmt.Printf("%p,%v\n", &s[0], s)
}
你的问题是一个非常经典的 Go 语言中切片(slice)行为的问题。让我们一步步分析为什么 s = append(s, 100) 修改了局部切片,却没有影响原始切片,同时结合你的代码输出进行解释。
代码运行结果
先运行你的代码,假设输出类似以下(具体地址会因运行环境不同而变化):
0xc0000b2000,[1 2 3] // main 中原始切片的地址和值
0xc0000b2000,[100 2 3] // appendToSlice 中 s[0] = 100 后的地址和值
0xc0000b2040,[100 2 3 100] // appendToSlice 中 append 后的地址和值
[100 2 3] // main 中最终的 s
从输出可以看到:
s[0] = 100修改了原始切片的内容(地址没变,值变为[100 2 3])。s = append(s, 100)追加了元素,但地址变了,最终没有影响main中的s。
关键问题:为什么 append 不影响原始切片?
1. 切片的底层结构
切片在 Go 中是一个结构体,包含三个字段:
- 指针:指向底层数组的起始位置。
- 长度 (len):当前切片包含的元素个数。
- 容量 (cap):底层数组的总容量。
当你将切片 s 传递给 appendToSlice 函数时,传递的是这个结构体的副本。副本中的指针仍然指向原始底层数组,因此修改 s[0] 会影响原始数据。但如果切片本身被重新赋值(比如通过 append),只会修改局部副本,不会影响调用者的切片。
2. append 的行为
append函数会向切片追加元素。- 如果当前切片的容量(cap)足够,
append会在底层数组中追加元素,并返回更新后的切片(长度增加)。 - 如果容量不足,
append会分配一个新的、更大的底层数组,将原有数据复制过去,然后追加新元素,并返回新的切片。
在你的代码中:
- 初始切片
s := []int{1, 2, 3}的长度是 3,容量也是 3(默认情况下,[]int{}的容量等于初始长度)。 - 调用
s = append(s, 100)时,长度需要从 3 增加到 4,但容量只有 3,不够用。 - 因此,
append创建了一个新的底层数组(地址变为0xc0000b2040),将[100 2 3]复制过去,再追加100,结果是[100 2 3 100]。 - 这个新切片被赋值给函数内的局部变量
s,但原始的s(在main中)没有被修改。
3. 函数参数是值传递
Go 中所有的参数传递都是值传递。对于切片来说,传递的是切片结构体的副本,而不是底层数组的副本。因此:
- 修改切片的内容(如
s[0] = 100)会影响底层数组,进而影响原始切片(因为指针指向同一个数组)。 - 修改切片本身(如
s = append(s, 100))只会影响局部变量s,不会影响调用者的s,因为这是对副本的重新赋值。
逐步分析代码
初始状态
s := []int{1, 2, 3}
// s: {ptr: 0xc0000b2000, len: 3, cap: 3}
- 底层数组:
[1 2 3]。 - 地址:
0xc0000b2000。
调用 appendToSlice(s)
- 传递
s的副本给函数,函数内的s初始状态与main中的s相同:s: {ptr: 0xc0000b2000, len: 3, cap: 3}。
执行 s[0] = 100
s[0] = 100
fmt.Printf("%p,%v\n", &s[0], s)
// 输出: 0xc0000b2000,[100 2 3]
- 修改底层数组的第一个元素,变为
[100 2 3]。 - 地址不变,因为操作的是底层数组的内容。
执行 s = append(s, 100)
s = append(s, 100)
fmt.Printf("%p,%v\n", &s[0], s)
// 输出: 0xc0000b2040,[100 2 3 100]
- 容量不足(len=3, cap=3),
append创建新数组。 - 新数组内容:
[100 2 3 100]。 - 新切片
s: {ptr: 0xc0000b2040, len: 4, cap: 4(或更大)}。 - 局部变量
s被更新为新切片,但main中的s保持不变。
返回到 main
fmt.Println(s)
// 输出: [100 2 3]
main中的s仍然是{ptr: 0xc0000b2000, len: 3, cap: 3}。- 底层数组被修改为
[100 2 3],但未追加100,因为append的结果没有传回。
如何让 append 影响原始切片?
有两种常见方法:
- 返回新切片并赋值:
func appendToSlice(s []int) []int {
s = append(s, 100)
return s
}
func main() {
s := []int{1, 2, 3}
s = appendToSlice(s)
fmt.Println(s) // 输出 [1 2 3 100]
}
- 使用指针传递切片:
func appendToSlice(s *[]int) {
*s = append(*s, 100)
}
func main() {
s := []int{1, 2, 3}
appendToSlice(&s)
fmt.Println(s) // 输出 [1 2 3 100]
}
总结
s = append(s, 100)不影响原始切片的原因是:append返回的新切片只赋值给了函数内的局部变量s。- 当容量不足时,
append创建了新的底层数组,局部s的指针指向新地址,但原始s未变。
- 修改切片内容(如
s[0] = 100)会影响原始数据,因为底层数组是共享的。 - 要让
append的结果影响原始切片,必须通过返回值或指针显式传递回去。
希望这个解释清楚地解答了你的疑问!如果还有其他问题,欢迎继续讨论。

浙公网安备 33010602011771号