通过append之后得到的切片更改元素值到底对原切片有没有影响?

slice原理我们之前看过一篇文章:
https://www.jianshu.com/p/843aa553b461
问题代码,求a与b的值

package main

import (
	"fmt"
)

func main() {
    var a = make([]int, 0, 10)
	a=append(a,1,2)
	b:=append(a,3)
	b[0] = 99
	fmt.Println(a)
	fmt.Println(b)
}

答案:

[99 2]
[99 2 3]

问题来了:通过append之后得到的切片更改元素值到底对原切片有没有影响?为什么?
为了搞懂它我们测试了一下,并且加了注释方便大家深入理解。

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func deferFunc(dsl []int) {
	//1、sl通过方法名传递,所以 sl是结构体值拷贝,所以会是一个新对象,参见pointer和append原理
	//2、新对象因为拷贝了ptr指针, 所以ptr对象不会变化, 参见:reflect.SliceHeader{Data:}
	//3、dsl与sl的ptr虽然相同,但是len不同,所以sl新增的元素对dsl无影响
	fmt.Printf("%s\t pointer:%p, struct: %#v, values:%v\n", "defer_before_append", &dsl, *((*reflect.SliceHeader)(unsafe.Pointer(&dsl))), dsl)
	dsl = append(dsl, 99)
	fmt.Printf("%s\t pointer:%p, struct: %#v, values:%v\n", "defer_after_append", &dsl, *((*reflect.SliceHeader)(unsafe.Pointer(&dsl))), dsl)
}

func main() {
	//拓展一下对象go的对象打印
	//slice是一个结构体
	fmt.Println("==========slice print=============")
	s:=[]int{1}
	//打印结构体指针地址
	fmt.Printf("%p\n",&s)
	//打印结构体首元素指针方法1
	fmt.Printf("%p\n",s)
	//打印结构体首元素指针方法2
	fmt.Printf("%p\n",&s[0])
	//打印结构体值:可以印证:打印结构体首元素指针方法1
	fmt.Printf("%#v\n",*((*reflect.SliceHeader)(unsafe.Pointer(&s))))
	fmt.Println("==========================")

	var sl = make([]int, 0, 10)
	for i := 0; i < 15; i++ {
		//0、append会发生值拷贝,如果是自身对象会复用,所以地址不会变化,参见pointer
		//1、不需要扩容时 ptr对象不会变化, 参见:reflect.SliceHeader{Data:}
		//2、扩容成功时,数据会发生拷贝所以 ptr对象会变化, 参见:reflect.SliceHeader{Data:}
		sl = append(sl, i)
		fmt.Printf("%s\t pointer:%p, struct: %#v, values:%v\n", "before_defer", &sl, *((*reflect.SliceHeader)(unsafe.Pointer(&sl))), sl)
	}
	defer deferFunc(sl)
	//1、append赋值给一个新对象时,因为是值拷贝,所以会是一个新对象,参见pointer和append原理  https://blog.golang.org/go-slices-usage-and-internals
	//2、新对象因为拷贝了ptr指针, 所以ptr对象不会变化, 参见:reflect.SliceHeader{Data:}
	sl2 := append(sl, 5)
	fmt.Printf("%s\t pointer:%p, struct: %#v, values:%v\n", "new_struct", &sl2, *((*reflect.SliceHeader)(unsafe.Pointer(&sl2))), sl2)
	sl2[0] = 100
	//1、sl与sl2的ptr相同,所以sl2对ptr的操作会对sl数据产生影响,所以sl[0]=100
	//2、sl与sl2的ptr虽然相同,但是len不同,所以sl2新增的元素对sl无影响
	sl = append(sl, 4)
	fmt.Printf("%s\t pointer:%p, struct: %#v, values:%v\n", "after_defer", &sl, *((*reflect.SliceHeader)(unsafe.Pointer(&sl))), sl)
}
==========slice print=============
0xc000088020
0xc000090000
0xc000090000
reflect.SliceHeader{Data:0xc000090000, Len:1, Cap:1}
==========================
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000094000, Len:1, Cap:10}, values:[0]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000094000, Len:2, Cap:10}, values:[0 1]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000094000, Len:3, Cap:10}, values:[0 1 2]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000094000, Len:4, Cap:10}, values:[0 1 2 3]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000094000, Len:5, Cap:10}, values:[0 1 2 3 4]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000094000, Len:6, Cap:10}, values:[0 1 2 3 4 5]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000094000, Len:7, Cap:10}, values:[0 1 2 3 4 5 6]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000094000, Len:8, Cap:10}, values:[0 1 2 3 4 5 6 7]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000094000, Len:9, Cap:10}, values:[0 1 2 3 4 5 6 7 8]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000094000, Len:10, Cap:10}, values:[0 1 2 3 4 5 6 7 8 9]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000098000, Len:11, Cap:20}, values:[0 1 2 3 4 5 6 7 8 9 10]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000098000, Len:12, Cap:20}, values:[0 1 2 3 4 5 6 7 8 9 10 11]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000098000, Len:13, Cap:20}, values:[0 1 2 3 4 5 6 7 8 9 10 11 12]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000098000, Len:14, Cap:20}, values:[0 1 2 3 4 5 6 7 8 9 10 11 12 13]
before_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000098000, Len:15, Cap:20}, values:[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
new_struct	 pointer:0xc000088260, struct: reflect.SliceHeader{Data:0xc000098000, Len:16, Cap:20}, values:[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 5]
after_defer	 pointer:0xc000088060, struct: reflect.SliceHeader{Data:0xc000098000, Len:16, Cap:20}, values:[100 1 2 3 4 5 6 7 8 9 10 11 12 13 14 4]
defer_before_append	 pointer:0xc0000882c0, struct: reflect.SliceHeader{Data:0xc000098000, Len:15, Cap:20}, values:[100 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
defer_after_append	 pointer:0xc0000882c0, struct: reflect.SliceHeader{Data:0xc000098000, Len:16, Cap:20}, values:[100 1 2 3 4 5 6 7 8 9 10 11 12 13 14 99]

总结:

  • append会发生值拷贝,如果是自身对象会复用,所以地址不会变化
  • append赋值给一个新对象时,因为是值拷贝,所以会是一个新对象
  • 切片翻倍扩容时,ptr会发生数据拷贝,变成一个新指针
  • append之后的新对象因为拷贝了ptr指针, 所以ptr对象不会变化
  • append赋值前后ptr相同,但是len不同,所以赋值后的对象对元素进行修改操作后,会对原对象产生影响
  • append赋值前后的ptr虽然相同,但是len不同,所以赋值后新增的元素对原对象无影响(如果原对象不新增对象的话),或者 对应的index值会被覆盖(如果原对象新增元素的话)

你可能感兴趣的:(golang)