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

请教 golang slice 相关的问题

  •  
  •   wewin · 2019-07-09 11:29:51 +08:00 · 4019 次点击
    这是一个创建于 1965 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我们知道 golang 中 slice 是引用类型,我声明长度为 10 的 slice,往其中插入了 6 个元素。打印能看到 addres4、addres5 地址相同,但是在 main 中打印 slice 的值和在 someChage 方法中打印的结果竟然不一样,请问大佬,这是什么原因?

    代码:

    package main
    
    import "fmt"
    
    func someChage(slice []int) {
    	fmt.Printf("addres2: %p\n", slice)
    
    	slice = append(slice, 1, 2, 3)
    	fmt.Printf("addres3: %p\n", slice)
    
    	slice = append(slice, 4, 5, 6)
    	fmt.Printf("addres4: %p\n", slice)
    	fmt.Println(slice)
    }
    
    func main() {
    	slice := make([]int, 0, 20)
    	fmt.Printf("addres1: %p\n", slice)
    	someChage(slice)
    
    	fmt.Printf("addres5: %p\n", slice)
    	fmt.Println(slice)
    }
    

    结果:

    addres1: 0xc00007a000
    addres2: 0xc00007a000
    addres3: 0xc00007a000
    addres4: 0xc00007a000
    [1 2 3 4 5 6]
    addres5: 0xc00007a000
    []
    
    25 条回复    2019-07-09 22:43:28 +08:00
    wewin
        1
    wewin  
    OP
       2019-07-09 11:32:04 +08:00
    期待大佬的回复
    misaka19000
        2
    misaka19000  
       2019-07-09 11:45:05 +08:00 via Android   ❤️ 1
    GGGG430
        3
    GGGG430  
       2019-07-09 11:47:47 +08:00
    https://stackoverflow.com/questions/22811138/print-the-address-of-slice-in-golang

    fmt.Printf("address of slice %p add of Arr %p \n", &slice, &intarr)
    %p will print the address.
    kidlj
        4
    kidlj  
       2019-07-09 11:49:47 +08:00
    Slice 并不是一个 pure 引用类型,更像是一个聚合类型:

    type IntSlice struct {
    ptr *int
    len int
    cap int
    }

    所以当你在 main 里将 slice 传给 someChange 函数,someChange 获得了一个 slice 的复制( Go 是参数传值),因此 someChange 里的 slice 和 main 函数里的 slice 不相关了。

    两个函数里打印 slice 是相同的地址可能是因为打印的是 slice 底层 underlying array 的地址,两个 slice 指向的是同一个 underlying array,在 append 的过程中并没有改变(因为 cap 还够)。
    petelin
        5
    petelin  
       2019-07-09 11:53:46 +08:00 via iPhone
    Somechange 函数接受到的是 slice 对象的一个拷贝
    Len cap 都是单独的 不过 底层数组确实是一个
    在执行修改之后 main 的 slice len 还是 0 只不过底层数组多了 1-2-3-4 这个细节对于 slice 是隐藏的 所以看起来没有产生任何修改
    fcten
        6
    fcten  
       2019-07-09 11:53:53 +08:00
    slice 的地址相同并不意味着它们是同一个 slice。
    你在 someChage 里操作的 slice 和 main 中的 slice 指向同一块数据,所以它们的地址相同。slice 本身是值传递的,所以在 someChage 里使用 append 操作 slice 并不会影响到 main 中的 slice。main 中的 slice 长度一直为 0,所以输出为 []。
    sryanyuan
        7
    sryanyuan  
       2019-07-09 11:53:54 +08:00
    golang 都是值复制 没有引用
    petelin
        8
    petelin  
       2019-07-09 11:58:19 +08:00
    理解一下这段代码, 尤其是 s1[0] 会 panic.
    func dos(s2 []int) {
    s2 = append(s2, 1)
    fmt.Printf("first element add: %p, slice addr: %p, real slice addr: %p", &s2[0], s2, &s2)
    }
    func TestSlice(t *testing.T) {
    s1 := make([]int, 0, 20)
    dos(s1)
    fmt.Println(s1)
    fmt.Println(s1[0])
    }
    tiedan
        9
    tiedan  
       2019-07-09 12:01:59 +08:00
    楼主,改成这样,你运行一下应该就明白了吧

    package main

    import "fmt"

    func someChage(slice []int) {
    slice[0] = 100
    fmt.Println(slice)
    fmt.Printf("addres2: %p\n", &slice)

    slice = append(slice, 1, 2, 3)
    fmt.Printf("addres3: %p\n", &slice)

    slice = append(slice, 4, 5, 6)
    fmt.Printf("addres4: %p\n", &slice)
    fmt.Println(slice)
    }

    func main() {
    slice := make([]int, 1, 10)
    fmt.Printf("addres1: %p\n", &slice)
    someChage(slice)

    fmt.Printf("addres5: %p\n", &slice)
    fmt.Println(slice)
    }
    tiedan
        10
    tiedan  
       2019-07-09 12:03:37 +08:00
    只是共用了一个底层数组,但是是不同的切片
    tiedan
        11
    tiedan  
       2019-07-09 12:05:31 +08:00
    main 里面 slice 长度一直为 1 所以只能看到底层数组下标为 0 的那个元素
    flyzero
        12
    flyzero  
       2019-07-09 12:08:17 +08:00 via Android
    有说的不对的请指出,函数参数都值传递,但是切片传的是指针地址值,
    append 是在原有后面添加,所以没有改变指针值,你试试插入到第一位,就会变了
    hduwillsky
        13
    hduwillsky  
       2019-07-09 12:17:21 +08:00 via iPhone
    关键在 append 返回
    petelin
        14
    petelin  
       2019-07-09 12:17:34 +08:00 via iPhone
    @flyzero 也不会变 看看上面的回复 你的理解有问题
    mengzhuo
        15
    mengzhuo  
       2019-07-09 12:26:06 +08:00 via iPhone
    底层是数组,当你要 append 的数量大于数组大小 n 就会复制到 2n 的新数组中,这时 slice 里的指针发生变化。

    好好看看基础就不会有这样的问题了
    ruin2016
        16
    ruin2016  
       2019-07-09 12:45:56 +08:00
    ```
    func someChage(slice []int) {
    fmt.Printf("addres2: %p\n", &slice)

    slice = append(slice, 1, 2, 3)
    fmt.Printf("addres3: %p\n", &slice)

    slice = append(slice, 4, 5, 6)
    fmt.Printf("addres4: %p\n", &slice)
    fmt.Println(slice)
    }

    func main() {
    slice := make([]int, 0, 20)
    fmt.Printf("addres1: %p\n", &slice)
    someChage(slice)

    fmt.Printf("addres5: %p\n", &slice)
    fmt.Println(slice)
    }

    ```

    addres1: 0xc00007e040
    addres2: 0xc00007e060
    addres3: 0xc00007e060
    addres4: 0xc00007e060
    [1 2 3 4 5 6]
    addres5: 0xc00007e040
    []

    值引用!
    wewin
        17
    wewin  
    OP
       2019-07-09 12:52:41 +08:00
    @mengzhuo
    你说的,len 超过 cap 的时候,会创建一个新的底层数组,数组的 cap 会按照 len 的两倍增加。是下面这种情况:

    ```
    package main

    import "fmt"

    func someChage(slice []int) {
    fmt.Printf("addres2: %p\n", slice)

    slice = append(slice, 1, 2, 3)
    fmt.Printf("addres3: %p\n", slice)

    slice = append(slice, 4, 5, 6)
    fmt.Printf("addres4: %p\n", slice)
    fmt.Println(slice)
    }

    func main() {
    slice := make([]int, 0, 4)
    fmt.Printf("addres1: %p\n", slice)
    someChage(slice)

    fmt.Printf("addres5: %p\n", slice)
    fmt.Println(slice)
    }

    ```
    输出结果:
    ```
    addres1: 0xc00008a000
    addres2: 0xc00008a000
    addres3: 0xc00008a000
    addres4: 0xc000090000
    [1 2 3 4 5 6]
    addres5: 0xc00008a000
    []
    ```

    前前排大佬们的回复,那是些 get 到点的大佬们
    impl
        18
    impl  
       2019-07-09 12:53:26 +08:00 via Android
    因为 main 里面的 slice 的 len 是还是 0,把最后一句改成 fmt.Println(slice[:6])试试
    ScepterZ
        19
    ScepterZ  
       2019-07-09 12:57:30 +08:00
    slice 里存了地址 len cap 三个东西,你不穿指针进去的话,main 里的 slice 的 len 是没有改变的
    可以输出 slice 的地址就明白了
    shawn7
        20
    shawn7  
       2019-07-09 14:10:33 +08:00
    我还是不太明白,append 值不会改变,但是赋值可以

    package main

    import (
    "fmt"
    )

    func addSome(s []int) {
    s[0] = 1
    }

    func main() {
    s := make([]int, 5)
    fmt.Println(s)

    addSome(s)
    fmt.Println(s)
    }

    输出
    [0 0 0 0 0]
    [1 0 0 0 0]
    reage
        21
    reage  
       2019-07-09 14:19:34 +08:00
    slice := make([]int, 0, 20)
    你将 20 改为 2 看下,就看到变化。了解下 map 的 cap 参数
    rrfeng
        22
    rrfeng  
       2019-07-09 14:41:35 +08:00
    刚好前几天看到一篇文章,可以解惑。
    《 Go Slices are Fat Pointers 》
    https://nullprogram.com/blog/2019/06/30/
    shawn7
        23
    shawn7  
       2019-07-09 14:49:34 +08:00 via Android
    明白了,2 楼发的文章讲的很清楚。谢谢楼主
    liulaomo
        24
    liulaomo  
       2019-07-09 20:34:34 +08:00
    jimmzhou
        25
    jimmzhou  
       2019-07-09 22:43:28 +08:00
    当把 slice 作为参数,本身传递的是值,但其内容就 byte* array,实际传递的是引用,所以可以在函数内部修改,但如果对 slice 本身做 append,而且导致 slice 进行了扩容,实际扩容的是函数内复制的一份切片,对于函数外面的切片没有变化。
    package main

    import(
    "fmt"
    )

    func someChage(slice *[]int) {
    fmt.Printf("addres2: %p\n", slice)

    *slice = append(*slice, 1, 2, 3)
    fmt.Printf("addres3: %p\n", slice)

    *slice = append(*slice, 4, 5, 6)
    fmt.Printf("addres4: %p\n", slice)
    fmt.Println(slice)
    }

    func main() {
    slice := make([]int, 0, 20)
    fmt.Printf("addres1: %p\n", slice)
    someChage(&slice)

    fmt.Printf("addres5: %p\n", slice)
    fmt.Println(*slice)
    }

    输出:
    addres1: 0xc00009c000
    addres2: 0xc00005e420
    addres3: 0xc00005e420
    addres4: 0xc00005e420
    [1 2 3 4 5 6]
    addres5: 0xc00009c000
    [1 2 3 4 5 6]
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1529 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 16:52 · PVG 00:52 · LAX 08:52 · JFK 11:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.