Skip to content

golang slice切片作为函数参数时的陷阱

Published: at 10:30 AM | 5 min read

直接用例子说话
例1:

func main() {
	s := make([]int, 1, 3) // 创建一个长度为1,容量为3的切片
	fmt.Printf("before: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))

	modifySlice1(s)
	fmt.Printf("after: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}

func modifySlice1(s []int) {
	s = append(s, 2)
	s[0] = 1
	fmt.Printf("modify: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}

运行结果:

before: slice addr 0xc000004480 point 0xc00000a480, val [0], len 1, cap 3
modify: slice addr 0xc000004500 point 0xc00000a480, val [1 2], len 2, cap 3
after: slice addr 0xc000004480 point 0xc00000a480, val [1], len 1, cap 3

例2,与上面代码的区别是append了三个元素

func main() {
	s := make([]int, 1, 3) // 创建一个长度为1,容量为3的切片
	fmt.Printf("before: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))

	modifySlice2(s)
	fmt.Printf("after: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}

func modifySlice2(s []int) {
	s = append(s, 2, 3, 4)
	s[0] = 1
	fmt.Printf("modify: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}

运行结果:

before: slice addr 0xc000054420 point 0xc00005c140, val [0], len 1, cap 3
modify: slice addr 0xc0000544a0 point 0xc000088030, val [1 2 3 4], len 4, cap 6
after: slice addr 0xc000054420 point 0xc00005c140, val [0], len 1, cap 3

通过上面两个例子就可以看出这样使用slice容易掉入陷阱,函数外面并不知道slice被修改了。

解决方法,遵守这两个规则:

所以正确的修改slice的方法是:

func main() {
	s := make([]int, 1, 3) // 创建一个长度为1,容量为3的切片
	fmt.Printf("before: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))

	s = modifySlice3(s)
	fmt.Printf("after3: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))

	s = modifySlice4(s)
	fmt.Printf("after4: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
}

func modifySlice3(s []int) []int {
	s = append(s, 2)
	s[0] = 1
	fmt.Printf("modify3: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
	return s
}

func modifySlice4(s []int) []int {
	s = append(s, 3, 4, 5, 6)
	s[0] = 1
	fmt.Printf("modify4: slice addr %p point %p, val %v, len %d, cap %d\n", &s, s, s, len(s), cap(s))
	return s
}

输出结果:

before: slice addr 0xc00004e420 point 0xc000054140, val [0], len 1, cap 3
modify3: slice addr 0xc00004e4a0 point 0xc000054140, val [1 2], len 2, cap 3
after3: slice addr 0xc00004e420 point 0xc000054140, val [1 2], len 2, cap 3
modify4: slice addr 0xc00004e540 point 0xc000080030, val [1 2 3 4 5 6], len 6, cap 6
after4: slice addr 0xc00004e420 point 0xc000080030, val [1 2 3 4 5 6], len 6, cap 6

这样不管底层数据有没有产生拷贝,表现在外的值都是一致的。

结论: