V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
yujianwjj
V2EX  ›  Go 编程语言

go slice 的一个疑问

  •  
  •   yujianwjj · 2020-03-04 11:46:23 +08:00 · 2815 次点击
    这是一个创建于 1754 天前的主题,其中的信息可能已经有所发展或是发生改变。
    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

    21 条回复    2020-03-09 13:11:59 +08:00
    zoowii
        1
    zoowii  
       2020-03-04 11:48:34 +08:00
    a=append(a, 4)执行前后的 a 不是同一个 slice, 参数 a 传的是引用的值,不改变原来的 s 变量
    yujianwjj
        2
    yujianwjj  
    OP
       2020-03-04 11:50:30 +08:00
    @zoowii cap(a) 是 4,实际 len(a) 是 3,为啥 append 前后的 a 不是同一个 slice ?
    kuro1
        3
    kuro1  
       2020-03-04 11:51:31 +08:00
    老一套 指针问题
    auta
        4
    auta  
       2020-03-04 11:55:57 +08:00 via iPhone
    @yujianwjj #2 外面的 s 的 len 没有变成 4
    lhx2008
        5
    lhx2008  
       2020-03-04 11:56:33 +08:00
    s 这个 struct 没有被改,里面存了 len 的数据,a 是另外一个 struct
    AzadCypress
        6
    AzadCypress  
       2020-03-04 12:02:46 +08:00 via Android   ❤️ 1
    type slice struct{
    array unsafe.Pointer
    len int
    cap int
    }
    当你用 slice 作为参数时,传进去的是这个结构体的 copy
    所以改变 slice 中的值是有效的,涉及到长度 /容量的在副本上进行 是无效的
    yujianwjj
        7
    yujianwjj  
    OP
       2020-03-04 12:03:55 +08:00 via iPhone
    @auta 懂了
    cmdOptionKana
        8
    cmdOptionKana  
       2020-03-04 12:06:18 +08:00
    你把 test 函数改成这样

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

    然后这样调用 test(&s)

    就可以了。
    ifconfig
        9
    ifconfig  
       2020-03-04 14:11:19 +08:00
    这是值传递,很多语言都是值传递吧
    zhs227
        10
    zhs227  
       2020-03-04 14:22:04 +08:00   ❤️ 3
    说到点子上的人不多。除了 auta,外面的 s.len 没变,但是 s[4]的内容实际是有了,只是 length 仍然是 3.
    具体可以看看 playground: https://play.golang.org/p/JjP8z4xQ1Qa
    luoqeng
        11
    luoqeng  
       2020-03-04 14:39:11 +08:00
    底层用的同一块内存,但是 s 与 a 的里记录的长度不一样。
    luoqeng
        12
    luoqeng  
       2020-03-04 15:08:03 +08:00
    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
    CEBBCAT
        13
    CEBBCAT  
       2020-03-04 18:38:34 +08:00
    visitant
        14
    visitant  
       2020-03-04 18:50:35 +08:00
    slice 实际上是一个结构体 struct{ptr,len,cap},ptr 指向存储数据的数组,len/cap 表示当前长度和容量,你传到函数内的是一个 struct 的拷贝,在一个内部函数 append 不会影响到外部函数的 len.
    index90
        15
    index90  
       2020-03-04 18:53:16 +08:00
    你把 slice 类型看作是一个大概这样子的 struct
    type Slice struct {
    len int
    mem *unsafe.Pointer
    ... other info
    }
    函数参数是值传递,就是把 s 复制了一遍到 a
    其实 a 和 s 只是用了同一个内存指针,其他都是独立的
    v2exe2v
        16
    v2exe2v  
       2020-03-04 18:54:51 +08:00
    func test(a []int) 这里的 a 是 s 的一个拷贝,所以对 a 的修改不影响外面的 s。我猜传 *[]int 可以
    CEBBCAT
        17
    CEBBCAT  
       2020-03-04 20:07:37 +08:00 via Android
    @v2exe2v 影响的,你可以看我上面贴的 gist,也可以看看前几楼的解释
    kuro1
        18
    kuro1  
       2020-03-05 10:59:12 +08:00
    ```
    package main

    import (
    "fmt"
    )

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

    func main() {
    s := make([]int, 3, 4)
    s[0] = 1
    s[1] = 2
    s[2] = 3
    fmt.Println(s) //[1 2 3]
    a := test(s)
    fmt.Println(a) //[1 2 3 4]
    s = append(s,5)
    fmt.Println(s) //[1 2 3 4]
    fmt.Println(a) //[1 2 3 5]
    }
    ```
    kuro1
        19
    kuro1  
       2020-03-05 11:03:04 +08:00
    更正,最后两行
    fmt.Println(s) //[1 2 3 5]
    fmt.Println(a) //[1 2 3 5]
    newmiao
        20
    newmiao  
       2020-03-07 17:30:51 +08:00
    建议了解下 slice 底层结构,再加上 Go 里传参都是值传递就好理解了
    https://www.v2ex.com/t/650724
    ericuni
        21
    ericuni  
       2020-03-09 13:11:59 +08:00
    test 把新的 slice 返回回来, 然后 caller 再捕获才行, 本质还是值拷贝, slice 只是一个 slice header
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4654 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 01:11 · PVG 09:11 · LAX 17:11 · JFK 20:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.