问题
Here is my program:
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())
}
}
Here is the output:
13
8
1
12
7
0
First question: why does the Increment()
method not update the value in the first for loop? I used pointer as the receiver so that val
can be updated for sure, but why would the second for
loop print out the original values of those Number
s?
Second question: what can be done so that when I iterate over a slice of Number
s and invoke the Increment()
method, all Number
s are correctly incremented?
[Edit] I noticed that if I use index-based for
loop and invoke the Increment()
method, values will be correctly updated. Why?
for i := 0; i < len(numbers); i++ {
numbers[i].Increment()
}
回答1:
This for range
loop:
for _, each := range numbers {
iterates over the elements of the numbers
slice, and in each iteration it assigns (copies) an element to the each
loop variable.
Since your numbers
slices is of type []Number
, it will copy the Number
struct into the each
variable (whose type will be Number
).
Then you call the Number.Increment()
method on this variable. Since Increment()
has pointer receiver, this is a shorthand for (&each).Increment()
. So the address of this loop variable is taken and used as the receiver for the Increment()
method. The Increment()
method will properly change this loop variable, but this is independent, distinct, detached from the slice, so you are not modifying the element in the slice.
When you do:
for i := 0; i < len(numbers); i++ {
numbers[i].Increment()
}
Elements of numbers
are not copied here. This:
numbers[i].Increment()
Indexes the numbers
slice, and since Increment()
has a pointer receiver, the address of numbers[i]
is taken and used, which is the address of the element in the slice. So here, you will modify the Number
struct value of the slice.
Note that you can also use for range
here:
for i := range numbers {
numbers[i].Increment()
}
The first iteration variable when ranging over a slice is the index.
Also, if you would store pointers in your numbers
slice (which would then have type of []*Number
), the same thing would happen, but in that case the for range
would copy pointers, not structs, and the pointer in the loop variable would point to the same Number
struct value as the pointer in the slice would, so that would also work with your first for range
variant.
All these are detailed in Spec: For statements, in the For statements with range clause subsection.
回答2:
In your original version, the loop variable each
is a copy of the Number
struct. Notice that it is not a pointer to it, nor is it the copy of a pointer to it. That means, that there is a newly created Number
in each iteration. You call a method on a pointer to this newly created instance, then it gets destroyed after the loop and your original data has not changed.
If you would instead use numbers := []*Number { ...
and iterate over that, the each
variable would be the copy of a pointer to Number
. Using copies of pointers is the same as using pointers, because the copy points to the same memory location, thus if you then call the method, the data in the slice wil change.
For your edit: if you use numbers[i]
then you reference the data inside the slice, obviously. As I stated above, the for range
loop will create a copy of the items in its each
variable.
回答3:
Whenever you loop through the slice it creates a copy of original variable which is used as value and is incremented. But when you are using the index
you are pointing to the value backed at that address which is than incremented and hence original value got changed.
Print the value of both variable to see the changes as:
for i, each := range numbers {
each.Increment()
fmt.Println(each, numbers[i])
}
you can also print the address of variable in a loop with the original value address to see that both variables have different address. Hence you are actually creating a local variable when iterating over numbers
.
for i, each := range numbers {
each.Increment()
fmt.Printf("%p -- %p\n",&each, &numbers[i])
}
Working Code to check address on Go Playground
来源:https://stackoverflow.com/questions/51490801/method-does-not-change-the-value-of-object-if-the-object-is-in-a-slice