在 for 循环中使用指针
Using Pointers in a for loop
我很难理解为什么我的代码在一种状态下存在错误,而在另一种状态下却没有。我已经有一段时间没有介绍指针了,所以我可能生疏了!
基本上我有一个用于在内存中存储对象的存储库结构,它具有 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)
}
我假设问题是因为循环覆盖了 for
循环中对 chart
的引用,指针引用也发生了变化。当指针在独立函数中生成时,该引用永远不会被覆盖。是吗?
我觉得我很愚蠢,但是指针不应该由 &chart
生成并且独立于 chart
引用吗?我还尝试在 for
循环中为指针 p := &chart
创建一个新变量,但也没有用。
我应该避免在循环中生成指针吗?
这是因为只有一个循环变量chart
,并且在每次迭代中只为其分配一个新值。所以如果你试图获取循环变量的地址,它在每次迭代中都是相同的,所以你将存储相同的指针,指向的对象(循环变量)在每次迭代中被覆盖(并且在循环之后它将保留上次迭代中分配的值)。
这个在Spec: For statements: For statements with range
clause:
中有提到
The iteration variables may be declared by the "range" clause using a form of short variable declaration (:=
). In this case their types are set to the types of the respective iteration values and their scope is the block of the "for" statement; they are re-used in each iteration. If the iteration variables are declared outside the "for" statement, after execution their values will be those of the last iteration.
你的第二个版本是可行的,因为你将循环变量传递给了一个函数,所以会生成一个副本,然后你存储副本的地址(它与循环变量分离)。
你可以在没有函数的情况下实现相同的效果:只需创建一个本地副本并使用它的地址:
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
值,因此如果有人修改传递的切片的图表值,那将影响您存储其指针的图表。
查看相关问题:
我今天遇到了类似的问题,创建这个简单的示例帮助我理解了这个问题。
// 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)
这打印出来:
address of 1: 0xc00008e1e0
address of 2: 0xc00008e1e0
address of 3: 0xc00008e1e0
[0xc00008e1e0 0xc00008e1e0 0xc00008e1e0]
如您所见,即使实际值不同(“1”、“2”和“3”),每次迭代中 value
的地址始终相同。这是因为 value
正在重新分配。
最后,outputList
中的每个值都指向同一个地址,该地址现在存储值“3”。
我很难理解为什么我的代码在一种状态下存在错误,而在另一种状态下却没有。我已经有一段时间没有介绍指针了,所以我可能生疏了!
基本上我有一个用于在内存中存储对象的存储库结构,它具有 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)
}
我假设问题是因为循环覆盖了 for
循环中对 chart
的引用,指针引用也发生了变化。当指针在独立函数中生成时,该引用永远不会被覆盖。是吗?
我觉得我很愚蠢,但是指针不应该由 &chart
生成并且独立于 chart
引用吗?我还尝试在 for
循环中为指针 p := &chart
创建一个新变量,但也没有用。
我应该避免在循环中生成指针吗?
这是因为只有一个循环变量chart
,并且在每次迭代中只为其分配一个新值。所以如果你试图获取循环变量的地址,它在每次迭代中都是相同的,所以你将存储相同的指针,指向的对象(循环变量)在每次迭代中被覆盖(并且在循环之后它将保留上次迭代中分配的值)。
这个在Spec: For statements: For statements with range
clause:
The iteration variables may be declared by the "range" clause using a form of short variable declaration (
:=
). In this case their types are set to the types of the respective iteration values and their scope is the block of the "for" statement; they are re-used in each iteration. If the iteration variables are declared outside the "for" statement, after execution their values will be those of the last iteration.
你的第二个版本是可行的,因为你将循环变量传递给了一个函数,所以会生成一个副本,然后你存储副本的地址(它与循环变量分离)。
你可以在没有函数的情况下实现相同的效果:只需创建一个本地副本并使用它的地址:
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
值,因此如果有人修改传递的切片的图表值,那将影响您存储其指针的图表。
查看相关问题:
我今天遇到了类似的问题,创建这个简单的示例帮助我理解了这个问题。
// 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)
这打印出来:
address of 1: 0xc00008e1e0
address of 2: 0xc00008e1e0
address of 3: 0xc00008e1e0
[0xc00008e1e0 0xc00008e1e0 0xc00008e1e0]
如您所见,即使实际值不同(“1”、“2”和“3”),每次迭代中 value
的地址始终相同。这是因为 value
正在重新分配。
最后,outputList
中的每个值都指向同一个地址,该地址现在存储值“3”。