在for循环中使用指针 - Golang

Sim*_*mon 15 pointers mutex for-loop go

我是golang的新手,并且很难理解为什么我的代码中有一个错误而不是另一个状态.我已经有一段时间了,因为我已经覆盖了指针,所以我可能生锈了!

基本上我有一个存储库结构,我用来将一个对象存储在内存中,它有一个Store函数.

type chartsRepository struct {
    mtx    sync.RWMutex
    charts map[ChartName]*Chart
}

func (r *chartsRepository) Store(c *Chart) error {
    r.mtx.Lock()
    defer r.mtx.Unlock()
    r.charts[c.Name] = c
    return nil
}
Run Code Online (Sandbox Code Playgroud)

所以它只是将RW互斥锁置于其上并将指针添加到地图,由标识符引用.

然后我有一个函数,它将基本上遍历这些对象的一部分,将它们全部存储在存储库中.

type service struct {
    charts Repository
}

func (svc *service) StoreCharts(arr []Chart) error {
    hasError := false
    for _, chart := range arr {
        err := svc.repo.Store(&chart)
        // ... error handling
    }
    if hasError {
        // ... Deals with the error object
        return me
    }
    return nil
}
Run Code Online (Sandbox Code Playgroud)

上面的方法不起作用,看起来一切正常,但是在尝试以后访问数据时Chart,尽管有不同的键,但是地图中的条目都指向同一个对象.

如果我执行以下操作并将指针引用移动到另一个函数,一切都按预期工作:

func (svc *service) StoreCharts(arr []Chart) error {
    // ...
    for _, chart := range arr {
        err := svc.storeChart(chart)
    }
    // ...
}

func (svc *service) storeChart(c Chart) error {
    return svc.charts.Store(&c)
}
Run Code Online (Sandbox Code Playgroud)

我假设的问题是,由于循环覆盖参考chartfor循环中,指针引用也会改变.当指针在独立函数中生成时,该引用永远不会被覆盖.是对的吗?

我觉得我是愚蠢的,但不应该由指针生成,&chart并且它与chart参考无关?我也尝试创建的指针一个新的变量p := &chartfor循环里,没有任何工作.

我应该避免在循环中生成指针吗?

对于众多问题感到抱歉,但我真的想要了解这个问题,我似乎无法找到资源来清楚地解释这一点.

谢谢.

icz*_*cza 26

这是因为只有一个循环变量chart,并且在每次迭代中只为其分配一个新值.因此,如果您尝试获取循环变量的地址,它将在每次迭代中相同,因此您将存储相同的指针,并且在每次迭代中覆盖指向的对象(循环变量)(并且在循环之后它将保留最后一次迭代中指定的值).

这在Spec:For语句中提到:对于带有range子句的语句:

迭代变量可以使用短变量declaration(:=)的形式由"range"子句声明.在这种情况下,它们的类型被设置为相应迭代值的类型,它们的范围是"for"语句的块; 它们在每次迭代中重复使用.如果迭代变量在"for"语句之外声明,则在执行之后,它们的值将是最后一次迭代的值.

您的第二个版本可以工作,因为您将循环变量传递给函数,因此将复制它,然后存储副本的地址(与循环变量分离).

不过没有函数你可以实现相同的效果:只需创建一个本地副本并使用它的地址:

for _, chart := range arr {
    chart2 := chart
    err := svc.repo.Store(&chart2) // Address of the local var
    // ... error handling
}
Run Code Online (Sandbox Code Playgroud)

另请注意,您还可以存储切片元素的地址:

for i := range arr {
    err := svc.repo.Store(&arr[i]) // Address of the slice element
    // ... error handling
}
Run Code Online (Sandbox Code Playgroud)

这样做的缺点是,由于存储了指向slice元素的指针,因此只要保留任何指针(数组不能被垃圾收集),就必须将切片的整个后备数组保存在内存中.此外,您存储的指针将Chart与切片共享相同的值,因此如果有人修改传递切片的图表值,则会影响您存储指针的图表.

查看相关问题:

Golang:使用环路切片/贴图的范围注册多个路线

为什么这两个for循环变化给我不同的行为?


Sar*_*smi 6

我今天遇到了类似的问题,创建这个简单的示例帮助我理解了这个问题。

// Input array of string values
inputList := []string {"1", "2", "3"}
// instantiate empty list
outputList := make([]*string, 0)

for _, value := range inputList {
    // print memory address on each iteration
    fmt.Printf("address of %v: %v\n", value, &value)
    outputList = append(outputList, &value)
}

// show memory address of all variables
fmt.Printf("%v", outputList)
Run Code Online (Sandbox Code Playgroud)

这打印出来:

address of 1: 0xc00008e1e0
address of 2: 0xc00008e1e0
address of 3: 0xc00008e1e0
[0xc00008e1e0 0xc00008e1e0 0xc00008e1e0]
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,即使实际值不同(“1”、“2”和“3”),每次迭代中的地址value始终相同。这是因为value正在重新分配。

最后, 中的每个值outputList都指向现在存储值“3”的同一地址。