Golang 混合赋值和声明

Golang mixed assignment and declaration

我开始使用 go 工作了几个星期,并且(再一次)我偶然发现了一些对我来说似乎很奇怪的东西:

// Not working
a := 1
{
    a, b := 2, 3
}

// Works
a := 1
a, b := 2, 3

playground

我想同时赋值两个变量。 一个已经声明了,在更高级的范围内,另一个还没有。

它不起作用:编译器试图重新声明以前的变量。 但是,如果在同一范围内声明此变量,它会正常工作。

这是为什么?

根据 golang the documentation:

An identifier declared in a block may be redeclared in an inner block.

这正是您的示例所显示的内容,a 在括号内重新声明,因为“:=”,并且从未使用过。

一个解决方案是声明两个变量然后使用它:

var a, b int
{
    b, a = 2, 3
    fmt.Println(b)
}
fmt.Println(a)

您所遇到的通常称为 "variable shadowing"。当您将 := 与内部范围内的任何变量一起使用时,包括 iffor 这样的语句,尽管没有大括号,新的值和类型与该变量相关联:

n := "Example"
//Prints the string variable `n` to standard output and
// returns the number of bytes written in int variable `n` and
// an error indicator in error variable `err`.
if n, err := fmt.Println(n); err != nil {
    panic(err)
} else {
    fmt.Println(n, "bytes written")
}

//Prints the string variable `n` to standard output.
fmt.Printf("n = %q\n", n)

输出:

Example
8 bytes written
n = "Example"

有几种不同的方法可以解决此问题:

  • 在使用之前声明您需要的变量,并使用 =
  • 的正常赋值
  • 使用不同的变量名
  • 创建一个新的范围并保存变量的值以供以后访问,根据需要使用带有 := 的变量名,并在范围结束之前恢复值;通常只使用不同的变量名会更容易,因为无论如何你都要创建另一个变量

也可能发生相反的效果,即您在内部范围内声明了某些内容但没有意识到:

if _, err := fmt.Println(n); err != nil {
    panic(err)
} else {
    fmt.Println(n, "bytes written")
}

//undefined: err
if _, err = fmt.Println(n); err != nil {
    //undefined: err
    panic(err)
}

同样,有几种不同的方法可以解决此问题:

  • 在使用之前声明您需要的变量,并使用 =
  • 的正常赋值
  • 将第一个 :=if 语句分开,因此变量按预期声明;这允许您在该范围的上下文中以及包含它的任何范围内对该变量的所有其他实例使用 =
  • = 的所有实例更改为 := 以修复错误

请注意,当函数 returns 有多个值时,您可能会在最后两种情况中的任何一种情况下遇到变量阴影问题,但这可以按照上述说明解决。

Try both examples on the Go Playground.

您的最后一个示例说明了声明和初始化新变量 b 的组合,同时还为现有变量 a 赋值。没有创建新的作用域,因此您不会隐藏原始变量 a,您可以 verify by printing the address of a after each assignment (but before the next declaration/assignment):

a := 1
fmt.Println(&a)
a, b := 2, 3
fmt.Println(&a)
a = b          // avoids a "declared but not used" error for `b`

当然,如果你没有声明b,那么你会从编译器收到一个错误,即在第二个声明的:=左侧没有新变量,这是一种迂回的说法,表示您正试图在同一范围内声明 a 两次。

请注意,如果仔细应用此想法,也可用于查找被隐藏的变量。例如,您示例中的 "not working" 代码将 print different addresses for a,具体取决于内部范围内的 a 是否已声明:

a := 1
{
    fmt.Println(&a)    // original `a`
    a, b := 2, 3
    fmt.Println(&a)    // new `a`
    a = b              // avoids a "declared but not used" error for `b`
}
fmt.Println(&a)        // original `a`

你的问题有两部分:
第一部分:
= 只是赋值
:= 是在功能块(非全局)内为新变量(至少一个新变量)定义和分配的,工作示例:

package main

import (
    "fmt"
)

func main() {
    var u1 uint32      //declare a variable and init with 0
    u1 = 32            //assign its value
    var u2 uint32 = 32 //declare a variable and assign its value at once
    //declare a new variable with defining data type:
    u3 := uint32(32)        //inside the function block this is equal to: var u3 uint32 = 32
    fmt.Println(u1, u2, u3) //32 32 32
    //u3 := 20//err: no new variables on left side of :=
    u3 = 20
    fmt.Println(u1, u2, u3)       //32 32 20
    u3, str4 := 100, "str"        // at least one new var
    fmt.Println(u1, u2, u3, str4) //32 32 100 str
}

第二部分:
在块中声明的标识符可以在内部块中重新声明。
这里有 4 个不同的变量范围和阴影工作示例:

限制变量范围的简单方法:

package main
import "fmt"
func main() {
    i := 1
    j := 2
    //new scope :
    {
        i := "hi" //new local var
        j++
        fmt.Println(i, j) //hi 3
    }
    fmt.Println(i, j) //1 3
}

使用函数调用限制变量范围:

package main
import "fmt"
func fun(i int, j *int) {
    i++                //+nice: use as local var without side effect
    *j++               //+nice: intentionally use as global var
    fmt.Println(i, *j) //11 21
}
func main() {
    i := 10 //scope: main
    j := 20
    fun(i, &j)
    fmt.Println(i, j) //10 21
}

在语句中使用速记赋值:

package main
import "fmt"
func main() {
    i := 10 //scope: main
    j := 4
    for i := 'a'; i < 'b'; i++ {
        fmt.Println(i, j) //97 4
    }
    fmt.Println(i, j) //10 4

    if i := "test"; len(i) == j {
        fmt.Println(i, j) // i= test , j= 4
    } else {
        fmt.Println(i, j) //test 40
    }
    fmt.Println(i, j) //10 4
}

隐藏全局变量:

package main
import "fmt"
var i int = 1 //global
func main() {
    j := 2
    fmt.Println(i, j) //1 2
    i := 10           //Shadowing global var
    fmt.Println(i, j) //10 2
    fun(i, j)         //10 2
}
func fun(i, j int) {
    //i := 100   //no new variables on left side of :=
    fmt.Println(i, j) //10 2
}

简而言之:由于a, b = 2, 3表示"assign both"而a, b := 2, 3表示"declare and assign both",您需要分配一个并声明并分配另一个,解决方案是声明另一个并分配两者:

a := 1
{
    var b int
    a, b = 2, 3
}