在Go中分配切片与重新定义切片

在Go中分配切片与重新定义切片,go,slice,assign,declare,Go,Slice,Assign,Declare,我试图使用切片作为队列数据结构,我提出了这个实现,它产生了一个无限循环。这是因为queueslice不会使用子切片queue[1://code>进行更新 func badQueue() { queue := []int{0,1,2,3,4,5} for len(queue) > 0 { current, queue := queue[0], queue[1:] fmt.Println(current, queue) } } 我发现问

我试图使用切片作为队列数据结构,我提出了这个实现,它产生了一个无限循环。这是因为
queue
slice不会使用子切片
queue[1://code>进行更新

func badQueue() {
    queue := []int{0,1,2,3,4,5}
    for len(queue) > 0 {
        current, queue := queue[0], queue[1:]
        fmt.Println(current, queue)
    }
}
我发现问题与我重新声明
当前
队列
(使用
:=
)而不是分配值有关,这解决了问题:

func goodQueue() {
    queue := []int{0,1,2,3,4,5}
    var current int
    for len(queue) > 0 {
        current, queue = queue[0], queue[1:]
        fmt.Println(current, queue)
    }
}
我知道是什么导致了这个问题,但我不完全理解为什么在这种情况下,重新声明操作的工作方式与赋值不同。为什么不使用队列的子片(
queue[1://code>)重新声明队列


谢谢

在短声明情况下,变量
queue
的作用域仅限于循环体,即为每个新迭代销毁和创建。引述

与常规变量声明不同,短变量声明可以重新声明具有相同类型的同一块(如果块是函数体,则为参数列表)中最初声明的变量,并且至少有一个非空变量是新的。因此,重新声明只能出现在多变量短声明中。重新声明不会引入新变量;它只是将一个新值赋给原始值


因为可以有多个同名变量,只要它们的作用域不同。内部作用域中的变量将在外部作用域中隐藏变量

所以如果我们分解你的例子

func badQueue() {
    // queue from outer scope, lets call it A
    queue := []int{0,1,2,3,4,5}
    // the only visible queue here is A, so len(queue) will always refer to A
    for len(queue) > 0 {
        // same thing here, the only visible queue is A, so queue[0] and queue[1:]
        // both refer to A
        // We are also declaring new variables, queue and current
        // This queue is now shadowing the outer queue, let's call this one B
        current, queue := queue[0], queue[1:]

        // Here queue will refer to B
        fmt.Println(current, queue)

        // When the current iteration of the loop ends current and queue B is destroyed
        // because they go out of scope and the loop start over with A unchanged
    }
}

要了解这在Go中的工作原理,需要两点:

  • 范围

    Go使用时,每个括号对将创建一个新的范围,如果内部范围中的标识符具有相同的声明名称,则它们将与外部范围中的标识符形成阴影

     func main() {
         var name string = "Golang"
         fmt.Printf("Outer Scope: %s\n", name) // Print "Golang"
         {
             var name string = "Java"
             fmt.Printf("Inner Scope: %s\n", name)   // Print "Java"
         }   
         fmt.Printf("Outer Scope: %s\n", name)  // Print "Golang" again
      }
    
  • 运算符
    :=
    是一个复合操作,它将在一个语句中执行多项操作:声明、类型推断和赋值,基本上您可以将其视为语法糖。S1和S2的以下代码示例是等效的:

    func main() {
        // S1
        var name string = "Golang" 
    
        // S2
        name := "Golang"
    }
    
  • 考虑到以上两点,翻译后您的代码将如下所示:

    func badQueue() {
        queue := []int{0,1,2,3,4,5}
        for len(queue) > 0 {
            var current int
            var queue []int
            current, queue = queue[0], queue[1:]
            fmt.Println(current, queue)
        }
    }
    
    可以清楚地看到,外部
    队列
    for
    循环内部不受影响

    顺便说一句,对于
    :=
    左侧的每个变量,编译器将查找当前块范围以尝试解析标识符,如果之前已经声明了标识符,编译器将重用它,而不是创建新的标识符。但是,如果之前声明了所有lhs变量,编译器将报告一个错误,并显示消息“在:=”的左侧没有新变量”。请查看以下代码:

    func main() {
        var name string
        name, age := "Golang", 10 // reuse the above 'name' and create a new variable 'age'
    
        var tom, jerry string
        tom, jerry := "Tom", "Jerry" // no new variables on left side of :=
    }
    

    这里是为那些对细节感兴趣的人准备的。

    “我不完全理解为什么在这种情况下,重新声明操作的工作方式与分配不同。”--因为Go是。谢谢,现在它非常有意义!
    func badQueue() {
        queue := []int{0,1,2,3,4,5}
        for len(queue) > 0 {
            var current int
            var queue []int
            current, queue = queue[0], queue[1:]
            fmt.Println(current, queue)
        }
    }
    
    func main() {
        var name string
        name, age := "Golang", 10 // reuse the above 'name' and create a new variable 'age'
    
        var tom, jerry string
        tom, jerry := "Tom", "Jerry" // no new variables on left side of :=
    }