2.5.1.5 作用域
和所有的编程语言一样;变量的使用范围是存在作用域的,超过变量的作用域,外部对变量是不能进行使用的。
如果一个变量在函数体或者结构体之外进行声明的话,则此变量的作用域是全域;也被称为全局变量;全局变量,可以在这整个包或者引用该包的外部进行使用;
提示
Golang里的标识符规则,能够让包外的进行访问的变量,结构体,结构体属性,自定义类型,函数,我们称之为(exportable);其标识符的不仅要符合标识符命名规则,还必须满足首字母大小的要求,才能成为exportable。
在函数体内声明的变量,相对于上面所提到的全局变量,可称为局部变量;局部变量的作用域仅仅只存在函数体内;局部变量的作用域可以通过代码块(用大括号括起来的部分)判断,局部变量在其作用域(代码块)结束后,局部变量释放,内存地址被回收。
在同一个代码块内部(不包括嵌套的内层代码块),局部变量的变量名要求必须是唯一的,但代码块的内层代码块中可以使用相同名称的局部变量,此时;外部的原有变量将会暂时隐藏,此时,在嵌套的内层代码块里的任何的操作都只会影响内部代码块的局部变量,而对原有已经隐藏的外部同名变量没有任何影响,只会影响目前嵌套的内存代码块里声明的同名变量。 嵌套的内部代码块结束后,内部同名变量在内部代码块结束后被释放,原有隐藏外部的同名变量重新使用。
在Golang语言里,函数的参数和返回值也都是局部变量。其作用域就是整个函数体。
看看下面的示例代码
func TestVariableScope(t *testing.T) {
var i int = 100 // 外部代码变量声明i
fmt.Printf("外部代码变量声明i 开始: %d\n", i)
{
var i int = 1 // 内部代码块变量声明i
fmt.Printf("内部代码块变量声明i 开始: %d\n", i)
for {
if i == 10 {
break
}
i++ // 使用内部代码块变量i 进行++操作
}
fmt.Printf("内部代码块变量声明i 结束: %d\n", i)
}
fmt.Printf("外部代码变量声明i 结束: %d\n", i)
}
运行结果
=== RUN TestVariableScope
外部代码变量声明i 开始: 100
内部代码块变量声明i 开始: 1
内部代码块变量声明i 结束: 10
外部代码变量声明i 结束: 100
--- PASS: TestVariableScope (0.00s)
代码解析
本程序代码目的是演示局部变量的作用域;
程序代码首先在函数代码块里定义了int数据类型变量;变量名为i;并且初始化赋值i=100
在定义后,对i的值进行打印;打印使用i时,在i的同一个代码块中,可以看到打印出来的值为100; 此时打印结果 “外部代码变量声明i 开始: 100”
接下来,程序定义内部代码块{},并在内部代码块中也定义了一个变量名为i的int数据类型;并且初始化赋值i=1;此时外包已经声明的同名的变量i,被隐藏,内部的代码块作用域内都将是使用刚刚定义的新的变量i; 此时打印结果 “内部代码块变量声明i 开始: 1”
在内部代码块里,启动一个循环代码块; 循环代码块里,对变量i进行++的操作; 此时,变量i还没有退出其作用域(代码块结束);所以此时的++操作是对内部的变量i在进行,而外部的变量i已经隐藏,不受影响;
程序在循环代码块结束后,对i进行了打印;同上,由于变量i还没有退出其作用域(代码块结束);所以此时打印的i还是内部代码块定义的变量i;此时打印结果 “内部代码块变量声明i 结束: 10”
最后内部代码块结束,内部定义的变量i,随着内部代码的结束而释放;原有的外部定义的变量i重新开始使用;此时打印结果 “外部代码变量声明i 结束: 100”
技巧
全局变量的声明一定要特别注意,用有意义的名字进行命名;并且用大写进行区分;以避免上述出现的偶然的变量隐匿(Accidental Variable Shadowing)的现象
在函数体内定义的局部变量尽量不要同名,否则很容易因为一个不小心;产生偶然的变量隐匿;笔者作为一个多年的Golang开发者;曾在自己的代码过程中,由于自己的不注意;而出现这样的问题;通常由Accidental Variable Shadowing造成的程序缺陷你难以通过阅读分析缺陷地方;往往在调式过程中才被发现。特别是使用了和全局变量而产生的变量隐匿;造成的程序缺陷更是难以发现。