首页 > 解决方案 > 如果对象在切片中,方法不会改变对象的值

问题描述

这是我的程序:

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循环会打印出那些Numbers 的原始值呢?

第二个问题:可以做些什么,以便当我遍历一个Numbers 切片并调用该Increment()方法时,所有Numbers 都正确递增?

[编辑]我注意到如果我使用基于索引的for循环并调用该Increment()方法,值将被正确更新。为什么?

for i := 0; i < len(numbers); i++ {
    numbers[i].Increment()
}

标签: pointersfor-loopgostructslice

解决方案


这个for range循环:

for _, each := range numbers {

迭代numbers切片的元素,并在每次迭代中将一个元素分配(复制each)给循环变量。

由于您的numbers切片是 type []Number,它会将结构复制Numbereach变量中(其类型将为Number)。

然后你调用Number.Increment()这个变量的方法。由于Increment()有指针接收器,这是(&each).Increment(). 所以这个循环变量的地址被用作Increment()方法的接收者。该Increment()方法将正确更改此循环变量,但这是独立的、不同的、与切片分离的,因此您不会修改切片中的元素。

当你这样做时:

for i := 0; i < len(numbers); i++ {
    numbers[i].Increment()
}

numbers这里没有复制的元素。这个:

numbers[i].Increment()

numbers对slice 进行索引,由于Increment()有指针接收器,所以取并使用 的地址,即 slicenumbers[i]中元素的地址。所以在这里,您将修改切片的结构值。Number

请注意,您也可以for range在此处使用:

for i := range numbers {
    numbers[i].Increment()
}

遍历切片时的第一个迭代变量是索引。

此外,如果您将指针存储在切片中numbers(然后其类型为值作为切片中的指针,因此这也适用于您的第一个变体。[]*Numberfor rangeNumberfor range

所有这些都在Spec: For statements中详细说明,在For statements with range 子句小节中。


推荐阅读