Golang range表达式的奇怪行为

huangapple go评论86阅读模式
英文:

strange behavior of golang range expression

问题

我有这样一个测试代码,它只会从一个整数切片中删除偶数:

package main

import "fmt"

func main() {
    a := []int{0, 1, 2, 3}
    for i, v := range a {
        fmt.Printf("i: %d v: %d\n", i, v)
        fmt.Println("before", a)
        if v%2 == 0 {
            // delete a[i]
            a = append(a[:i], a[i+1:]...)
        }
        fmt.Println("after", a, "\n")
    }
    fmt.Println("final", a)
}

输出结果为:

i: 0 v: 0
before [0 1 2 3]
after [1 2 3] 

i: 1 v: 2
before [1 2 3]
after [1 3] 

i: 2 v: 3
before [1 3]
after [1 3] 

i: 3 v: 3
before [1 3]
after [1 3] 

final [1 3]

你也可以在这里找到它:http://play.golang.org/p/BFPxekBggS。我的问题是为什么变量 v 在最后两次迭代中的值都是 3?提前谢谢。

英文:

I have this test code which just delete even numbers from a int slice:

package main

import "fmt"

func main() {
	a := []int{0, 1, 2, 3}
	for i, v := range a {
		fmt.Printf("i: %d v: %d\n", i, v)
		fmt.Println("before", a)
		if v%2 == 0 {
			// delete a[i]
			a = append(a[:i], a[i+1:]...)
		}
		fmt.Println("after", a, "\n")
	}
	fmt.Println("final", a)

}

The output is :

i: 0 v: 0
before [0 1 2 3]
after [1 2 3] 

i: 1 v: 2
before [1 2 3]
after [1 3] 

i: 2 v: 3
before [1 3]
after [1 3] 

i: 3 v: 3
before [1 3]
after [1 3] 

final [1 3]

You can also find it here http://play.golang.org/p/BFPxekBggS. My question is why the variable v evaluates to 3 in the last two iterations? Thanks in advance.

答案1

得分: 7

在内部,切片类似于包含三个元素的结构体:

  • 一个底层数组
  • 底层数组的大小,可以通过cap(slice)访问
  • 切片的长度,可以通过len(slice)访问

在循环运行之前,切片a的底层数组是[0, 1, 2, 3],且cap(a) == len(a) == 4

当你使用以下代码修改a时:

a = append(a[:i], a[i+1:]...)

由于新的长度小于容量,a的新值共享原始的底层数组。因此,在第一次迭代中修改后,底层数组现在包含[1, 2, 3, 3],且len(a) == 3。数组中的最后一个元素在正常的切片操作中不可见,但保留了其旧值。

在第二次迭代中,切片再次缩短,因此底层数组现在是[1, 3, 3, 3],且len(a) == 2

现在,当循环运行时,range表达式只会被评估一次,因此无论在循环内部做了什么更改,它都会始终导致4次迭代。它还将从相同的底层数组返回结果,这解释了你看到的数字。

英文:

Internally, a slice is like a struct containing three elements:

  • A backing array
  • The size of the backing array, which can be accessed as cap(slice)
  • The length of the slice, which can be accessed as len(slice)

Before your loop runs, the backing array for a is [0, 1, 2, 3] with cap(a) == len(a) == 4.

When you modify a with the following code:

a = append(a[:i], a[i+1:]...)

The new value for a shares the backing array of the original since the new length is less than the capacity. So after the modification in the first iteration, the backing array now contains [1, 2, 3, 3] with len(a) == 3. The final element in the array is not visible via normal slice operations, but retains its old value.

In the second iteration the slice is shortened again, so the backing array is now [1, 3, 3, 3] with len(a) == 2.

Now when the loop runs the range expression is only evaluated once, so it will always result in 4 iterations no matter what changes you make within the loop. It will also be returning results from the same backing array, which explains the numbers you're seeing.

答案2

得分: 4

问题在于你在迭代过程中修改了 a(删除元素),所以结果可能会有些...令人惊讶。我猜测在第一次删除后,a 在内存中的样子可能是 [1 2 3 3],所以 a[2]3,在第二次删除后,a 在内存中的样子可能是 [1 3 3 3],所以 a[3]3

所以,我的第一个建议是将代码改为使用传统的 for 循环而不是 range,并且只有在不删除任何东西时才增加 i

package main

import "fmt"

func main() {
    a := []int{0, 1, 2, 3}
    for i := 0; i < len(a); {
        v := a[i]
        fmt.Printf("i: %d v: %d\n", i, v)
        fmt.Println("before", a)
        if v%2 == 0 {
            // delete a[i]
            a = append(a[:i], a[i+1:]...)
        } else {
            i++
        }
        fmt.Println("after", a, "\n")
    }
    fmt.Println("final", a)

}

以下是输出结果:

i: 0 v: 0
before [0 1 2 3]
after [1 2 3]

i: 0 v: 1
before [1 2 3]
after [1 2 3]

i: 1 v: 2
before [1 2 3]
after [1 3]

i: 1 v: 3
before [1 3]
after [1 3]

final [1 3]

我的第二个建议是反转循环(从末尾开始迭代),以避免增减的“特殊情况”:

package main

import "fmt"

func main() {
    a := []int{0, 1, 2, 3}
    for i := len(a) - 1; i >= 0; i-- {
        v := a[i]
        fmt.Printf("i: %d v: %d\n", i, v)
        fmt.Println("before", a)
        if v%2 == 0 {
            // delete a[i]
            a = append(a[:i], a[i+1:]...)
        }
        fmt.Println("after", a, "\n")
    }
    fmt.Println("final", a)

}

以下是输出结果:

i: 3 v: 3
before [0 1 2 3]
after [0 1 2 3]

i: 2 v: 2
before [0 1 2 3]
after [0 1 3]

i: 1 v: 1
before [0 1 3]
after [0 1 3]

i: 0 v: 0
before [0 1 3]
after [1 3]

final [1 3]
英文:

The problem is that you are modifying a (deleting elements) while you are iterating on it, so the results can be a little bit... surprising. My guess is that after your first deletion, a is something like that in memory : [1 2 3 3], and so a[2] is 3, and after your second deletion, a is something like that in memory : [1 3 3 3], and so a[3] is 3.

So, my first suggestion is to change the code to use a traditional for loop instead of range, and increment i only when you don't delete something :

package main

import &quot;fmt&quot;

func main() {
    a := []int{0, 1, 2, 3}
    for i := 0; i &lt; len(a); {
        v := a[i]
        fmt.Printf(&quot;i: %d v: %d\n&quot;, i, v)
        fmt.Println(&quot;before&quot;, a)
        if v%2 == 0 {
            // delete a[i]
            a = append(a[:i], a[i+1:]...)
        } else {
            i++
        }
        fmt.Println(&quot;after&quot;, a, &quot;\n&quot;)
    }
    fmt.Println(&quot;final&quot;, a)

}

Here is the output :

i: 0 v: 0
before [0 1 2 3]
after [1 2 3]

i: 0 v: 1
before [1 2 3]
after [1 2 3]

i: 1 v: 2
before [1 2 3]
after [1 3]                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
                                                                                                                                                                                                                                                                               
i: 1 v: 3                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
before [1 3]                                                                                                                                                                                                                                                                   
after [1 3]                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
final [1 3]

And my second suggestion is to inverse the loop (iterate from the end) to avoid the "special case" for incrementation/decrementation :

package main

import &quot;fmt&quot;

func main() {
    a := []int{0, 1, 2, 3}
    for i := len(a) - 1; i &gt;= 0; i-- {
        v := a[i]
        fmt.Printf(&quot;i: %d v: %d\n&quot;, i, v)
        fmt.Println(&quot;before&quot;, a)
        if v%2 == 0 {
            // delete a[i]
            a = append(a[:i], a[i+1:]...)
        }
        fmt.Println(&quot;after&quot;, a, &quot;\n&quot;)
    }
    fmt.Println(&quot;final&quot;, a)

}

Here is the output :

i: 3 v: 3                                                                                                                                                                                                                                                                   
before [0 1 2 3]                                                                                                                                                                                                                                                               
after [0 1 2 3]                                                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                               
i: 2 v: 2                                                                                                                                                                                                                                                                       
before [0 1 2 3]                                                                                                                                                                                                                                                               
after [0 1 3]                                                                                                                                                                                                                                                                   
                                                                                                                                                                                                                                                                               
i: 1 v: 1                                                                                                                                                                                                                                                                      
before [0 1 3]                                                                                                                                                                                                                                                                 
after [0 1 3]                                                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                                                               
i: 0 v: 0                                                                                                                                                                                                                                                                      
before [0 1 3]                                                                                                                                                                                                                                                                 
after [1 3]                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                               
final [1 3]

huangapple
  • 本文由 发表于 2014年1月16日 13:52:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/21154286.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定