【问题标题】:Why is string pointer in golang behavior counter-intuitive in range-loop?为什么golang行为中的字符串指针在范围循环中违反直觉?
【发布时间】:2019-07-09 03:04:56
【问题描述】:

使用此代码:https://play.golang.org/p/tCm1W-K-6ob

此代码将打印:[c c c],但 [a b c] 除外。

type A struct {
    a *string
}

func f() {
    slist := []string{"a", "b", "c"}
    list := make([]*A, len(slist))
    for i, v := range slist {
        item := &A{
            a: &v,
        }
        list[i] = item
    }
    fmt.Printf("[%s %s %s]", *list[0].a, *list[1].a, *list[2].a)
}

func main() {
    f()
}

为什么列表不是["a", "b", "c"]?范围或 &string 发生了什么变化?

【问题讨论】:

  • Golang 中的循环变量实际上是为循环的所有迭代共享的,类似于 JS,所以iv 是整个循环的相同变量。因此,如果您想获取地址,则需要为 v 创建一个临时值。

标签: pointers go


【解决方案1】:

https://play.golang.org/p/utJrUmxeqfh

package main

import (
    "fmt"
)

func main() {
    foo := []int{1, 2, 3}
    for _, f := range foo {
        fmt.Printf("value is %d, addr is %p \n", f, &f)
    }
    fmt.Println("Hello, playground")
}
value is 1, addr is 0x414020 
value is 2, addr is 0x414020 
value is 3, addr is 0x414020 
Hello, playground

range 中的值f 具有相同的地址

【讨论】:

【解决方案2】:

这些项目都包含一个局部变量v的地址。

如果您的目标是将切片元素的地址分配给 a 字段,请执行以下操作:

for i := range slist {
    item := &A{
        a: &slist[i],  // address of element instead of local variable.
    }
    list[i] = item
}

Run it on the Go Playground.

您还可以通过在每次循环迭代中创建一个新变量来获得所需的输出:

for i, v := range slist {
    v := v  // create new value on each iteration.
    item := &A{
        a: &v,
    }
    list[i] = item
}

Run it on the Go Playground.

【讨论】:

    【解决方案3】:

    问题是迭代器变量只分配一次,它指向的值随着循环的每次迭代而改变:

    package main
    
    import (
        "fmt"
    )
    
    type A struct {
        a *string
    }
    
    func f() {
        slist := []string{"a", "b", "c"}
        list := make([]*A, len(slist))
    
        // v is only allocated once and the value at it's address is changed
        // on each iteration.
        for i, v := range slist {
            fmt.Printf("Iteration %d: address of v %#v, value of v \"%s\"\n", i+1, &v, v)
            // Add in a temp variable, which will get allocated
            // and hence have a new address each iteration.
            t := v
    
            // Now assign that temp variable's address to the Item's a.
            list[i] = &A{
                a: &t,
            }
        }
        fmt.Printf("[%s %s %s]", *list[0].a, *list[1].a, *list[2].a)
    }
    
    func main() {
        f()
    }
    

    Run on playground

    但是,我有一种强烈的感觉,这是不直观的,因为我们几乎都在谈论过早的优化。 (有点简化):Go 中的字符串基本上是字节切片([]byte),切片已经是指向后备数组的指针。详情请见Go Slices: usage and internals

    【讨论】:

      【解决方案4】:

      你可以试试这个

      在这里我可以运行您的代码并进行一些更改。

      https://play.golang.org/p/GmU3goeOKdF

      这里是代码

      package main
      
      import (
          "fmt"
      )
      
      type A struct {
          a  *string
      }
      
      func f() {
          slist := []string{"a", "b", "c"}
          list := make([]*A, len(slist))
          for i, v := range slist {
              item := &A{
                  a: &v,
              }
              list[i] = item
      
          fmt.Printf("%s", *list[i].a)
          }
      }
      func main() {
          f()
      }
      
      

      输出 abc

      【讨论】:

      • 你可以看到答案..我认为它对你有帮助。 @首钢
      • 仅链接答案没有多大帮助。如果链接改变或消失,答案就变得毫无意义。请在您的答案中至少包含要点。
      • @MarkusWMahlberg,我正在尝试解决他们的问题。这样我就只能运行代码并进行一些更改。
      • 我明白这一点。但是为了长期保留知识,这里完全回答这个问题是有意义的,链接只是添加信息。请参阅meta.stackexchange.com/questions/8231/… 以深入讨论为什么应避免仅链接答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-26
      • 2019-05-09
      • 2014-03-16
      • 1970-01-01
      • 2013-09-06
      • 2015-03-30
      • 1970-01-01
      相关资源
      最近更新 更多