Golang Go语言中 slice 的一个疑问

发布于 1周前 作者 wuwangju 来自 Go语言

Golang Go语言中 slice 的一个疑问

package main

import ( “fmt” )

func test(a []int) { a = append(a, 4) }

func main() { s := make([]int, 3, 4) s[0] = 1 s[1] = 2 s[2] = 3 fmt.Println(s)//1, 2, 3 test(s) fmt.Println(s)//1, 2, 3 }

我认为第二个 println 打印出来的应该是 1, 2, 3, 4, 但是实际打印的是 1, 2, 3


更多关于Golang Go语言中 slice 的一个疑问的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

22 回复

a=append(a, 4)执行前后的 a 不是同一个 slice, 参数 a 传的是引用的值,不改变原来的 s 变量

更多关于Golang Go语言中 slice 的一个疑问的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


cap(a) 是 4,实际 len(a) 是 3,为啥 append 前后的 a 不是同一个 slice ?

老一套 指针问题

#2 外面的 s 的 len 没有变成 4

s 这个 struct 没有被改,里面存了 len 的数据,a 是另外一个 struct

type slice struct{
array unsafe.Pointer
len int
cap int
}
当你用 slice 作为参数时,传进去的是这个结构体的 copy
所以改变 slice 中的值是有效的,涉及到长度 /容量的在副本上进行 是无效的

你把 test 函数改成这样

func test(a *[]int) { *a = append(*a, 4) }

然后这样调用 test(&s)

就可以了。

这是值传递,很多语言都是值传递吧

说到点子上的人不多。除了 auta,外面的 s.len 没变,但是 s[4]的内容实际是有了,只是 length 仍然是 3.
具体可以看看 playground: https://play.golang.org/p/JjP8z4xQ1Qa

底层用的同一块内存,但是 s 与 a 的里记录的长度不一样。

sSliceArrayPointer := unsafe.Pointer(*(*unsafe.Pointer)(unsafe.Pointer(&s)))

offset3Pointer := unsafe.Pointer(uintptr(sSliceArrayPointer ) + unsafe.Sizeof(&s[0])*3)

fmt.Println(“offset s[3]:”, *(*int)(offset3Pointer))

输出 offset s[3]: 4

slice 实际上是一个结构体 struct{ptr,len,cap},ptr 指向存储数据的数组,len/cap 表示当前长度和容量,你传到函数内的是一个 struct 的拷贝,在一个内部函数 append 不会影响到外部函数的 len.

你把 slice 类型看作是一个大概这样子的 struct
type Slice struct {
len int
mem *unsafe.Pointer
… other info
}
函数参数是值传递,就是把 s 复制了一遍到 a
其实 a 和 s 只是用了同一个内存指针,其他都是独立的

func test(a []int) 这里的 a 是 s 的一个拷贝,所以对 a 的修改不影响外面的 s。我猜传 *[]int 可以

影响的,你可以看我上面贴的 gist,也可以看看前几楼的解释

<br>package main<br><br>import (<br> "fmt"<br>)<br><br>func test(a []int) []int{<br> a = append(a, 4)<br> return a<br>}<br><br>func main() {<br> s := make([]int, 3, 4)<br> s[0] = 1<br> s[1] = 2<br> s[2] = 3<br> fmt.Println(s) //[1 2 3]<br> a := test(s)<br> fmt.Println(a) //[1 2 3 4]<br> s = append(s,5)<br> fmt.Println(s) //[1 2 3 4]<br> fmt.Println(a) //[1 2 3 5]<br>}<br>

更正,最后两行
fmt.Println(s) //[1 2 3 5]
fmt.Println(a) //[1 2 3 5]

建议了解下 slice 底层结构,再加上 Go 里传参都是值传递就好理解了
https://www.v2ex.com/t/650724

test 把新的 slice 返回回来, 然后 caller 再捕获才行, 本质还是值拷贝, slice 只是一个 slice header

当然,以下是对关于Go语言中slice疑问的专业回复:


您好!

关于Go语言中的slice,确实有很多值得探讨的地方。slice作为Go语言中的一种动态数组,提供了灵活且高效的内存管理方式。

首先,slice本质上是一个包含三个元素的结构体:指向底层数组的指针(Pointer)、slice的长度(Length)和slice的容量(Capacity)。这意味着slice本身并不存储数据,而是对底层数组的一个抽象和封装。

在使用slice时,常见的疑问可能包括:

  1. slice的扩容:当向slice添加元素并超出其容量时,Go会自动分配一个新的底层数组,并将旧数组的元素复制到新数组中。扩容的具体策略(如扩容倍数)在Go的不同版本中可能有所不同。

  2. slice的共享底层数组:由于slice只是底层数组的一个视图,因此不同的slice可能共享同一个底层数组。这在进行slice操作时需要注意,以避免意外的数据覆盖或丢失。

  3. slice的切片操作:通过切片操作可以创建一个新的slice,这个新slice会共享原slice的底层数组,但具有不同的长度和容量。

理解这些概念对于编写高效且正确的Go代码至关重要。如果您有更具体的问题或场景,欢迎进一步讨论!


希望这个回复能帮助您更好地理解Go语言中的slice。

回到顶部