我正在努力理解为什么我的代码在一种状态下有错误,而在另一种状态下却没有。自从我介绍了指针以来已经有一段时间了,所以我可能会生锈!
基本上,我有一个用于将对象存储在内存中的存储库结构,该对象具有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
}
上面的方法不起作用,起初看起来一切正常,但是以后尝试访问数据时,尽管键不同,但映射中的所有条目都指向相同的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)
}
我假设问题是因为循环会覆盖chart
循环中对for
的引用,所以指针引用也会更改。在独立函数中生成指针时,该引用永远不会被覆盖。是吗?
我感觉自己很蠢,但是指针不应该由&chart
生成,并且该指针独立于chart
引用吗?我还尝试在p := &chart
循环中为指针for
创建一个新变量,但该方法也不起作用。
我应该避免在循环中生成指针吗?
这是因为只有一个循环变量chart
,并且在每次迭代中都只为其分配了一个新值。因此,如果您尝试获取循环变量的地址,则在每次迭代中它都将是相同的,因此您将存储相同的指针,并且在每次迭代中(在循环之后,它都将覆盖该指向对象(循环变量))将保留在上一次迭代中分配的值)。
这在Spec: For statements: For statements with range
clause:中提到
迭代变量可以由“ range”子句以
range
(short variable declaration)的形式声明。在这种情况下,将它们的类型设置为各个迭代值的类型,并且它们的:=
是“ for”语句的块; 它们在每次迭代中都会重复使用。如果迭代变量在“ for”语句之外声明,则执行后它们的值将是上一次迭代的值。
您的第二个版本有效,因为您将循环变量传递给了一个函数,因此将对其进行复制,然后存储该副本的地址(与循环变量分离)。
尽管您无需功能也可以实现相同的效果:只需创建本地副本并使用该地址即可:
scope
还请注意,您也可以存储slice元素的地址:
for _, chart := range arr {
chart2 := chart
err := svc.repo.Store(&chart2) // Address of the local var
// ... error handling
}
缺点是,由于您存储了指向slice元素的指针,因此,只要保留了任何指针,就必须将slice的整个后备数组保留在内存中(无法对数组进行垃圾回收)。此外,您存储的指针将与切片共享相同的for i := range arr {
err := svc.repo.Store(&arr[i]) // Address of the slice element
// ... error handling
}
值,因此,如果有人修改传递的切片的图表值,则会影响您存储其指针的图表。
查看相关问题:
Chart
Golang: Register multiple routes using range for loop slices/map