For loop 在for循环中使用指针

For loop 在for循环中使用指针,for-loop,pointers,go,mutex,For Loop,Pointers,Go,Mutex,我很难理解为什么我的代码在一种状态下有一个bug,而在另一种状态下却没有。我已经有一段时间没有学习指针了,所以我可能已经生锈了 基本上,我有一个用于在内存中存储对象的存储库结构,它有一个store函数 type chartsRepository struct { mtx sync.RWMutex charts map[ChartName]*Chart } func (r *chartsRepository) Store(c *Chart) error { r.mt

我很难理解为什么我的代码在一种状态下有一个bug,而在另一种状态下却没有。我已经有一段时间没有学习指针了,所以我可能已经生锈了

基本上,我有一个用于在内存中存储对象的存储库结构,它有一个
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
}
因此,它所做的就是打开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
}
上面的方法不起作用,一开始看起来一切正常,但在稍后尝试访问数据时,地图中的条目都指向相同的
图表
对象,尽管键不同

如果我执行以下操作并将指针引用移动到另一个函数,则一切正常:

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)
}
我假设问题在于,由于循环覆盖了
for
循环中对
图表的引用,指针引用也会发生变化。在独立函数中生成指针时,该引用永远不会被覆盖。是这样吗

我觉得我很愚蠢,但是指针不应该由
&chart
生成吗?它独立于
chart
引用吗?我还尝试在
for
循环中为指针
p:=&chart
创建一个新变量,但也没有成功


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

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

这一点在本节中提到

迭代变量可以使用(
:=
)的形式由“range”子句声明。在这种情况下,它们的类型被设置为各自迭代值的类型,并且它们是“for”语句的块它们在每次迭代中重复使用。如果迭代变量在“for”语句之外声明,那么在执行之后,它们的值将是上一次迭代的值

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

不过,您可以在不使用函数的情况下实现相同的效果:只需创建一个本地副本并使用该副本的地址:

for _, chart := range arr {
    chart2 := chart
    err := svc.repo.Store(&chart2) // Address of the local var
    // ... error handling
}
还请注意,您还可以存储切片元素的地址:

for i := range arr {
    err := svc.repo.Store(&arr[i]) // Address of the slice element
    // ... error handling
}
这样做的缺点是,由于存储指向切片元素的指针,因此只要保留任何指针,切片的整个备份数组就必须保留在内存中(数组不能被垃圾收集)。此外,存储的指针将与切片共享相同的
Chart
值,因此如果有人修改传递切片的图表值,这将影响存储指针的图表

见相关问题:


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

//字符串值的输入数组
inputList:=[]字符串{“1”、“2”、“3”}
//实例化空列表
outputList:=make([]*字符串,0)
对于u,值:=范围输入列表{
//每次迭代时打印内存地址
fmt.Printf(“%v的地址:%v\n”、值和值)
outputList=append(outputList和value)
}
//显示所有变量的内存地址
fmt.Printf(“%v”,输出列表)
这张照片打印出来:

1的地址:0xc00008e1e0
2的地址:0xc00008e1e0
3的地址:0xc00008e1e0
[0xc00008e1e0 0xc00008e1e0 0xc00008e1e0]
如您所见,每次迭代中
值的地址始终相同,即使实际值不同(“1”、“2”和“3”)。这是因为
被重新分配


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

谢谢,这很有意义。我不知道为什么循环中的
p:=&chart
不起作用,是指向
chart
的指针,而不是指向当时的
chart
的指针吗?golang教程上的措辞表明指针是由代码
&图表
生成的。也许我最好有一个存储库来接受对象本身,而不是指针并总是复制以避免潜在的陷阱和垃圾收集问题?@Simon如果你这样做
p:=&chart
,那么你再次获取单个循环变量的地址并将其存储在一个变量(指针类型)中,然后在每次迭代中存储这个指针。压力是复制
图表
值,然后是这个新变量的地址,而不是循环变量。@Simon,在局部变量中复制它并获取它的地址没有错。在Go中,获取局部变量的地址是完全正常的,运行时和垃圾收集器会跟踪所有这些,只要您有指针,指针对象就可以使用。在这方面,Go与C不同。是的,我理解这一点,但我认为,就存储库应该如何与
Store
之类的函数一起工作而言,也许在业务层面上,制作副本并保存副本更有意义。特别是因为这种内存存储在某个时刻会被持久化的东西所取代。谢谢@icza,这真的很有帮助。这是一个谜的形式,并演示了一些解决方案