理解 Go 中的变量作用域

Understanding variable scope in Go

我正在通过 Go specification 学习语言,这些要点取自 Declarations and scope 下的规范。

虽然我能理解第 1-4 点,但我对 56 点感到困惑:

  1. The scope of a constant or variable identifier declared inside a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl for short variable declarations) and ends at the end of the innermost containing block.
  2. The scope of a type identifier declared inside a function begins at the identifier in the TypeSpec and ends at the end of the innermost containing block.

这是我用来理解 Go 中作用域的代码:

package main

import "fmt"

func main() {
    x := 42
    fmt.Println(x)
    {
        fmt.Println(x)
        y := "The test message"
        fmt.Println(y)
    }
    // fmt.Println(y) // outside scope of y
}

据此我的理解是scope of xmain函数内,scope of yfmt.Println(x)之后的左右括号内,我不能在右括号外使用 y

如果我没理解错的话,这两点4 and 5说的是同一件事。所以我的问题是:

  1. 如果他们说的是同一件事,那么importance 积分?

  2. 如果它们不同,你能告诉我difference吗?

他们在两个不同的事情上提出了相同的观点,相同的规则:第一个是关于变量和常量,第二个是关于类型标识符。因此,如果您在块内声明类型,则适用与在同一位置声明的变量相同的范围规则。

除了适用于不同的东西(规则#5适用于constant- and variable declarations, rule #6 is for type declarations),在措辞上也有一个重要的区别:

  1. The scope of a constant or variable identifier declared inside a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl for short variable declarations) and ends at the end of the innermost containing block.
  2. The scope of a type identifier declared inside a function begins at the identifier in the TypeSpec and ends at the end of the innermost containing block.

这就是为什么有 2 个规则而不是一个的原因。

这是什么意思?差异意味着什么?

# 5 变量和常量声明(函数内)

声明的变量或常量的范围从声明的末尾开始。这意味着如果你正在创建一个函数变量,用匿名函数初始化它,它不能引用它自己。

这是无效的:

f := func() {
    f()
}

正在尝试编译:

prog.go:5:3: undefined: f

这是因为声明在匿名函数的右括号之后结束,所以在其中不能调用f()。解决方法是:

var f func()
f = func() {
    f()
}

这里 f 的声明结束于右括号(其类型为 func()),因此在下一行中,当我们为其分配匿名函数时,引用是有效的到 f (调用存储在 f 变量中的函数值)因为它现在在范围内。参见相关问题:

类似地,在初始化变量时,例如使用 composite literal,您不能引用其中的变量:

var m = map[int]string{
    1:  "one",
    21: "twenty-" + m[1],
}

这会产生编译时错误 ("undefined: m"),因为 m 还不在复合文字的范围内。

显然这个解决方法有效:

var m = map[int]string{
    1: "one",
}
m[21] = "twenty-" + m[1]

# 6 类型声明(函数内)

声明类型的范围从声明中的标识符开始。因此,与规则 #5 相反,在其声明中引用类型本身是有效的。

有什么优势/意义吗?

是的,您可以声明递归类型,例如:

type Node struct {
    Left, Right *Node
}

类型标识符 Node 在它出现在类型声明中之后就在范围内,类型声明以右括号结尾,但在此之前我们可以有意义地引用它。

另一个例子是一个切片类型,它的元素类型是它自己:

type Foo []Foo

您可以在此处阅读更多相关信息:How can a slice contain itself?

又一个有效的例子:

type M map[int]M