Go(8)作用域

这是我参与更文挑战的第12天
,活动详情查看更文挑战

作用域和生命周期的区别

  • 声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。
  • 一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。

句法块和词法块

  • 句法块是由花括弧所包含的一系列语句,就像函数体或循环体花括弧包裹的内容一样。句法块内部声明的名字是无法被外部块访问的。

  • 在代码中并未显式地使用花括号包裹起来的声明,我们称之为词法块。对全局的源代码来说,存在一个整体的词法块,称为全局词法块;对于每个包;每个for、if和switch语句,也都对应词法块;每个switch或select的分支也有独立的语法块;当然也包括显式书写的词法块(花括弧包含的语句)。

作用域

声明语句对应的词法域决定了作用域范围的大小。

  • 对于内置的类型、函数和常量,比如int、len和true等是在全局作用域的,因此可以在整个程序中直接使用。

  • 任何在在函数外部(也就是包级语法域)声明的名字可以在同一个包的任何源文件中访问的。

  • 对于导入的包,例如tempconv导入的fmt包,则是对应源文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问在当前源文件导入的包

  • 控制流标号,就是break、continue或goto语句后面跟着的那种标号,则是函数级的作用域。

  1. 当编译器遇到一个名字引用时,如果它看起来像一个声明,它首先从最内层的词法域向全局的作用域查找。

  2. 如果查找失败,则报告“未声明的名字”这样的错误。

  3. 如果该名字在内部和外部的块分别声明过,则内部块的声明首先被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部的声明的名字无法被访问:

示例一

下面的代码有三个不同的变量x,因为它们是定义在不同的词法域

func main() {
x := "hello!"
for i := 0; i < len(x); i++ {
x := x[i]
if x != '!' {
x := x + 'A' - 'a'
fmt.Printf("%c", x) // "HELLO" (one letter per iteration)
         }
     }
}

复制代码

上面的for语句创建了两个词法域:花括弧包含的是显式的部分是for的循环体部分词法域,另外一个隐式的部分则是循环的初始化部分。隐式的词法域部分的作用域还包含条件测试部分和循环后的迭代部分( i++ ),当然也包含循环体词法域。

示例二

下面的if-else测试链演示了x和y的有效作用域范围:

if x := f(); x == 0 {
fmt.Println(x)
} else if y := g(x); x == y {
fmt.Println(x, y)
} else {
fmt.Println(x, y)
}
fmt.Println(x, y) // compile error: x and y are not visible here
复制代码

第二个if语句嵌套在第一个内部,因此第一个if语句条件初始化词法域声明的变量在第二个if中也可以访问。switch语句的每个分支也有类似的词法域规则:条件部分为一个隐式词法域,然后每个是每个分支的词法域。

示例三

var cwd string
func init() {
cwd, err := os.Getwd() // NOTE: wrong!
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
log.Printf("Working directory = %s", cwd)
}
复制代码

虽然cwd在外部已经声明过,但是 := 语句还是将cwd和err重新声明为新的局部变量。因为内部声明的cwd将屏蔽外部的声明,因此上面的代码并不会正确更新包级声明的cwd变量.全局的cwd变量依然是没有被正确初始化的,而且看似正常的日志输出更是让这个BUG更加隐晦。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享