结合上面的例子讲解C语言的作用域。
1. 代码块作用域 (block scope)
位于一对花括号之间的所有语句称为一个代码块。任何在代码块的开始位置声明的标识符都具有代码块作用域 (block scope),表示它们可以被这个代码块中的所有语句访问。上图中标识为6、7、9、10的变量都具有代码块作用域。函数定义的形式参数(声明5)在函数体内部也具有代码块作用域。
当代码块处于嵌套状态时,声明于内层代码块的标识符的作用域到达该代码块的尾部便告终止。然而,如果内层代码块有一个标识符的名字与外层代码块的一个标识符同名,内层的那个标识符就将隐藏外层的标识符——外层的那个标识符无法在内层代码块中通过名字访问。声明9的f和声明6的f是不同的变量,后者无法在内层代码块中通过名字来访问。
应该避免在嵌套的代码块中出现相同的变量名。并没有很好的理由使用这种技巧,它们只会在程序的调试或维护期间引起混淆。
不是嵌套的代码块则稍有不同。声明于每个代码块的变量无法被另一个代码块访问,因为它们的作用域并无重叠之处。由于两个代码块的变量不可能同时存在,所以编译器可以把它们存储于同一个内存地址。例如,声明10的i可以和声明9的任何一个变量共享同一个内存地址。这种共享并不会带来任何危害,因为在任何时刻,两个非嵌套的代码块最多只有一个处于活动状态。
2. 文件作用域
任何在所有代码块之外声明的标识符都具有文件作用域 (file scope),它表示这些标识符从它们的声明之处直到它所在的源文件结尾处都是可以访问的。上图中的声明1和2都属于这一类。在文件中定义的函数名也具有文件作用域,因为函数名本身并不属于任何代码块(如声明4)。应该指出,在头文件中编写并通过#include指令包含到其他文件中的声明就好像它们是直接写在那些文件中一样。它们的作用域并不局限于头文件的文件尾。
3. 原型作用域
原型作用域 (prototype scope)只适用于在函数原型中声明的参数名,如上图中的声明3和声明8。在原型中(与函数的定义不同),参数的名字并非必需。但是,如果出现参数名,你可以随你所愿给它们取任何名字,它们不必与函数定义中的形参名匹配,也不必与函数实际调用时所传递的实参匹配。原型作用域防止这些参数名与程序其他部分的名字冲突。事实上,唯一可能出现的冲突就是在同一个原型中不止一次地使用同一个名字。
4. 函数作用域
最后一种作用域的类型是函数作用域 (function scope)。它只适用于语句标签,语句标签用于goto语句。基本上,函数作用域可以简化为一条规则——一个函数中的所有语句标签必须唯一。作者希望读者永远不要用到这个知识。
再举个例子
第一次调用print_time
打印的是全局变量的值,第二次直接调用printf
打印的则是main
函数局部变量的值。这里使用了全局变量,它是定义在所有函数体之外的标识符,它的作用域从定义的位置开始直到源文件结束(对应于上面的文件作用域),而main
函数局部变量的作用域仅限于main
函数之中(代码块作用域)。如上图所示,设想整个源文件是一张大纸,也就是全局变量的作用域,而main
函数是盖在这张大纸上的一张小纸,也就是main
函数局部变量的作用域。在小纸上用到标识符hour
和minute
时应该参考小纸上的定义,因为大纸(全局变量的作用域)被盖住了,如果在小纸上用到某个标识符却没有找到它的定义,那么再去翻看下面的大纸上有没有定义,例如上图中的变量x。
参考
- 《C和指针》
- Linux C编程一站式学习-全局变量、局部变量和作用域