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

初学 golang,小白求解惑!

  •  
  •   superfatboy · 12 天前 · 2944 次点击
    package main
    
    import "fmt"
    
    type Test struct {
    	Id   int64
    	Name string
    }
    
    func main() {
    	var list = []Test{
    		Test{Id: 1, Name: "1XX"},
    		Test{Id: 2, Name: "2XX"},
    		Test{Id: 3, Name: "3XX"},
    	}
    	for _, v := range list {
    		v.Name = "888"
    	}
            fmt.Println(list)
    }
    

    为啥修改 Name 貌似没生效

    第 1 条附言  ·  12 天前
    习惯了写 js ,学习 go 有点不适应啊!!
    27 条回复    2021-11-26 18:11:17 +08:00
    rrfeng
        1
    rrfeng  
       12 天前 via Android   ❤️ 2
    for range 是 copy 一份对象,所以没改原始的。
    用索引访问数组元素就可以了。
    yin1999
        2
    yin1999  
       12 天前 via Android   ❤️ 1
    因为你获取的是一个拷贝,而不是 slice 中的原始对象
    helone
        3
    helone  
       12 天前   ❤️ 1
    ```
    for k, _ := range list {
    list[k].Name = "888"
    }
    ```
    这样就行了
    superfatboy
        4
    superfatboy  
    OP
       12 天前
    @rrfeng
    @yin1999
    感谢,刚才仔细看了一下教程,果然是一个拷贝,nnd ,大意了
    superfatboy
        5
    superfatboy  
    OP
       12 天前
    @helone 感谢
    corningsun
        6
    corningsun  
       12 天前
    Javaer 震怒 ~
    wunonglin
        7
    wunonglin  
       12 天前   ❤️ 1
    ```
    func main() {
    var list = []*Test{
    {Id: 1, Name: "1XX"},
    {Id: 2, Name: "2XX"},
    {Id: 3, Name: "3XX"},
    }
    for _, v := range list {
    v.Name = "888"
    }

    for _, v := range list {
    fmt.Printf("%+v\n", v)
    }
    }
    ```

    用指针就好了
    Rooger
        8
    Rooger  
       12 天前   ❤️ 1
    原因:for range 的方式其实一种 copy 了 list 。
    两种修改方式,一种就是 `list[k].Name = 888`,另一种是将 slice 修改为存储指针 var list = []*Test 。
    同样的坑你也应该注意,不能使用 k 和 v 的地址。
    whyso
        9
    whyso  
       12 天前
    @corningsun 为啥
    superfatboy
        10
    superfatboy  
    OP
       12 天前
    @wunonglin 这个方法不错
    ClarkAbe
        11
    ClarkAbe  
       12 天前 via Android
    用指针,和 C 系一样
    ClarkAbe
        12
    ClarkAbe  
       12 天前 via Android
    不过 js 那边不是有句名言不建议在循环里面修改元素嘛
    corningsun
        13
    corningsun  
       12 天前
    @whyso

    Java 和 Go 的 for 循环 默认策略完全相反。
    Java 必须要主动拷贝才会新建对象。这种 List 结构还得深拷贝,不然用的还是同一个对象。
    Go 和 Java stream() 比较像了。
    superfatboy
        14
    superfatboy  
    OP
       12 天前
    @ClarkAbe 哈哈,怎么顺手怎么来,不过确实不推荐
    yrj
        15
    yrj  
       12 天前 via iPad
    你这么理解,range 的时候,其实是 copy 了一份新的数据给 for 所以你在 for 里修改的是 copy 的新数据。可以按照楼上指针的写法,原理是直接操作了指针
    mmuggle
        16
    mmuggle  
       12 天前
    虽然 for range 确实是循环的副本,但是这个是循环的切片,切片结构是指向一个底层数组的指针

    所以我觉得原因是 v 是在 for 循环的时候初始化的,循环体中修改的是 v 的 Name ,而不是切片元素的 Name
    SimbaPeng
        17
    SimbaPeng  
       12 天前   ❤️ 10
    我真的怀疑楼上的回复者,真的会 Golang ?

    什么 range copy 了 list ,range 策略,深拷贝浅拷贝?能不能不要在这里误人子弟?

    这里的关键就是 v := range list 是个赋值操作,将 list 元素赋值给 v ,golang 里只有值传递,所以 list 元素在复制给 v 的时候必然产生了拷贝。而结构体是值类型,没有引用的对象,所以是拷贝整个结构体,直接修改 v 的属性就是修改拷贝的结构体,所以原结构体属性并不会改变。

    同理,但凡你将一个结构体赋值给一个新变量,然后修改新变量的字段,都不会对原结构体有任何影响。

    这跟你用没用 range 一点关系都没有。

    如果你的 list 里存的是结构体的指针,range 的时候一样会产生拷贝,不过拷贝的是指针结构,底层引用的结构体不会产生拷贝,所以你解指针修改字段的时候,修改的是同一个结构体。但如果将一个新的指针赋值给 v ,原 list 的元素依然不会被改变。

    最后总结一句话,golang 只有值传递。
    SimbaPeng
        18
    SimbaPeng  
       12 天前
    ```
    func main() {
    a := []int{1, 2, 3}
    for i, v := range a {
    if i == 0 {
    a[2] = 4
    }
    fmt.Println(v)
    }
    }
    ```

    那些说是因为 range 的是 list 的副本的,自己运行一下这段代码。copy 的是切片,又不是底层数组。
    ila
        19
    ila  
       12 天前 via Android
    range map 时可以修改 map 的 value ,倒推过来看看
    cassyfar
        20
    cassyfar  
       12 天前
    @SimbaPeng 这是正解

    所以如果你要用 for loop 改值,换成指针就好了。实际操作中,也基本用的指针数组存放 struct 。
    mmuggle
        21
    mmuggle  
       12 天前   ❤️ 1
    代码其实相当于下面这种
    ```
    package main

    import "fmt"

    type Test struct {
    Id int64
    Name string
    }

    func main() {
    var list = []Test{
    Test{Id: 1, Name: "1XX"},
    Test{Id: 2, Name: "2XX"},
    Test{Id: 3, Name: "3XX"},
    }
    v := Test{}
    for _, v = range list {
    v.Name = "888"
    }
    fmt.Println(list)
    }
    ```
    superfatboy
        22
    superfatboy  
    OP
       12 天前
    @mmuggle 你这种写法就容易理解了
    Mitt
        23
    Mitt  
       12 天前
    @SimbaPeng #17 一个意思的两种表达而已,虽然你说的是对的,但你显然误解了上面所说的拷贝,在我看来两种说法都没有问题
    whyso
        24
    whyso  
       12 天前
    @SimbaPeng 真相了,LZ 的问题在于 V 而不是 range ,循环修改值我一般用 for i:=0; i<len();i++ {}这种,不修改用 range
    SimbaPeng
        25
    SimbaPeng  
       12 天前   ❤️ 1
    @Mitt 在我看来你所谓的“两种表达”,描述的本质是不同,楼上有些回复的本意是:range 会 产生迭代对象的副本,所以无法修改原对象。事实是 range 确实会产生 迭代对象的副本,但这不是 LZ 不能用 v 来修改原对象中元素的原因。

    有的甚至扯到深浅拷贝,认为 range 产生了深拷贝,但其实 range 只会 产生 切片结构的拷贝,底层引用的数组还是同一个。
    oluoluo
        26
    oluoluo  
       11 天前   ❤️ 1
    ```
    package main

    import "fmt"

    type Item struct {
    Id uint64
    Name string
    }

    func main() {
    list := []Item{
    {Id: 1, Name: "1xxx"},
    {Id: 2, Name: "2xxx"},
    {Id: 3, Name: "3xxx"},
    }
    for i := 0; i < len(list); i++ {
    fmt.Printf("item %d address: %p\n", i, &list[i])
    }
    for _, v := range list {
    v.Name = "888"
    fmt.Printf("list item address: %p\n", &v)
    }
    fmt.Println("---------------")
    fmt.Println(list)
    }
    ```
    item 0 address: 0x14000060050
    item 1 address: 0x14000060068
    item 2 address: 0x14000060080
    list item address: 0x1400000c030
    list item address: 0x1400000c030
    list item address: 0x1400000c030
    ---------------
    [{1 1xxx} {2 2xxx} {3 3xxx}]
    wangritian
        27
    wangritian  
       11 天前
    想修改内容时,我都会这样写:
    for i := range list {
    v := &(list[i])
    v.Name = "xxx"
    }
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   4208 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 06:24 · PVG 14:24 · LAX 22:24 · JFK 01:24
    ♥ Do have faith in what you're doing.