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}]
是不是变了? 至于为什么… 就交给你们写到评论里啦。
总结
- 切片的传递为引用传递。引用指的是切片中的元素,而非切片本身。
- 值类型的切片传递,会保持元素的修改,影响原切片;切片元素的增删,不会影响原切片
- 指针类型的切片传递,所有操作均会影响原切片。
("%p",l)
为打印切片一个元素的地址,("%p",&l)
为打印切片本身的地址。