迭代时更新golang数组

时间:2019-12-20 09:28:21

标签: arrays pointers go iteration

使用范围迭代数组时,如果数组已更新,则更新的位置不会使其进入以后的循环运行。以下显示“ 1 2”而不是“ 1 0”

package main

import (
    "fmt"
)

func main() {
    var A = &[2]int{1, 2}
    for i, v := range A {
        if i == 0 {
            A[1] = 0
        }
        fmt.Print(v, " ")
    }

    fmt.Println()

    var B = [2]int{1, 2}
    for i, v := range B {
        if i == 0 {
            B[1] = 0
        }
        fmt.Print(v, " ")
    }
}

https://play.golang.org/p/0zZY6vjxwut

看起来数组在迭代之前就已被复制。

规范的哪一部分描述了此行为? 请参见“ https://golang.org/ref/spec#For_range”中的“具有范围子句的语句”

2 个答案:

答案 0 :(得分:6)

TLDR; :无论范围如何,都会对其进行复制(这是一般的“规则”,但有例外,请参阅下文)。 Arrays在Go语言中很少见,通常使用slices。切片值(slice headers)包含指向基础数组的指针,因此复制切片标头是快速,高效的,并且不像数组那样复制切片元素。在这方面,对数组指针的定位类似于在切片上的定位。


Spec: For statements:

  

范围表达式x在开始循环之前被求值一次,但有一个例外:如果最多存在一个迭代变量并且len(x)是常数,则范围表达式没有评估。

数组是值,它们不包含指向位于数组内存之外的数据的指针(与切片不同)。 The Go Blog: Go Slices: usage and internals:

  

Go的数组是值。 数组变量表示整个数组;它不是指向第一个数组元素的指针(就像C语言一样)。这意味着当您分配或传递数组值时,您将复制其内容。 (为避免复制,您可以将指针传递给数组,但这是指向数组而不是数组的指针。)考虑数组的一种方法是将其作为一种结构,但使用索引字段而不是命名字段:大小的复合值。

评估数组是整个数组的副本,它是所有元素的副本。 Spec: Variables:

  

通过引用expression 中的变量来检索变量的值;它是变量的最新值assigned

在您的第一个示例中,范围表达式只是数组的 pointer ,因此仅复制此指针(而不是指针数组),因此在执行A[1] = 0(是(*A)[1] = 0的简写),您可以修改原始数组,然后迭代变量从指向的数组中获取元素。

在第二个示例中,范围表达式是数组,因此复制了数组(及其所有元素),并且在其中B[1] = 0仍修改了原始数组(B是变量,而不是范围表达式的求值结果),但是v是副本的元素(v是在每次迭代中从复制的数组中填充的。)

引擎盖下

那么该“副本”如何实现?编译器为for range生成代码,该代码将范围表达式的结果复制(分配)到一个临时变量(如果需要,因为可能并不总是需要它:”,如果最多存在一个迭代变量并且len(x)是常量,则不计算范围表达式”。

可以在cmd/compile/internal/gc/range.go文件中检查此代码。

请参阅相关文章:Go Range Loop Internals

答案 1 :(得分:2)

规范说

  

范围表达式x在开始循环之前被评估一次,但有一个例外:如果最多存在一个迭代变量并且len(x)是常量,则不评估范围表达式。

     

左侧的函数调用每次迭代评估一次。对于每次迭代,如果存在各自的迭代变量,则会按以下方式生成迭代值

这里的事情是,给定您的循环使用多个变量,范围表达式在迭代开始时仅被评估一次。因此,分配给B[1]的{​​{1}}的值不会改变。

在有引用的情况下,您会看到修改后的值,因为表达式会评估对v的引用,该引用不会被修改,并且会打印该引用变量的值,该引用变量实际上已经被修改。