slice 在 append 不同情况下的理解 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
iyear
V2EX    Go 编程语言

slice 在 append 不同情况下的理解

  •  
  •   iyear
    iyear 2021-07-15 20:54:28 +08:00 2440 次点击
    这是一个创建于 1619 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Q 群里讨论起来 slice 的传递,才发现有坑 = =

    看了网上的一些文章。slice 在传入函数后 append 会有坑 下面是我的理解,不知道恰不恰当。

    一种情况是 cap 够,不扩容

    func main() { sliceA := make([]int, 3, 4) sliceA[0] = 0 sliceA[1] = 1 sliceA[2] = 2 fmt.Println(sliceA) changeSlice(sliceA) fmt.Println(sliceA) fmt.Println(sliceA[:4]) } func changeSlice(slicePass []int) { slicePass = append(slicePass, 3) } //Output /* [0 1 2] [0 1 2] [0 1 2 3] */ 

    slice 结构中的 len,cap 都是 int,无法在函数里面被改变。 这种情况下指定了一个 len 为 3,cap 为 4 的 slice 。append 完后发现正常输出只会输出前三个数,验证了 len 并没有被改变。而当强制输出第四项时又发现 3 是存在的。 例如截取数组等操作都是这种情况。

    也就是说这种情况下 append 对原数组生效,只是由于 len 没有改变而无法呈现出 append 的项。

    还有种情况是 cap 不够,slice 扩容

    slice 扩容会把扩容后的数组指向新内存,直接与原数组无关了,append 的项也不存在于原数组 大概代码长这样

    func main() { sliceA := []int{1, 2, 3, 4, 5} fmt.Println(sliceA) fmt.Printf("%d %p main\n", len(sliceA),sliceA) changeSliceA(sliceA) fmt.Println(sliceA) } func changeSliceA(slicePass []int) { slicePass = append(slicePass, 6) fmt.Printf("%d %p pass\n", len(slicePass),slicePass) } // Output /* [1 2 3 4 5] 5 0xc00000c690 main 6 0xc000016550 pass [1 2 3 4 5] */ 

    所以是 go 中的 slice 在函数中被 append 时数据呈现不变分为两种情况。

    一种是 len 未被改变,由传值导致;

    一种是指针发生改变,由 slice 的内部扩容实现导致?

    这样理解有没有问题?

    第 1 条附言    2021-07-17 01:48:17 +08:00
    可能是我描述的问题,感觉大家没看懂我想问的是什么。我是想问问大家我这样两种情况的理解有没有错,大家都在给我科普 slice 的原理
    然后把这个扩写成了一篇小短文,好久没写博客了,语言组织能力急剧下降。有空再完善一下代码
    https://blog.ljyngup.com/archives/868.html/
    15 条回复    2021-07-21 09:44:24 +08:00
    Jirajine
        1
    Jirajine  
       2021-07-15 21:10:46 +08:00 via Android
    slice 是一个 fat pointer (即一个 pointer 加一个 length ),而函数都是值传递,append 会自动扩容(容量不够时分配一个新数组并把数据复制过去),返回指向(无论新旧)数组的 slice 。
    传统 OO 语言一切皆对象(引用)一致性更好,也没有这些心智负担,go 这种保留裸指针纯粹是开 dao 车。
    CEBBCAT
        2
    CEBBCAT  
       2021-07-15 22:37:47 +08:00 via Android   1
    你第二个测试根本不完备嘛,既没有验证修改之后对原 slice 有没有影响,也没有验证 slice 的 cap

    不要听楼上瞎扯,Python 、Java 也有深浅复制问题。

    关于 slice,强烈推荐去读 Golang 的博客
    附链接: https://blog.golang.org/slices-intro

    或者去看 go 源码,记得是在 runtime 底下
    ongongethan
        4
    ongongethan  
       2021-07-15 23:29:41 +08:00
    使用切片指针,可以解决上面两个例子的问题。

    例子 1
    func main() {
    sliceA := make([]int, 3, 4)
    sliceA[0] = 0
    sliceA[1] = 1
    sliceA[2] = 2
    fmt.Println(sliceA)
    changeSlice(&sliceA)
    fmt.Println(sliceA)
    fmt.Println(sliceA[:4])
    }
    func changeSlice(slicePass *[]int) {
    *slicePass = append(*slicePass, 3)
    }

    输出:
    [0 1 2]
    [0 1 2 3]
    [0 1 2 3]


    例子 2
    func main() {
    sliceA := []int{1, 2, 3, 4, 5}
    fmt.Printf("main before append: len:%d address:%p\n", len(sliceA),sliceA)
    fmt.Println("content:", sliceA)
    changeSliceA(&sliceA)
    fmt.Printf("main after append: len:%d address:%p\n", len(sliceA),sliceA)
    fmt.Println("content:", sliceA)
    }

    func changeSliceA(slicePass *[]int) {
    fmt.Printf("func before append: len:%d address:%p\n", len(*slicePass),*slicePass)
    fmt.Println("content:", *slicePass)
    *slicePass = append(*slicePass, 6)
    fmt.Printf("func after append: len:%d address:%p\n", len(*slicePass),*slicePass)
    fmt.Println("content:", *slicePass)
    }

    输出:
    main before append: len:5 address:0xc00007a030
    content: [1 2 3 4 5]
    func before append: len:5 address:0xc00007a030
    content: [1 2 3 4 5]
    func after append: len:6 address:0xc000014050
    content: [1 2 3 4 5 6]
    main after append: len:6 address:0xc000014050
    content: [1 2 3 4 5 6]
    iyear
        5
    iyear  
    OP
       2021-07-15 23:51:03 +08:00 via Android
    @ongongethan 这我知道,只是针对这个问题
    @CEBBCAT 还是官方博客权威啊
    BeautifulSoap
        6
    BeautifulSoap  
       2021-07-15 23:54:56 +08:00 via Android
    一开始没反应过来,然后才想到,把 slice 看成含有指向目标内存块指针,len,cap 三个元素的 struct 值就好理解发生的这一切了。就是上面官方文档里的那样

    至于为什么超了 cap 要重新分配内存,lz 你学 go 的时候应该知道 slice 底层是数组,而数组在内存上是连续空间,你想要获得更大的连续空间的话,只能重新申请一块新的连续空间(无视长度直接继续写就是内存泄漏了,鬼知道会发生什么,学 C 的人一定很亲切)

    至于有没有办法用不连续内存存数据?当然了,链表之类的就行
    iyear
        7
    iyear  
    OP
       2021-07-15 23:59:01 +08:00 via Android
    @BeautifulSoap 嗯这就是我的意思,我写的是针对 append 下带坑的两种情况的解释
    iyear
        8
    iyear  
    OP
       2021-07-16 00:11:02 +08:00 via Android
    slice 的实现是已经了解过。想询问的是这两种情况下都会导致数据问题的理解有无问题,还是有其他原因
    hannibalm
        9
    hannibalm  
       2021-07-16 16:42:16 +08:00
    把 slice 当作一种数据类型,给函数传值,则在该函数只读 slice ;给函数传 slice 地址,则可读可修改。
    以前 C 语言就是这个思路。
    EscYezi
        10
    EscYezi  
       2021-07-18 05:00:48 +08:00 via iPhone
    我记得 golang 圣经里面有提过参数传递 slice,函数内操作不会导致 slice 本身发生变化,但是可以使 slice 指向的底层数组变化
    EscYezi
        11
    EscYezi  
       2021-07-18 05:05:02 +08:00 via iPhone
    类似于传递指针,对这个指针操作不会改变指针本身( slice 的 len 和 cap 属性),而是指针指向的对象(底层数组)
    asLw0P981N0M0TCC
        12
    asLw0P981N0M0TCC  
       2021-07-18 12:24:33 +08:00
    之前刷题的时候遇到过 都是复制一份出来操作的 也不太明白
    barathrum
        13
    barathrum  
       2021-07-19 11:48:33 +08:00
    @BeautifulSoap 应该叫溢出不叫泄露吧。
    zhangsanfeng2012
        14
    zhangsanfeng2012  
       2021-07-20 10:41:56 +08:00
    https://golang.org/doc/effective_go 官方文档两句话
    1 、If a function takes a slice argument, changes it makes to the elements of the slice will be visible to the caller, analogous to passing a pointer to the underlying array.
    2 、We must return the slice afterwards because, although Append can modify the elements of slice, the slice itself (the run-time data structure holding the pointer, length, and capacity) is passed by value.
    iyear
        15
    iyear  
    OP
       2021-07-21 09:44:24 +08:00
    @zhangsanfeng2012 #14 感谢原来官方文档已经有说明了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     900 人在线   最高记录 6679       Select Language
    创意作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 23:20 PVG 07:20 LAX 15:20 JFK 18:20
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86