Golang中Range循环的意外变更问题解析

Golang中Range循环的意外变更问题解析 大家好:

我在为LeetCode上的一个问题(这不重要)编写代码时遇到了一个问题。我写了一个函数,但它没有按我预期的方式工作。我将代码移到了playground

以下是我的问题:

我打印了每一次循环,item [1 1 2]这一行附近的输出让我感到困惑。

在追加之前,变量result的值是[[] [1] [1 1] [2] [1 2] [1 1 2] [2 2] [1 2 2] [1 1 2 2]],这符合我的预期。

但是在将变量n(其值为3)追加到一个元素中而没有进行赋值操作之后,值变成了[[] [1] [1 1] [2] [1 2] [1 1 2] [2 2] [1 2 2] [1 1 2 3]]

我不知道为什么会发生这种情况,以及我该如何解决它?


更多关于Golang中Range循环的意外变更问题解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

好的,我明白了。

前几次循环绕过了这个问题,因为分配了一个新的切片。我现在明白了!

非常感谢!你救了我的命!

更多关于Golang中Range循环的意外变更问题解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


切片不是不可变的。当你从一个类似 []int{} 的切片开始时,它的容量为零。第一次追加操作会分配一个容量为1的新切片,并将追加的值存储到这个切片中。第二次追加会创建一个容量翻倍(从1到2)的新切片,并将追加的值存储进去。当你第三次向切片追加时,会再次分配一个容量翻倍(到4)的新切片。第四次追加时,第三次追加得到的切片仍有可用容量,因此会复用该切片而无需分配新的。

func main() {
    fmt.Println("hello world")
}

这是一个典型的切片引用问题。在Go语言的range循环中,循环变量是复用的,每次迭代时会将当前元素的值复制到同一个变量中。当你操作切片中的切片时,如果直接使用循环变量,可能会导致意外的修改。

以下是问题重现和解决方案:

package main

import "fmt"

func main() {
    nums := []int{1, 1, 2, 2}
    result := [][]int{{}}
    
    for _, n := range nums {
        fmt.Printf("Processing n=%d\n", n)
        length := len(result)
        for i := 0; i < length; i++ {
            // 问题代码:直接使用result[i]会导致引用问题
            // item := result[i]
            // item = append(item, n)
            // result = append(result, item)
            
            // 正确做法:创建新的切片副本
            newItem := make([]int, len(result[i]))
            copy(newItem, result[i])
            newItem = append(newItem, n)
            result = append(result, newItem)
            
            fmt.Printf("  result[%d] + %d = %v\n", i, n, newItem)
        }
    }
    
    fmt.Printf("Final result: %v\n", result)
}

或者更简洁的写法:

package main

import "fmt"

func main() {
    nums := []int{1, 1, 2, 2}
    result := [][]int{{}}
    
    for _, n := range nums {
        length := len(result)
        for i := 0; i < length; i++ {
            // 使用append创建新切片
            newItem := append([]int{}, result[i]...)
            newItem = append(newItem, n)
            result = append(result, newItem)
        }
    }
    
    fmt.Printf("Final result: %v\n", result)
}

问题的根本原因是:在Go中,切片是对底层数组的引用。当你执行item := result[i]时,itemresult[i]共享同一个底层数组。后续对item的修改可能会影响result中已存在的元素,特别是当append操作触发重新分配时。

在Playground中运行修正后的代码可以看到正确的输出。

回到顶部