天天看点

Go语言:append函数源码学习及切片深度拷贝问题

调用append函数时,当原有长度加上新追加的长度如果超过容量则会新建一个数组,新旧切片会指向不同的数组;如果没有超过容量则在原有数组上追加元素,新旧切片会指向相同的数组,这时对其中一个切片的修改会同时影响到另一个切片。其伪代码在如下文件里,而实际上append会在编译时期被当成一个TOKEN直接编译成汇编代码,因此append并不是在运行时调用的一个函数。 另外,切片截取后生成的新切片与原切片始终指向同一个数组。

\src\cmd\compile\internal\gc\walk.go

// expand append(l1, l2...) to
//   init {
//     s := l1
//     n := len(s) + len(l2)
//     // Compare as uint so growslice can panic on overflow.
//     if uint(n) > uint(cap(s)) {
//       s = growslice(s, n)
//     }
//     s = s[:n]
//     memmove(&s[len(l1)], &l2[0], len(l2)*sizeof(T))
//   }
//   s
//
           

 验证append之后新旧切片是否指向同一个数组:

package main

import(
	"fmt"
)

func print(sl1 []string, sl2 []string){
	fmt.Printf("sl1 len=%d cap=%d\n", len(sl1),cap(sl1))
	fmt.Printf("sl2 len=%d cap=%d\n", len(sl2),cap(sl2))
	fmt.Printf("sl1=%v\n", sl1)
	fmt.Printf("sl2=%v\n\n", sl2)
}
func main(){
	sl1 := make([]string,3,5)
	sl1[0] = "a"
	sl1[1] = "b"
	sl1[2] = "c"
	//sl1追加上"e"之后长度小于sl1容量,无需扩容,sl1,sl2指向相同数组
	sl2 := append(sl1,"e")
	sl2[0] = "change_by_sl2_1st_time"
	print(sl1,sl2)

	//sl2追加上"h"之后长度等于sl2容量,无需扩容,sl1,sl2仍然指向相同数组
	sl2 = append(sl2,"h")
	sl2[0] = "change_by_sl2_2nd_time"
	print(sl1,sl2)

	//sl2追加上"j"之后长度超过sl2容量,需要扩容,新建数组,sl1,sl2指向不同数组
	sl2 = append(sl2,"j")
	sl2[0] = "change_by_sl2_3rd_time"
	print(sl1,sl2)

	//截取后,新旧切片指向同一个数组,但起止索引可能不同。
	//这里 sl2[0] 和 sl1[1] 指向同一个数组元素
	sl2 = sl1[1:len(sl1)]
	sl2[0] = "change_by_sl2_4th_time"
	print(sl1,sl2)
}
           

输出:

sl1 len=3 cap=5
sl2 len=4 cap=5
sl1=[change_by_sl2_1st_time b c]
sl2=[change_by_sl2_1st_time b c e]

sl1 len=3 cap=5
sl2 len=5 cap=5
sl1=[change_by_sl2_2nd_time b c]
sl2=[change_by_sl2_2nd_time b c e h]

sl1 len=3 cap=5
sl2 len=6 cap=10
sl1=[change_by_sl2_2nd_time b c]
sl2=[change_by_sl2_3rd_time b c e h j]

sl1 len=3 cap=5
sl2 len=2 cap=4
sl1=[change_by_sl2_2nd_time change_by_sl2_4th_time c]
sl2=[change_by_sl2_4th_time c]
           

参考文章:

《Where is the implementation of func append in Go?》

相关文章:

《Go语言:几种深度拷贝(deepcopy)方法的性能对比》