在 Go 中分配切片与重新声明切片
Assigning slice vs redeclaring slice in Go
我正在尝试使用切片作为队列数据结构,我想出了这个实现,这会导致无限循环。这是因为 queue
切片没有更新子切片 queue[1:]
。
func badQueue() {
queue := []int{0,1,2,3,4,5}
for len(queue) > 0 {
current, queue := queue[0], queue[1:]
fmt.Println(current, queue)
}
}
0 [1 2 3 4 5]
0 [1 2 3 4 5]
0 [1 2 3 4 5]
0 [1 2 3 4 5]
0 [1 2 3 4 5]
...
我发现这个问题与我重新声明 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)
}
}
0 [1 2 3 4 5]
1 [2 3 4 5]
2 [3 4 5]
3 [4 5]
4 [5]
5 []
我知道是什么导致了这个问题,但我不完全理解为什么在这种情况下重新声明操作与分配的方式不同。为什么不使用队列的子切片 (queue[1:]
) 重新声明队列?
谢谢!
短声明情况下的变量 queue
的范围仅限于循环体,即为每次新迭代销毁和创建。如引用 Short variable declarations
Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-blank variables is new. As a consequence, redeclaration can only appear in a multi-variable short declaration. Redeclaration does not introduce a new variable; it just assigns a new value to the original.
因为你可以有多个同名的变量,只要它们有不同的作用域。内部作用域中的变量将影响外部作用域中的变量。
所以如果我们分解你的例子
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 使用 block scope,每个大括号对将创建一个新的作用域,如果它们具有相同的声明名称,则内部作用域中的标识符将覆盖外部作用域中的标识符。
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)
}
}
可以清楚地看到外部 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 :=
}
new compiler implementation 在这里,对于那些对细节感兴趣的人。
我正在尝试使用切片作为队列数据结构,我想出了这个实现,这会导致无限循环。这是因为 queue
切片没有更新子切片 queue[1:]
。
func badQueue() {
queue := []int{0,1,2,3,4,5}
for len(queue) > 0 {
current, queue := queue[0], queue[1:]
fmt.Println(current, queue)
}
}
0 [1 2 3 4 5]
0 [1 2 3 4 5]
0 [1 2 3 4 5]
0 [1 2 3 4 5]
0 [1 2 3 4 5]
...
我发现这个问题与我重新声明 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)
}
}
0 [1 2 3 4 5]
1 [2 3 4 5]
2 [3 4 5]
3 [4 5]
4 [5]
5 []
我知道是什么导致了这个问题,但我不完全理解为什么在这种情况下重新声明操作与分配的方式不同。为什么不使用队列的子切片 (queue[1:]
) 重新声明队列?
谢谢!
短声明情况下的变量 queue
的范围仅限于循环体,即为每次新迭代销毁和创建。如引用 Short variable declarations
Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-blank variables is new. As a consequence, redeclaration can only appear in a multi-variable short declaration. Redeclaration does not introduce a new variable; it just assigns a new value to the original.
因为你可以有多个同名的变量,只要它们有不同的作用域。内部作用域中的变量将影响外部作用域中的变量。
所以如果我们分解你的例子
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 使用 block scope,每个大括号对将创建一个新的作用域,如果它们具有相同的声明名称,则内部作用域中的标识符将覆盖外部作用域中的标识符。
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)
}
}
可以清楚地看到外部 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 :=
}
new compiler implementation 在这里,对于那些对细节感兴趣的人。