无法重新声明块范围变量。此处也声明了 。_Go 语句块与作用域
一个声明语句将程序中的实体和一个名字关联,比如一个函数或一个变量。声明语句的作用域是指源代码中可以有效使用这个名字的范围。
不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。
句法块是由花括弧所包含的一系列语句,就像函数体或循环体花括弧包裹的内容一样。句法块内部声明的名字是无法被外部块访问的。这个块决定了内部声明的名字的作用域范围。
我们可以把块(block)的概念推广到包括其他声明的群组,这些声明在代码中并未显式地使用花括号包裹起来,我们称之为词法块。
对全局的源代码来说,存在一个整体的词法块,称为全局词法块;对于每个包;每个for、if和switch语句,也都有对应词法块;每个switch或select的分支也有独立的词法块;当然也包括显式书写的词法块(花括弧包含的语句)。
声明语句对应的词法域决定了作用域范围的大小。对于内置的类型、函数和常量,比如int、len和true等是在全局作用域的,因此可以在整个程序中直接使用。任何在函数外部(也就是包级语法域)声明的名字可以在同一个包的任何源文件中访问的。对于导入的包,例如tempconv导入的fmt包,则是对应源文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问在当前源文件导入的包。还有许多声明语句,比如tempconv.CToF函数中的变量c,则是局部作用域的,它只能在函数内部(甚至只能是局部的某些部分)访问。
控制流标号,就是break、continue或goto语句后面跟着的那种标号,则是函数级的作用域。
一个程序可能包含多个同名的声明,只要它们在不同的词法域就没有关系。例如,你可以声明一个局部变量,和包级的变量同名。你可以将一个函数参数的名字声明为new,虽然内置的new是全局作用域的。但是物极必反,如果滥用不同词法域可重名的特性的话,可能导致程序很难阅读。
当编译器遇到一个名字引用时,它会对其定义进行查找,查找过程从最内层的词法域向全局的作用域进行。如果查找失败,则报告“未声明的名字”这样的错误。如果该名字在内部和外部的块分别声明过,则内部块的声明首先被找到。在这种情况下,内部声明屏蔽了外部同名的声明,让外部的声明的名字无法被访问:
func f() {}var g = "g"func main() {f := "f"fmt.Println(f) // "f"; local var f shadows package-level func ffmt.Println(g) // "g"; package-level varfmt.Println(h) // compile error: undefined: h
}
在函数中词法域可以深度嵌套,因此内部的一个声明可能屏蔽外部的声明。还有许多语法块是if或for等控制流语句构造的。下面的代码有三个不同的变量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变量,稍后我们会解释这个。(注意,后面的表达式与unicode.ToUpper并不等价。)
正如上面例子所示,并不是所有的词法域都显式地对应到由花括弧包含的语句;还有一些隐含的规则。上面的for语句创建了两个词法域:花括弧包含的是显式的部分,是for的循环体部分词法域,另外一个隐式的部分则是循环的初始化部分,比如用于迭代变量i的初始化。隐式的词法域部分的作用域还包含条件测试部分和循环后的迭代部分(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: freturn err
}
f.ReadByte() // compile error: undefined f
f.Close() // compile error: undefined f
变量f的作用域只在if语句内,因此后面的语句将无法引入它,这将导致编译错误。你可能会收到一个局部变量f没有声明的错误提示,具体错误信息依赖编译器的实现。
通常需要在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 toof.ReadByte()f.Close()
}
但这不是Go语言推荐的做法,Go语言的习惯是在if中处理错误然后直接返回,这样可以确保正常执行的语句不需要代码缩进。
要特别注意短变量声明语句的作用域范围,考虑下面的程序,它的目的是获取当前的工作目录然后保存到一个包级的变量中。这本来可以通过直接调用os.Getwd完成,但是将这个从主逻辑中分离出来可能会更好,特别是在需要处理错误的时候。函数log.Fatalf用于打印日志信息,然后调用os.Exit(1)终止程序。
var cwd stringfunc init() {cwd, err := os.Getwd() // compile error: unused: cwdif err != nil {log.Fatalf("os.Getwd failed: %v", err)}
}
虽然cwd在外部已经声明过,但是:=
语句还是将cwd和err重新声明为新的局部变量。因为内部声明的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 errorcwd, err = os.Getwd()if err != nil {log.Fatalf("os.Getwd failed: %v", err)}
}
更多:
switch i := 2; i * 4 {case 8:j := 0fmt.Println(i, j)
default:// "j" is undefined herefmt.Println(“default”)
}
// "j" is undefined here
v := "outer"
fmt.Println(v)
{v := "inner"fmt.Println(v){fmt.Println(v)}
}
{fmt.Println(v)
}
fmt.Println(v)// outer
// inner
// inner
// outer
// outer
无法重新声明块范围变量。此处也声明了 。_Go 语句块与作用域相关推荐
- python使用什么来表示不同级别的语句块-python通过什么来区分不同的语句块?
python是通过缩进格式来区分不同语句块的.Python语言利用缩进表示语句块的开始和退出(Off-side规则),增加缩进表示语句块的开始,而减少缩进则表示语句块的退出. Python语句块 1. ...
- python区分不同语句块_python通过什么来区分不同的语句块
python通过什么来区分不同的语句块 python是通过缩进格式来区分不同语句块的.Python语言利用缩进表示语句块的开始和退出(Off-side规则),增加缩进表示语句块的开始,而减少缩进则表示 ...
- python中使用什么划分语句块_Python 使用符号() 标示注释,以() 划分语句块。_学小易找答案...
[填空题]import numpy as np arr=np.array([[1,2,3],[4,5,6],[7,8,9]]) 则arr[0]为 ,arr[1][1]为 . [填空题]4 . 幂 级数 ...
- 关于VsCode中TypeScript文件提示:标识符“xxx“重复,此处也声明了“xxx“ 解决方案
问题出现场景 在学习TypeScript过程中,发现不同的.ts文件中用class声明相同的类,会报错,提示: 标识符"xxx"重复, xxx.ts文件中也声明了"xxx ...
- [C][变量作用域]语句块
概述 C语言作用域有点类似于链式结构,就是下层能访问上层声明的变量,但是上层则不能访问下层声明的变量: #include <stdio.h> #define TRUE 1int main( ...
- eclipse折叠if语句块_「03」java中的方法以及控制语句
语句块(有时叫做复合语句),是用花括号扩起的任意数量的简单Java语句.块确定了局部变量的作用域.块中的程序代码,作为一个整体,是要被一起执行的.块可以被嵌套在另一个块中,但是不能在两个嵌套的块内声明 ...
- C语言REPEAT程序,汇编语言定使用WHILE、REPEAT、FOR 和 FORC伪指令定义重复语句块
MASM 有许多循环伪指令用于生成重复的语句块:WHILE.REPEAT.FOR 和 FORC.与 LOOP 指令不同,这些伪指令只在汇编时起作用,并使用常量值作为循环条件和计数器: WHILE 伪指 ...
- 关于 Java 中 finally 语句块的深度辨析
可不能小看这个简单的 finally,看似简单的问题背后,却隐藏了无数的玄机.接下来我就带您一步一步的揭开这个 finally 的神秘面纱. 问题分析 首先来问大家一个问题:finally 语句块一定 ...
- finally语句块
finally语句块是搭配着try语句块出现的,也就说必须有try语句块才会有finally语句块,但是并不是try语句块都会搭配有finally语句块出现,我们常见的更多是try...catch.. ...
最新文章
- 解决SQL Server管理器无法连接远程数据库Error: 1326错误
- 过期时间_2020年最新航空里程过期时间及避免过期方法总结
- python pp模块_Python模块--Pexpect
- nginx编译安装与配置使用
- Sql Server远程连接
- Android音视频【十一】视频混音
- git本地仓库基本使用(Repository)
- Product Key Algorithm
- 二、NPP/VIIRS夜间灯光数据校正之投影变换、裁剪、重采样
- 概率论——马尔科夫链
- 五分钟科普:微信 PC 端多开的秘密
- 有参组装新转录本cufflinks_转录组组装软件stringtie
- Geekon移动电源概念版
- 查询大全,肯定有你需要的!
- 微信公众平台生成二维码海报是如何做到的?
- P1655 小朋友的球
- 信息增益、信息增益率、Gini
- php session header,php session header()重定向后丢失 - php
- 操作系统和数据库专业单词
- oracle erp云服务器配置,cloud介绍相关现代erp云解决方案- oracle erp cloud.pdf
热门文章
- 【java】java ssh 远程执行命令 并且获取执行的结果
- 【Elasticsearch】 es ES节点memory lock重要性与实现方式
- 【clickhouse】clickhouse 利用Grafana与系统表监控ClickHouse查询
- 【Java】44个Java代码性能优化总结
- 【Spark】SparkStreaming之windows操作
- 【kafka】scalac compiled against an incompatible version of joptsimple
- 14-win10下ElasticSearch.6.1.0与head、Kibana、X-Pack、SQL、IK、PINYIN插件的配置安装
- 【Hbase】HBase分布式安装
- java list map嵌套_Java 集合-Map集合嵌套 的遍历四种方式
- 爽到爆!阿里腾讯都在用的API管理神器,API文档、测试、Mock全搞定