golang范围表达的奇怪行为

时间:2014-01-16 05:52:39

标签: go

我有这个测试代码,只删除int切片中的偶数:

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?提前谢谢。

2 个答案:

答案 0 :(得分:5)

在内部,切片就像一个包含三个元素的结构:

  • 支持阵列
  • 支持数组的大小,可以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次迭代。它也将返回来自相同后备阵列的结果,这解释了您所看到的数字。

答案 1 :(得分:4)

问题是你在迭代时正在修改a(删除元素),所以结果可能有点......令人惊讶。我的猜测是,在您第一次删除后,a就像记忆中的那样:[1 2 3 3],因此a[2]3,并且在您第二次删除后{{1}在内存中就是这样的:a,所以[1 3 3 3]a[3]

所以,我的第一个建议是更改代码以使用传统的3循环而不是范围,并且只有在不删除某些内容时才会增加for

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)

}