变量的作用域是指程序代码中能够有效使用这个变量的范围。

不要将作用域和生命期混在一起。

作用域是代码中的一块区域,是一个编译期的属性;生命期是程序执行期间变量存活的时间段。在此时间段内,变量能够被程序的其他部分所引用,是执行期的概念。

语法块是包括在花括号内的一系列语句。比如函数体或者循环体。语法块内部声明的变量是无法被语法块外部代码訪问的。我们能够扩展局部语法块的概念,在某些场景下。是不须要花括号的,这样的形式称之为词法块。

词法块分为几种:全局词法块,包括全部源码。包 词法块。包括整个package;文件词法块。包括整个文件;for、if、switch语句的词法块;switch或select中的case分支的词法块。当然也包括之前提到的语法块

声明语句的词法块决定了变量的作用域。

Go语言的内置类型、内置函数、内置常量都是全局词法块,因此它们都是全局作用域的。比如int、len、true等。能够在整个程序直接使用;对于导入的包。比如temconv导入的fmt包,是文件词法块,因此仅仅能在当前文件里訪问fmt包。这里fmt是全文件范围的作用域;tempconv.CToF函数中的变量c,则是局部词法块(语法块)的,因此它的作用域是函数的内部。

控制语句后面的标签(label)。比如break、continue或goto后的标签。它们的作用域是在控制语句所在的函数内部。

一个程序可能会有多个同样的变量名,仅仅要它们的声明在不同的词法块就好。

比如。你能够在函数内声明一个局部变量x,同一时候再声明一个包级的变量x,这是在函数内部,局部变量x就会替代后者,这里称之为shadow,意味着在函数作用域内局部变量将包变量隐藏了。

当编译器遇到一个变量名的引用时,会去搜索该变量的声明语句,首先从最内部的词法块開始,然后直到全局词法块。假设编译期找不到变量名的声明语句。那么就会报错:undeclared name。假设变量名在内部的词法块和外部的词法块同一时候声明,那么依据编译期的搜索规则,内部的词法块会先找到。

在这样的情况下。内部的声明会隐藏外部的声明(shadow),此时。外部声明的变量是无法訪问的:

func f() {}

var g = "g"

func main() {
f := "f"
fmt.Println(f) // "f"; 本地变量f隐藏了包级函数f
fmt.Println(g) // "g"; 包级变量g
fmt.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变量,后面会解释。

就像之前提到的那样,不是全部的词法块都有显式的花括号。上面的for循环创建了两个词法块:带花括号的循环主体,显式词法块;还有不带花括号的隐式词法块。比如for循环的条件语句中声明一个变量i。这里i的作用域包括for的条件语句和for的主体。

以下的样例也创建了三个变量x,每一个都在不同的词法块中声明,一个在函数主体中,一个在for的隐式词法块中,另一个在显式词法块-循环主体中,这当中仅仅有1和3是显式词法块:

func main() {
x := "hello"
for _, x := range x {
x := x + 'A' - 'a'
fmt.Printf("%c", x) // "HELLO" (每次循环一个字符)
}
}

就像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中也有相似的规则:除了条件词法块外。每一个case也有自己的词法块。

对于包级变量来说,声明的顺序和作用域是无关的,全部一个包级变量声明时能够引用它自身也能够引用在它之后声明的包级变量。然而,假设一个变量或者常量在声明时引用了它自己。编译器会报错。

看以下的程序:

if f, err := os.Open(fname); err != nil { // compile error: unused: f
return err
}
f.ReadByte() // compile error: undefined f
f.Close() // compile error: undefined f

f的作用域仅仅是if语句,因此在if之外的词法块是不可訪问的,报编译错误。

这里能够更改代码,提前声明f变量:

f, err := os.Open(fname)
if err != nil {
return err
}
f.ReadByte()
f.Close()

假设不想在外部词法块声明变量,能够这么写:

if f, err := os.Open(fname); err != nil {
return err
} else {
// f and err are visible here too
f.ReadByte()
f.Close()
}

可是第三种不是Go推荐的写法。另外一种比較合适,将正常逻辑和错误处理分离。

短声明变量的作用域是要特别注意的,考虑以下的程序,開始时会获取当前的工作文件夹,保存在一个包级变量中。

这个本来能够通过在main函数中调用os.Getwd来完毕,可是用init函数将这块儿逻辑从主逻辑中分离是一个更好的选择,特别是由于获取文件夹的操作可能会是失败的,这个时候须要处理返回的错误。函数log.Fatalf会打印一条信息。然后调用os.Exit(1)终结程序:

var cwd string

func init() {
cwd, err := os.Getwd() // compile error: unused: cwd
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
}

这里cwd和err在init的词法块中都没有声明过,因此 := 语句会将它们两声明为本地变量。init内部的cwd声明会隐藏外部的,因此这个程序没有达到更新包级变量cwd的目的。

当前版本号,Go编译器会检測到本地变量cwd从未使用,因此会报错,可是这样的检查并非非常严格。比如。假设在log.Fatalf中打印cwd的值(这时本地变量cwd会被使用)。那么这样的错误就会被隐藏!!

var cwd string

func init() {
cwd, err := os.Getwd() // 注意这里的包级cwd被本地隐藏了,可是编译器没有报错!
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
log.Printf("Working directory = %s", cwd)
}

这里全局变量cwd没有得到正确的初始化,同一时候,log函数使用了cwd本地变量。隐藏了这个bug。

有一些办法能够处理这样的潜在的错误,最直接的就是避免使用:=,通过var来声明err变量:

var cwd string

func init() {
var err error
cwd, err = os.Getwd()
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
}

在这一章里,我们简单学习了包。文件。声明。语句等。

在接下来的两章,我们会学习数据结构

PS. 这一章真心非常难写,足足用了3个小时。作为质量对照大家能够參见这篇文章Scope

文章全部权:Golang隐修会 联系人:孙飞。CTO@188.com!

Go语言核心之美 1.5-作用域的更多相关文章

  1. Go语言核心之美-必读

    Go语言核心之美开篇了!.不管你是新手还是一代高人,在这个系列文章中.总能找到你想要的! 博主是计算机领域资深专家并且是英语专8水平,翻译标准仅仅有三个:精确.专业.不晦涩,为此每篇文章可能都要耗费数 ...

  2. [程序设计语言]-[核心概念]-02:名字、作用域和约束(Bindings)

    本系列导航 本系列其他文章目录请戳这里. 1.名字.约束时间(Binding Time) 在本篇博文开始前先介绍两个约定:第一个是“对象”,除非在介绍面向对象语言时,本系列中出现的对象均是指任何可以有 ...

  3. Go语言核心之美 3.2-slice切片

    Slice(切片)是长度可变的元素序列(与之相应,上一节中的数组是不可变的),每一个元素都有同样的类型.slice类型写作[]T.T是元素类型.slice和数组写法非常像,差别在于slice没有指定长 ...

  4. Go语言核心之美 1.1-命名篇

    命名篇 1.Go的函数.变量.常量.自己定义类型.包(Package)的命名方式遵循以下规则: 1)首字符能够是随意的Unicode字符或者下划线 2)剩余字符能够是Unicode字符.下划线.数字 ...

  5. Go语言核心之美 4.3-多返回值

    在Go语言中.函数能够有多个返回值,这个特性我们已经在之前的样例见过非常多,非常多标准库函数都会返回两个值,一个是期望得到的函数执行结果,另外一个是函数出错时的错误值. 以下的程序是findlinks ...

  6. JS 语言核心(JavaScript权威指南第六版)(阅读笔记)

    前言: 对于程序员,学习是无止境的,知识淘换非常快,能够快速稳固掌握一门新技术,是一个程序员应该具备的素质.这里将分享本人一点点不成熟的心得. 了解一门语言,了解它的概念非常重要,但是一些优秀的设计思 ...

  7. 第一部分 JavaScript语言核心(二)

    第四章 表达式和运算符 P66 运算符优先级,从上到下: p68 属性访问表达式和调用表达式的优先级比运算符优先级都要高,eg: typeof my.functions[x](y) //typeof在 ...

  8. 第一章:Javascript语言核心

    本节是javascript语言的一个快速预览,也是本书的第一部分快速预览. 读此书之前,感谢淘宝技术团队对此javascript核心的翻译,感谢弗拉纳根写出此书.感谢你们无私的分享,仅以此笔记献给你们 ...

  9. [java学习笔记]java语言核心----面向对象之this关键字

    一.this关键字 体现:当成员变量和函数的局部变量重名时,可以使用this关键字来区别:在构造函数中调用其它构造函数 原理:         代表的是当前对象.         this就是所在函数 ...

随机推荐

  1. UVALive 6486 Skyscrapers 简单动态规划

    题意: 有N个格子排成一排,在每个格子里填上1到N的数(每个只能填一次),分别代表每个格子的高度.现在给你两个数left和right,分别表示从左往右看,和从右往左看,能看到的格子数.问有多少种情况. ...

  2. ArcGIS api for javascript——渲染-使用唯一值渲染

    描述 本例使用唯一值渲染器来作为美国的符号.每个州有一个字符串属性"SUB_REGION"表示它的国家的地区.UniqueValueRenderer.addValue()方法被用来 ...

  3. ArcGIS api for javascript——以地理处理结果为条件查询地图

    这里发生什么任务呢?当第一次单击地图,单击的坐标被发送到一个Geoprocessor任务.该任务访问服务器上的通过ArcGIS Server 地理处理服务提供的可用的GIS模型.本例中模型计算驱动时间 ...

  4. Hive Cilent数据操作

    Hive运行命令方式有cli,jdbc.hwi.beeline.而我们经常使用的往往是cli shell 操作. cli shell hive -help hive --help 注:命令脚本必须在集 ...

  5. openVswitch(OVS)源码分析之工作流程(哈希桶结构体的解释)

    这篇blog是专门解决前篇openVswitch(OVS)源码分析之工作流程(哈希桶结构体的疑惑)中提到的哈希桶结构flex_array结构体成员变量含义的问题. 引用下前篇blog中分析讨论得到的f ...

  6. GOLANG 加密,解密,GUID 小方法

    golang的 MD5加密.BASE64解密  guid 的代码: /** * 用于加密,解密,(包含MD5加密和base64加密/解密)以及GUID的生成 * 时间: * zhifieya */ p ...

  7. JSP中 input type 用法

    JSP中 input type 用法 Input表示Form表单中的一种输入对象,其又随Type类型的不同而分文本输入框,密码输入框,单选/复选框,提交/重置按钮等,下面一一介绍. 1,type=te ...

  8. Python之路:画空心矩形

    1.if语句中当要判断多个语句成立才能执行语句时候,可以用or(或,只要其中一个成立就可以执行下一条语句)或者and(只要有其中一个不成立就不会执行下一条语句) 2.在Python中range函数会产 ...

  9. BZOJ2690: 字符串游戏(平衡树动态维护Dfs序)

    Description 给定N个仅有a~z组成的字符串ai,每个字符串都有一个权值vi,有M次操作,操作分三种: Cv x v':把第x个字符串的权值修改为v' Cs x a':把第x个字符串修改成a ...

  10. 商业模式(三):P2P网贷平台,毛利润测算

    之前谈到P2P网贷平台,主要的收入就是"息差".        一直以来,想详细写点P2P平台的收益到底如何的,奈何自己感觉收入上的点不算多,对财务这种核心机密了解的也不多,一直没 ...