切片传递

golang中的切片传递

当前有一个函数和一个结构体定义

type s struct {
	A int
}

func testFunc(l []s) {
	fmt.Printf("in testFunc: %p\n",l)
}
测试 1

我们首先传递一个切片,并打印他的内存地址

func TestA(t *testing.T)  {
	xs := s{A: 10}
	xs1 := s{A: 20}
	l := []s{xs,xs1}
	fmt.Printf("%p\n",l)
	testFunc(l)
	fmt.Printf("%p\n",l)
}
=== RUN   TestA
0xc000344800
in testFunc: 0xc000344800
0xc000344800

可以看到是相同的内存地址。 注意,这里我们并没有传递切片指针,即&l,但地址确实是一样的。 所以我们暂时得出切片的传递是引用传递

测试 2

查阅文档, “%p”还有“fmt.Printf("%p\n",&l)”这样的打印语法, 我们来试试。

func testFunc(l []s) {
	fmt.Printf("in testFunc: %p\n",&l)
}

func TestA(t *testing.T)  {
	xs := s{A: 10}
	xs1 := s{A: 20}
	l := []s{xs,xs1}
	fmt.Printf("%p\n",&l)
	testFunc(l)
	fmt.Printf("%p\n",&l)
}
=== RUN   TestA
0xc000390d60
in testFunc: 0xc000390d80
0xc000390d60

可以看到,在测试TestA中的两次打印,地址是一样的,这是符合预期的。但是在testFunc函数中的打印却不一致。是我们上文判断错了吗?

测试 3

继续查阅文档,地址的打印还有另外的方式: unsafe

func testFunc(l []s) {
	fmt.Println("in testFunc: ",unsafe.Pointer(&l))
	fmt.Printf("in testFunc: %p\n",&l)
}

func TestA(t *testing.T)  {
	xs := s{A: 10}
	xs1 := s{A: 20}
	l := []s{xs,xs1}
	fmt.Println(unsafe.Pointer(&l))
	fmt.Printf("%p\n",&l)
	testFunc(l)
	fmt.Printf("%p\n",&l)
}
=== RUN   TestA
0xc00041a240
0xc00041a240
in testFunc:  0xc00041a260
in testFunc: 0xc00041a260
0xc00041a240

unsafe%p的打印结果是相同的, 是我们真的判断错了么?别着急,我们换个打印的对象。

func testFunc(l []s) {
	fmt.Println("in testFunc: ",unsafe.Pointer(&l),unsafe.Pointer(&(l[0])))
	fmt.Printf("in testFunc: %p\n",&l)
}

func TestA(t *testing.T)  {
	xs := s{A: 10}
	xs1 := s{A: 20}
	l := []s{xs,xs1}
	fmt.Println(unsafe.Pointer(&l),unsafe.Pointer(&(l[0])))
	fmt.Printf("%p\n",&l)
	testFunc(l)
	fmt.Printf("%p\n",&l)
}
=== RUN   TestA
0xc00000e2a0 0xc00003e280
0xc00000e2a0
in testFunc:  0xc00000e2c0 0xc00003e280
in testFunc: 0xc00000e2c0
0xc00000e2a0

有没有发现些什么? l[0]0xc00003e280这个地址是一致的! 注意, 我们这里并没有传递指针切片, 而切片内元素的地址却是一致的。 所以,引用传递指的是切片内元素是引用,而非切片本身。

测试 4

细心的小伙伴可能会提出疑问, 测试 1 打印的明明都是一样的,你这样不能自圆其说。 别着急, 那我们继续测试.

func testFunc(l []s) {
	fmt.Println("in testFunc: ",unsafe.Pointer(&l),unsafe.Pointer(&(l[0])),unsafe.Pointer(&(l[1])))
	fmt.Printf("in testFunc: %p\n",l)
}

func TestA(t *testing.T)  {
	xs := s{A: 10}
	xs1 := s{A: 20}
	l := []s{xs,xs1}
	fmt.Println(unsafe.Pointer(&l),unsafe.Pointer(&(l[0])),unsafe.Pointer(&(l[1])))
	fmt.Printf("%p\n",l)
	testFunc(l)
	fmt.Printf("%p\n",l)
}
=== RUN   TestA
0xc000418300 0xc000420140 0xc000420148
0xc000420140
in testFunc:  0xc000418340 0xc000420140 0xc000420148
in testFunc: 0xc000420140
0xc000420140

发现了什么? %p的结果和0位置的结果是一致的。 所以("%p",l)打印的是切片0位置元素的地址,("%p",&l)打印的是切片本身的地址。

测试 5

既然是引用传递,那么修改切片内元素的属性会发生什么?

func testFunc(l []s) {
	fmt.Println("in testFunc(begin): ",l)
	l[0].A = 10
	fmt.Println("in testFunc(after): ",l)

}

func TestA(t *testing.T)  {
	xs := s{A: 10}
	xs1 := s{A: 20}
	l := []s{xs,xs1}
	fmt.Println("begin: ",l)
	testFunc(l)
	fmt.Println("after: ",l)
}

testFunc中对0位置元素属性的修改会影响到原切片吗?

=== RUN   TestA
begin:  [{10} {20}]
in testFunc(begin):  [{10} {20}]
in testFunc(after):  [{1} {20}]
after:  [{1} {20}]

可以看到,影响到了原切片。 注意,这里我们并没有传递指针切片。原因参考测试3

测试 6

那我们向切片中添加元素会发生什么?

func testFunc(l []s) {
	fmt.Println("in testFunc(begin): ",l)
	l = append(l, s{A: 30})
	fmt.Println("in testFunc(after): ",l)

}

func TestA(t *testing.T)  {
	xs := s{A: 10}
	xs1 := s{A: 20}
	l := []s{xs,xs1}
	fmt.Println("begin: ",l)
	testFunc(l)
	fmt.Println("after: ",l)
}

testFunc中增加的元素会影响到原切片吗?

=== RUN   TestA
begin:  [{10} {20}]
in testFunc(begin):  [{10} {20}]
in testFunc(after):  [{10} {20} {30}]
after:  [{10} {20}]

可以看到,并没有影响到了原切片。 原因参考测试3

测试 7

测试6中我们向切片添加元素,并没有影响到原切片,那要怎么才能影响到原切片呢? 我们知道没有影响到原切片的原因是:引用传递指的是切片内元素是引用,而非切片本身 那么,我们传递切片指针是不是就可以了呢?

func testFunc(l *[]s) {
	fmt.Println("in testFunc(begin): ",l)
	_l := *l
	_l = append(_l, s{A: 30})
	l = &_l
	fmt.Println("in testFunc(after): ",l)
}

func TestA(t *testing.T)  {
	xs := s{A: 10}
	xs1 := s{A: 20}
	l := []s{xs,xs1}
	fmt.Println("begin: ",l)
	testFunc(&l)
	fmt.Println("after: ",l)
}

指针类型的切片是不能直接通过append添加元素的,我们通过一个中间的 _l来做桥接。

=== RUN   TestA
begin:  [{10} {20}]
in testFunc(begin):  &[{10} {20}]
in testFunc(after):  &[{10} {20} {30}]
after:  [{10} {20}]

咦? 还是没变。别着急, 我们换一种写法。

func testFunc(l *[]s) {
	fmt.Println("in testFunc(begin): ",l)
	*l = append(*l, s{A: 30})
	fmt.Println("in testFunc(after): ",l)
}

func TestA(t *testing.T)  {
	xs := s{A: 10}
	xs1 := s{A: 20}
	l := []s{xs,xs1}
	fmt.Println("begin: ",l)
	testFunc(&l)
	fmt.Println("after: ",l)
}

结果

=== RUN   TestA
begin:  [{10} {20}]
in testFunc(begin):  &[{10} {20}]
in testFunc(after):  &[{10} {20} {30}]
after:  [{10} {20} {30}]

是不是变了? 至于为什么… 就交给你们写到评论里啦。

总结

  1. 切片的传递为引用传递。引用指的是切片中的元素,而非切片本身。
  2. 值类型的切片传递,会保持元素的修改,影响原切片;切片元素的增删,不会影响原切片
  3. 指针类型的切片传递,所有操作均会影响原切片。
  4. ("%p",l)为打印切片一个元素的地址, ("%p",&l)为打印切片本身的地址。