理解Go语言中的变量作用域

10
我正在阅读Go语言规范以学习该语言,并从以下声明和作用域规范中摘录了这些要点。

尽管我能够理解1-4点,但我对第5点和第6点感到困惑:

  1. 在函数内部声明的常量或变量标识符的作用域始于 ConstSpec 或 VarSpec(短变量声明的ShortVarDecl) 的末尾,并在最内层包含块的末尾结束。
  2. 在函数内部声明的类型标识符的作用域始于 TypeSpec 中的标识符并在最内层包含块的末尾结束。
以下是我用于了解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
}

从这个理解来看,x的范围main函数内部,而y的范围fmt.Println(x)之后的大括号内部,在闭合括号外无法使用y
如果我理解正确,那么4和5点都是在说同样的事情。所以我的问题是:
  1. 如果它们是在说同样的事情,两点之间的重要性是什么?

  2. 如果它们不同,可以告诉我它们之间的差异吗?

2个回答

7
除了适用于不同的事物(规则#5适用于常数变量声明,规则#6适用于类型声明),措辞上也有一个重要区别:
引用块:
  1. 在函数内部声明的常量或变量标识符的作用域从ConstSpec或VarSpec的结尾开始(对于短变量声明,是ShortVarDecl)并在最内层包含块的结尾处结束。
  2. 在函数内声明的类型标识符的作用域从TypeSpec中的标识符开始,并在最内层包含块的结尾处结束。
这就是为什么有两个规则而不是一个的原因。
这意味着什么?这种差异意味着什么?
#5 变量和常量声明(函数内部)
声明变量或常量的作用域从声明的结尾开始。这意味着,如果您正在创建一个函数变量,并使用匿名函数进行初始化,则它不能引用自身。
以下代码是无效的:
f := func() {
    f()
}

尝试编译:
prog.go:5:3: undefined: f

这是因为声明在匿名函数的闭合括号之后结束,所以在其中您无法调用 f()。解决方法是:
var f func()
f = func() {
    f()
}

现在这里的声明f在其类型func()的括号()中结束,因此在下一行当我们将一个匿名函数分配给它时,可以引用f(调用存储在f变量中的函数值),因为它现在在作用域中。请参见相关问题:在Go中定义一个内部递归函数
同样地,当使用组合文字初始化变量时,无法在其中引用该变量:
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

您可以在此处阅读更多信息:一个切片如何包含它自己?

另一个有效的示例:

type M map[int]M

1
这是一个重要的区别,允许类型引用自身(例如,包含指向其类型的指针的结构体),但不允许变量或常量声明引用正在声明的变量。 - Jeff Learman

6
他们正在用相同的规则针对两个不同的事物进行相同的观点阐述:第一个是关于变量和常量,第二个是关于类型标识符。因此,如果你在块内声明一个类型,与在同一位置声明变量时应用的相同作用域规则也适用于该类型。

1
关于局部作用域类型的使用,有时在较小的范围内声明一个临时类型是有用的,这样您就不必担心混淆命名空间。 - RayfenWindspear
1
或者用于本地声明的函数。但通常,本地声明的类型是相当罕见的。 - Adrian
1
在调用第三方API时,我在我的API中经常使用它们。我在函数中本地声明响应类型,因为响应是未编组的,拆卸的,并且其值仅在该函数内部使用(请参见https://mholt.github.io/json-to-go/)。这只是一个例子。我应该进行基准测试以查看本地声明的类型是否更/少开销...可能没有区别,但可能是一个有趣的实验。 - RayfenWindspear
1
我觉得匿名类型可能是更常见的解决方案,但我对此可能是错误的。 - Adrian
抱歉,我的意思是匿名的。 - RayfenWindspear
很好的回答,除了它几乎是关于两件不同事物的几乎相同点和几乎相同规则(由@icza的回答澄清)。 - Jeff Learman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接