Why is string pointer in golang behavior counter-intuitive in range-loop?

北慕城南 提交于 2021-02-11 14:31:24

问题


With this code: https://play.golang.org/p/tCm1W-K-6ob

This code would print: [c c c], but [a b c] is excepted.

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()
}

Why the list is not ["a", "b", "c"]? What happened to the range or the &string?


回答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

the value f in range has the same address




回答2:


The items all contain the address of the one local variable v.

If your goal is to assign the address of the slice element the a field, then do the following:

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.

You can also get the output you want by creating a new variable on each loop iteration:

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:


The thing is that the iterator variable is allocated only once and the value it points to is changed with each iteration of the loop:

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

However, I have a strong feeling that this is unintuitive because we are pretty much talking of premature optimization. (A bit simplified): A string in Go is basically a slice of bytes([]byte), with a slice already being a pointer to the backing array. See Go Slices: usage and internals for details.




回答4:


You can try this

Here I can run your code and do some changes.

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

here is code

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()
}

Output abc



来源:https://stackoverflow.com/questions/56944781/why-is-string-pointer-in-golang-behavior-counter-intuitive-in-range-loop

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!