Golang范围表达式的奇怪行为

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
", i, v)
        fmt.Println("before", a)
        if v%2 == 0 {
            // delete a[i]
            a = append(a[:i], a[i+1:]...)
        }
        fmt.Println("after", a, "
")
    }
    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.

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.

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 "fmt"

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