使用范围迭代数组时,如果数组已更新,则更新的位置不会使其进入以后的循环运行。以下显示“ 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”中的“具有范围子句的语句”
答案 0 :(得分:6)
TLDR; :无论范围如何,都会对其进行复制(这是一般的“规则”,但有例外,请参阅下文)。 Arrays在Go语言中很少见,通常使用slices。切片值(slice headers)包含指向基础数组的指针,因此复制切片标头是快速,高效的,并且不像数组那样复制切片元素。在这方面,对数组指针的定位类似于在切片上的定位。
范围表达式
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
的引用,该引用不会被修改,并且会打印该引用变量的值,该引用变量实际上已经被修改。