这是我的程序:
package main
import (
"fmt"
)
type Number struct {
val int
}
func (num * Number) Increment () {
num.val += 1
}
func (num Number) Value() int {
return num.val
}
func main() {
numbers := []Number {
{val: 12},
{val: 7},
{val: 0},
}
for _, each := range numbers {
each.Increment()
fmt.Println(each.Value())
}
for _, each := range numbers {
fmt.Println(each.Value())
}
}
以下是输出:
13
8
1
12
7
0
第一个问题:为什么Increment()
方法不更新第一个for循环中的值?我使用指针作为接收器,以便可以肯定地更新val
,但是为什么第二个for
循环会打印出这些Number
的原始值?
第二个问题:如何做,以便当我遍历Number
的一部分并调用Increment()
方法时,所有Number
s都正确地递增了?
[编辑] 我注意到,如果我使用基于索引的for
循环并调用Increment()
方法,则值将被正确更新。为什么?
for i := 0; i < len(numbers); i++ {
numbers[i].Increment()
}
答案 0 :(得分:2)
此for range
循环:
for _, each := range numbers {
在numbers
切片的元素上进行迭代,并在每次迭代中为each
循环变量分配(副本)一个元素。
由于您的numbers
切片的类型为[]Number
,它将把Number
结构复制到each
变量中(其类型将为Number
)。
然后在此变量上调用Number.Increment()
方法。由于Increment()
具有指针接收器,因此这是(&each).Increment()
的简写。因此,此循环变量的地址被用作Increment()
方法的接收器。 Increment()
方法将正确更改此循环变量,但这是独立于切片的独立变量,因此您无需修改切片中的元素。
当您这样做:
for i := 0; i < len(numbers); i++ {
numbers[i].Increment()
}
numbers
的元素未在此处复制。这个:
numbers[i].Increment()
为numbers
切片建立索引,并且由于Increment()
具有指针接收器,因此numbers[i]
的地址被获取和使用,这是切片中元素的地址。因此,在这里,您将修改切片的Number
结构值。
请注意,您也可以在此处使用for range
for i := range numbers {
numbers[i].Increment()
}
遍及切片时的第一个迭代变量是索引。
此外,如果将指针存储在numbers
切片(其类型为[]*Number
)中,则将发生相同的情况,但是在这种情况下,for range
将被复制指针,而不是结构,并且循环变量中的指针将指向与切片中的指针相同的Number
结构值,因此也可以与您的第一个for range
变体一起使用。
所有这些详细信息在Spec: For statements的具有范围子句的语句小节中。
答案 1 :(得分:1)
在您的原始版本中,循环变量each
是Number
结构的副本。请注意,它不是指向它的指针,也不是指向它的指针的副本。这意味着,每个迭代中都有一个新创建的Number
。您在指向该新创建实例的指针上调用方法,然后在循环且原始数据未更改后被销毁。
如果您改为使用numbers := []*Number { ...
并对其进行迭代,则each
变量将是指向Number
的指针的副本。使用指针副本与使用指针副本相同,因为副本指向相同的内存位置,因此,如果您随后调用该方法,则切片中的数据将会更改。
进行编辑:显然,如果使用numbers[i]
,则将引用切片中的数据。如上所述,for range
循环将在其each
变量中创建项目的副本。
答案 2 :(得分:0)
无论何时遍历切片,它都会创建原始变量的副本,该副本用作值并递增。但是,当您使用index
时,您所指向的是该地址上支持的值,该值将被递增,因此原始值将被更改。
打印两个变量的值,以查看更改为:
for i, each := range numbers {
each.Increment()
fmt.Println(each, numbers[i])
}
您还可以使用原始值地址循环打印变量的地址,以查看两个变量具有不同的地址。因此,当您在numbers
上进行迭代时,实际上是在创建局部变量。
for i, each := range numbers {
each.Increment()
fmt.Printf("%p -- %p\n",&each, &numbers[i])
}
用于检查Go Playground上地址的工作代码