作用域
作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。编译器可以确认4种不同类型的作用域:文件作用域,函数作用域,代码块作用域,原型作用域。
代码块作用域
位于一对花括号之间的语句称为语句块,任何在代码块的开始位置声明的标识符都具有代码块作用域,表示它们可以被这个代码块中的所有语句访问。
当代码块嵌套时,声明于内层代码块的标识符的作用域到达该代码块的尾部就结束,如果内层代码块有一个标识符与外层的代码块的标识符同名,内层的标识符将隐藏外层的标识符。
文件作用域
任何在所有代码块之外声明的标识符都具有文件作用域(file scope)。并且,通过#include指令包含到其他文件中的声明就好像它们是直接写在那些文件中一样。它们的作用域并不局限于头文件的文件尾。
原型作用域
原型作用域(prototype scope)只适用于在函数原型中声明的参数名,将它独立出来只是为了这个名字不能与其他作用域的符号混淆,它其实没有什么用,甚至都可以不写。事实上,唯一可能出现的冲突就是在同一个原型中不止一次使用同一个名字。
函数作用域
它只适用于语句标签,用于goto语句。
命名空间分类
对属于同一命名空间(Name Space) 的重名标识符,内层作用域的标识符将覆盖外层作用域的标识符,例如局部变量名在它的函数中将覆盖重名的全局变量。命名空间可分为以下几类:
(1)语句标号单独属于一个命名空间。例如在函数中局部变量和语句标号可以重名,互不影响。由于使用标号的语法和使用其它标识符的语法都不一样,编译器不会把它和别的标识符弄混。
(2)struct, enum和union(下一节介绍union)的类型Tag属于一个命名空间。由于Tag前面总是带struct, enum或union关键字,所以编译器不会把它和别的标识符弄混。
(3)struct和union的成员名属于一个命名空间。由于成员名总是通过.或->运算符来访问而不会单独使用,所以编译器不会把它和别的标识符弄混。
(4)所有其它标识符,例如变量名、函数名、宏定义、 typedef的类型名、 enum成员等等都属于同一个命名空间。如果有重名的话,宏定义覆盖所有其它标识符,因为它在预处理阶段而不是编译阶段处理,除了宏定义之外其它几类标识符按上面所说的规则处理,内层作用域覆盖外层作用域。
标识符的链接属性(Linkage)
(1)none(无)
总是被当做单独的个体,也就是说该标识符的多个声明被当作独立不同的实体。
(2)internal(内部)
在同一个源文件中的所有声明中都指向同一个实体,但位于不同源文件的多个声明则分属不同的实体。
(3)external(外部):
不论声明多少次、位于几个源文件都表示同一个实体。
extern和static两个关键字可以用于设定标识符的链接属性。当没有这两个关键字时,默认的链接属性与标识符的作用域相关。
typedef char *a;
int b;
int c(int d)
{
int e;
int f(int g);
...
}
上面的代码块中,b、c、f有external链接属性,f在本代码中被调用,定义在其他源文件或者库中,所以也是external属性。其他的标识符都是none属性。
static关键字可以把一个默认为external属性的标识符改为internal,如上例中,可以把b、c的链接属性改为internal,使其在其他源文件中不可见。
static int b;
static int c(int d);
//extern 关键中可以把none属性改为external属性。
// linkage_test1.c
#include<stdio.h>
extern int a; // 可选,因为默认就是external
// 但是应该写上,增加程序可读性
int main() {
printf("a = %d\n", a);
extern int b; // 必需,默认为none
printf("b = %d\n", b);
}
最后,当extern关键字用于源文件中一个标识符的一次声明时,它指定该标识符具有external链接属性,但是,如果它用于该标识符的第2次或者以后的声明时,它并不会更改由第一次声明所指定的链接属性。如下例所示:
static int i;
int func()
{
extern int i; //i的链接属性仍然为static
}