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

go 新手求助,关于 slice 的问题。

  •  
  •   tousfun · 2022-02-14 10:44:50 +08:00 · 2337 次点击
    这是一个创建于 998 天前的主题,其中的信息可能已经有所发展或是发生改变。
    func main() {
    	s2 := make([]int, 0, 10)
    	fmt.Printf("%p\n", s2)
    	addValue(s2)
    	fmt.Println(s2)
    }
    
    func addValue(list []int) {
    	fmt.Printf("%p\n", list)
    	list = append(list, 1, 2, 3, 4)
    	fmt.Println(list)
    	fmt.Printf("%p\n", list)
    }
    

    最后打印出的结果是:

    0x140000b4000
    0x140000b4000
    [1 2 3 4]
    0x140000b4000
    []
    
    

    为什么 s2 和 list 指向的地址相同,保存的值不同呢

    15 条回复    2022-02-14 17:47:47 +08:00
    mainjzb
        1
    mainjzb  
       2022-02-14 10:50:04 +08:00
    func addValue(list []int) []int{
    fmt.Printf("%p\n", list)
    list = append(list, 1, 2, 3, 4)
    fmt.Println(list)
    fmt.Printf("%p\n", list)
    return list
    }
    hujun528
        2
    hujun528  
       2022-02-14 10:58:33 +08:00
    nulIptr
        3
    nulIptr  
       2022-02-14 11:00:48 +08:00   ❤️ 2
    因为 slice 除了起始地址之外还保存了长度,你 main 函数里面的 s2 长度还是 0 ,长度还是 0 的原因的参数是值传递,addValue 里面修改的是拷贝一次的 slice 结构,而不是 main 函数里的 s2
    shyz
        4
    shyz  
       2022-02-14 11:12:07 +08:00
    函数都是值拷贝,sli 是引用类型,list 拷贝了 sli 的地址,直接输出地址肯定一样了。
    monetto
        5
    monetto  
       2022-02-14 11:18:35 +08:00   ❤️ 3
    可以这样理解,一个 slice 实际是一个 []int + 长度。
    你在函数里 addValue 之后,数组的下一位其实是赋值成功的。(如果没有触发 Slice 重新分配数组内存)
    但是,长度是 “形参” ,函数内的改变不会影响函数外部。

    也就是说,其实整个过程,是赋值成功的,但是,由于长度没有发生变化,addValue 外部的逻辑无法感知到新添加的元素。

    想要验证的话,可以通过 unsafe 获取 addValue 添加元素的地方的那块内存,会发现实际是添加成功了的。

    顺便一提,如果 addValue 的过程中,数组的长度达到阈值了,那么就会触发 Slice 重新分配内存,这个时候,传入函数中的 那个时刻的那个 数组 ,实际上是没有任何变化的。
    tousfun
        6
    tousfun  
    OP
       2022-02-14 11:23:29 +08:00 via iPhone
    @mainjzb
    @hujun528
    @nulIptr
    @shyz 谢谢各位,明白了
    tousfun
        7
    tousfun  
    OP
       2022-02-14 11:29:22 +08:00 via iPhone
    @monetto 非常感谢,明白了
    hujun528
        8
    hujun528  
       2022-02-14 11:37:49 +08:00   ❤️ 1
    package main

    import (
    "fmt"
    "testing"
    )

    func Test_test33(T *testing.T) {
    test33()
    }

    func test33() {
    s2 := make([]int, 2, 10) //初始长度 2
    s2 = append(s2, 66, 88) //长度+2
    fmt.Println(s2) //s2 总长度 =4
    fmt.Printf("%p\n", s2)
    addValue(s2) //传递 切片底向的 底层数组 针针
    fmt.Println(s2) //s2 总长度 4 并没有改变

    s2 = s2[:8] //如果改变切片 s2 的长度 =list 长度,
    fmt.Println(s2) // 是不是和 list 输出一样了
    }

    func addValue(list []int) {
    fmt.Printf("%p\n", list) //此切片 list 并不完全等于 切片 s2,切片之间也是无法进行比较的,list 只是和 s2 共用同一底层数组 ,切片 还有长度和容量
    list = append(list, 1, 2, 3, 4) //list 长度 +4 ,此操作 并不会改变 s2 的切片长度
    fmt.Printf("%p\n", list)
    fmt.Println(list)

    }
    iyear
        9
    iyear  
       2022-02-14 11:39:27 +08:00 via Android   ❤️ 2
    自己写过一篇关于 slice 参数传递的文章,可以看看

    https://blog.ljyngup.com/archives/868.html/
    wheeler
        10
    wheeler  
       2022-02-14 12:22:03 +08:00 via iPhone
    go slice 的 api 太不直观了。好像是肯汤普森写的。
    WhereverYouGo
        11
    WhereverYouGo  
       2022-02-14 13:02:24 +08:00
    @iyear #9 博客不错
    CEBBCAT
        12
    CEBBCAT  
       2022-02-14 13:12:56 +08:00
    Go 博客可以多读一读 https://go.dev/blog/slices-intro
    ryalu
        13
    ryalu  
       2022-02-14 13:43:50 +08:00
    lpxxn
        14
    lpxxn  
       2022-02-14 16:31:20 +08:00
    Lexgni
        15
    Lexgni  
       2022-02-14 17:47:47 +08:00
    你的 len 是 0 ,然后你追加的话原来的放不下,就会重新分配一个,你打印一下追加后的 list 就会发现和前面不一样了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2548 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 01:33 · PVG 09:33 · LAX 17:33 · JFK 20:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.