golang中的切片传递

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)为打印切片本身的地址。
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在Go语言切片(slice)是一个动态数组,它提供了对数组的部分或全部元素的访问和操作功能。切片的底层是通过数组实现的,但与数组相比,切片具有更强大的功能和灵活性。切片的底层实现原理是使用了一个结构体(runtime.slice)来表示切片的结构信息,包括指向底层数组的指针切片的长度和容量等信息。切片的长度表示切片实际存储的元素个数,而容量则表示切片底层数组的长度。切片可以根据需要动态扩容,当切片的长度超过了容量时,会重新分配更大的底层数组,并将原来的元素复制到新的底层数组。这个过程是由Go语言的运行时系统自动完成的,不需要程序员手动操作。使用切片作为参数传递时,性能损耗很小,因为切片作为参数是传递的是一个结构体实例。切片作为参数具有比数组更强大的功能和灵活性,因此在Go语言推荐使用切片而不是数组作为参数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Golang切片的底层实现原理](https://blog.csdn.net/m0_72560813/article/details/129231704)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值