[Go]程序结构——作用域
来源:互联网 发布:merge算法 c语言 编辑:程序博客网 时间:2024/06/05 04:40
一个声明语句将程序中的实体和一个名字关联,比如一个函数或一个变量。声明语句的作用域是指源代码中刻意有效使用这个名字的范围。
不要将作用域和声明周期混为一谈。作用域对应的是一个源代码的文本区域,它是编译时属性;生命周期是指程序中对象存在的有效时间段,在此时间段内,它可以被程序的其它部分引用,是一个运行时的概念。
语法块是由花括号所包含的一些列语句,就像函数体或循环体那样。块内部声明的对象是无法被外部成员访问的。有一个语法块可以包含整个源代码中声明的对象,称为全局语法块;然后是每个包的包语法块;再然后是函数内部的语法块;再然后是for、if和switch语句的语法块;最后是这些语句内部的由花括号显示指定的语法块。
整个源代码>包>文件>函数>循环/分支>循环/分支内部显示花括号
声明语句对应的词法域决定了作用域范围的大小。对于内置的类型、函数和常量,比如 int、len和true等是全局作用域,因此可以在整个程序中直接使用。任何在函数外部(包级)声明的名字可以在同一个包的任何源文件中访问。对于导入的包,例如tempconv导入的fmt包,则是对应文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问此源文件导入的包。还有许多声明语句,比如tempconv.CToF函数中的变量c,则是局部作用域,它只能在函数内部(甚至是函数内部的某些部分)访问。
控制流标号,就是break、continu或goto语句后面跟着的名字,则是函数级的作用域。
一个程序可能包含多个同名的声明,只要它们在不同的词法域就没有关系。例如,你可以声明一个局部变量和包级变量同名。或者像前面所写将一个函数参数的名字声明为new,虽然内置的new是全局作用域。但是如果滥用不同词法域可同名的特性,可能导致程序很难阅读。
当编译器遇到一个名字引用时,如果它看起来像一个声明,它会从最内层的局部作用域向全局作用域查找。如果未找到,则报告“未声明的名字”这样的错误。如果该名字在内部和外部都声明过,则首先在内部被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部声明的名字无法被访问:
func f() {}var g = "g"func main() { f := "f" fmt.Println(f) // "f"; local var f shadows package-level func f fmt.Println(g) // "g"; package-level var fmt.Println(h) // compile error: undefined: h}
在函数中词法域可以深度嵌套,因此内部的一个声明可能屏蔽外部的声明。下面的代码有三个不同的变量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) } }}
在 x[i ]和 x + 'A' - 'a'声明语句的初始化表达式中,都引用了外部作用域声明的变量x,稍后我们会解释这个。正如上面例子所示,并不是所有的词法域都显示地对应到由花括号包含的语句,还有一些隐式的规则。上面的for语句创建了两个词法域:花括号包含的for循环体是显示部分,
另一个隐式部分则是循环的初始化部分,比如用于迭代变量的初始化。隐式的词法域部分还包含条件测试语和i++。
下面的例子同样有三个不同的变量x,每个声明在不同的词法域,一个在函数体词法域,一个在for隐式的初始化词法域,一个在for循环体词法域;只有两个是显示创建的。
func main() { x := "hello" for _, x := range x { x := x + 'A' - 'a' fmt.Printf("%c", x) // "HELLO" (one letter per iteration) }}
和for循环类似,if和switch语句也会在条件部分创建隐式词法域,还有他们对应的执行体词法域。下面的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语句的每个分支也有类似的词法域规则:条件部分是一个隐式词法域,然后是每个分支的词法域。在包级别声明的顺序并不会影响作用域范围,因此一个先声明的可以引用它自身或者是引用后面的一个声明,这可以让我们定义一些互相嵌套或递归的类型或函数。但是如果一个变量或常量递归引用了自身,则会产生编译错误。
if f, err := os.Open(fname); err != nil { // compile error: unused: f return err}f.ReadByte() // compile error: undefined ff.Close() // compile error: undefined f
变量f的作用域只有在if语句内,因此后面的语句将无法引用它,这将导致编译错误。通常需要在if之前声明变量,这样可以确保后面的语句依然可以访问该变量:
f, err := os.Open(fname)if err != nil { return err}f.ReadByte()f.Close()
你可能会考虑通过将ReadByte和Close移动到if的else块来解决这个问题:if f, err := os.Open(fname); err != nil { return err} else { // f and err are visible here too f.ReadByte() f.Close()}
但这不是Go语言推荐的做法,Go语言的习惯是在if中处理错误然后直接返回,这样可以确保正常执行的语句不需要代码缩进。要特别注意短变量声明语句的作用域范围,考虑下面的程序,它的目的是获取当前的工作目录然后保存到一个包级别的变量中。这本来可以直接通过调用os.Getwd完成,但是将这个从主逻辑中分离出来可能会更好,特别是在需要处理错误的时候。函数log.Fataif用于打印日志信息,然后调用os.Exit(1)终止程序。
var cwd stringfunc init() { cwd, err := os.Getwd() // compile error: unused: cwd if err != nil { log.Fatalf("os.Getwd failed: %v", err) }}
虽然cwd在外部已经生命果,但是:=语句还是将cwd重新声明为新的局部变量。因为内部声明的cwd将屏蔽外部的声明,因此上面的代码并不会正确更新包级别的变量cwd。由于当前的编译器会检测到局部变量cwd并没有使用,所以会编译失败。但是这种检测并不可靠。因为一些小的代码变更,例如增加一个局部cwd的打印语句,就可能导致这种检测失效。var cwd stringfunc init() { cwd, err := os.Getwd() // NOTE: wrong! if err != nil { log.Fatalf("os.Getwd failed: %v", err) } log.Printf("Working directory = %s", cwd)}
全局的cwd变量依然没有被正确地初始化,而且看似正常的日志输出更是让这个bug更加隐晦。有许多方式可以避免出现类似潜在的问题。最直接的方法是通过单独声明err变量,来避免使用:=的简短声明方式:
var cwd stringfunc init() { var err error cwd, err = os.Getwd() if err != nil { log.Fatalf("os.Getwd failed: %v", err) }}
- [Go]程序结构——作用域
- [Go]程序结构——命名
- [Go]程序结构——声明
- [Go]程序结构——变量
- [Go]程序结构——赋值
- [Go]程序结构——类型
- [Go]程序结构——包和文件
- Go程序结构
- GO语言程序结构
- go语言学习-程序结构
- Go基础-基本程序结构
- Go语言的程序结构
- C#——程序结构
- Go-作用域
- 《go语言圣经》之程序结构
- windows——程序结构概述
- Go 语言变量作用域
- GO声明和作用域
- linux学习笔记
- 协方差矩阵
- cropper.js 实现裁剪图片并上传(PC端)
- Android中的Apk的加固(加壳)原理解析和实现
- 暑假集训日记--8.19--树状数组
- [Go]程序结构——作用域
- for循环的一个BUG分享,希望新手小伙伴来看看
- hdu 6148 数位DP(板子 递增递减
- C++ 二维数组与指针
- 随机算法 —— 模拟退火
- 如何给cc debugger烧录固件?
- javascript函数的参数
- 设计模式-结构型模式的要点/结构/适用范围
- leetcode 654 Maximum Binary Tree C++