简介
在计算机科学中,作用域(scope)是名字(name)与实体(entity)的绑定(binding)保持有效的那部分计算机程序。显然,这种名字绑定既可以是在编译时的静态绑定,也可使程序运行时的动态绑定,所产生的作用域分别称为静态作用域与动态作用域。1外部名作用域是相对内部名作用域而言的,例如在一个C程序中,外部名的作用域是整个程序,包括定义它的C源程序文件和构成该程序的其他C源文件,对于它都是可视的。
命名规则TheC Programming Language书中第二章讲到变量名时有这么一段话:
“对于内部名而言,至少前31个字符是有效的。函数名与外部变量名包含的字符数目可能小于31,这是因为汇编程序和加载程序可能会使用这些外部名,而语言本身是无法控制加载和汇编程序的。对于外部名,ANSI标准仅保证前6个字符的唯一性,并且不区分大小写。”2解释如下:
A N S I标准规定,标识符可以为任意长度,但外部名必须至少能由前6个字符唯一地区分,并且不区分大小写。这里外部名指的是在链接过程中所涉及的标识符,其中包括文件间共享的函数名和全局变量名。因此外部名abcdefgh和abcdef将被当作同一个标识符处理。
A N S I标准还规定内部名必须至少能由前31个字符唯一地区分。内部名指的是仅出现于定义该标识符的文件中的那些标识符。C语言中的字母是有大小写区别的,因此count Count COUNT是三个不同的标识符。标识符不能和C语言的关键字相同,也不能和用户已编制的函数或C语言库函数同名。
C语言标识符命名规则
所谓标识符,是指我们为变量(variable)、宏(macro),或者函数(function)等等取的名字。(在C语言中,标识符是对变量、函数标号和其它各种用户定义对象的命名。)例如 int num; 这个语句中的 num 就是一个标识符。
1.长度限制
C89 规定,编译器至少应该能够处理 31 个字符(包括 31)以内的内部标识符(internalidentifier);而对于外部标识符(external identifier),编译器至少应该能够处理 6 个字符(包括 6)以内的外部标识符。
最新的 C99 标准规定,编译器至少应该能够处理 63 个字符(包括 63)以内的内部标识符;编译器至少应该能够处理 31 个字符(包括 31)以内的外部标识符。
事实上,我们可以使用超出最大数目限制的字符来命名标识符,不过编译器会忽略超出的那部分字符。也就是说,如果我们用 35 个字符来命名变量,而那个编译器最多只能处理 31 个字符的变量名的话,那么多出的那 4 个字符就会被编译器忽略,只有前面的 31 个字符有效。有些古老的编译器只能处理 8 个字符以内的标识符,对于这样的编译器来说,标识符 kamehameha 和 kamehameko 是等价的,因为它们前面 8 个字符相等。
2. 可用字符和组合规则
标准规定,标识符只能由大小写英文字母,下划线(_),以及阿拉伯数字组成。标识符的第一个字符必须是大小写英文字母或者下划线,而不能是数字。
作用域的类别块作用域
块作用域(block scope),也即局部作用域(local scope)。3其声明区域典型为一对花括号{ }括起来的程序块。其内部声明的名字的作用域从首次声明之处至该块的结束之处。如:局部变量、局部类型等的名字。
for、while、if、switch等语句也构成了块作用域。
函数作用域
函数作用域(function scope),只适用标签(label)的名字。标签名字可以在它所出现的函数中的任何位置被goto语句使用。也就是说,在函数内部,可以先通过goto语句使用一个标签名字,之后在该函数内部才有该标签名字的定义。
函数原型作用域
函数原型作用域(function prototype scope)是指,函数原型中声明的名字只在该原型结束前可见,在原型结束处即为作用域结束之处。3例如:
voidfoo(int a, float b, vector c);
全局作用域
全局命名空间作用域(global namespace scope),也称作全局作用域(global scope)是指编译单元的最外部的声明区域中声明的名字的作用域。3这些名字在程序的各个编译单元之间均可见。全局名字是链接器需要识别的名字。一般说来,全局名字对应的实体在程序中应仅有一份,而不同编译单元都可以通过同一名字绑定到该实体上。
典型的具有全局作用域的名字,包括:全局变量、全局函数等。
C++规定,可以通过::来限定一个名字为全局作用域中的名字。这也是“全局命名空间”称谓的来源。例如:
int i; //全局变量
namespace ns{int i;}
using namespace ns;
::i=102;//给全局变量i赋值,而不是ns::i赋值。这行如果不用“::”,则编译错误: reference to 'i' is ambiguous|
文件作用域
文件作用域(file scope)是从名字声明之处直至该编译单元结束之处。静态全局变量、内联全局函数、const限定的全局变量等的名字的作用域均为典型的文件作用域。
各种类型,不论是通过typedef、class、struct、union、enum等方式声明或定义,如果不是内部类型,则名字都具有文件作用域。也就是说,同一个类型的声明与定义可以出现在不同的编译单元,在链接时不产生名字冲突。实际上,编译时并不把类型的名字放到链接器的输入文件中,链接器根本看不到类型的名字,所以也无可能产生名字冲突。甚至在不同的编译单元分别适用了同一类型名字的完全不同的定义,也不会有编译链接错误。例如:
//main.cpp
struct T{int i};
intmain(){
Tv;
v.i=101;
return 0;
}
//foo.cpp
struct T{float i};
voidfoo(){
Tv;
v.i=3.14;
}
C++引入了无名命名空间(unnamednamespace),其作用域即为当前编译单元。例如:
namespace {int i;}
也可以通过前面加上::限定访问文件作用域中的名字。
命名空间作用域
主条目:命名空间
命名空间的成员名字具有命名空间作用域(namespace scope)。该名字的作用域从在该命名空间内的声明点直至当前编译单元内该命名空间的结尾处。使用using namespace语句可以提升该命名空间此时已经声明的名字到该using语句的作用域。
使用::限定符,可以在命名空间名字的作用域内访问该命名空间的成员名字。
类作用域
类(class、struct、union)内定义的名字的作用域称为类作用域(class scope)。3这些名字在当前类的定义内部,以及类定义词法范围外的类成员定义中是可见的。因此,在类内部,成员名字可以先使用后定义,不必前向声明(forward declaration)。
类静态数据成员具有外部链接属性。
类的成员名字在其所在的类作用域内、或者派生类作用域内可见,或者通过 .运算符、->运算符、::限定符访问。
匿名类的作用域
这里的匿名类是指匿名struct、匿名class、匿名union,且没有直接用这种类型定义了变量。如果紧随这些无名类型的定义之后,定义了该类型的变量,则类型的定义及使用与普通情况完全一样;严格说,这种情形可以不算是匿名类。
匿名类的作用域有特别含义:
匿名类作为嵌套类,即匿名类在一个外部类的内部定义:则编译器就在此处定义一个该匿名类的无名变量,并把该匿名类的成员的名字提升到该类型定义所在的外部类的作用域内。由于匿名类不能使用点运算符访问其成员,所以匿名类只能有数据成员,不允许有成员函数,也不能包含私有或受保护的数据成员。如果匿名类的定义是连续嵌套,则最内部的匿名类的成员名字被提升至最外部的非匿名类或可用变量访问的成员类之处。
匿名类不作为嵌套类定义,即匿名类定义在一个全局函数内部或者全局函数外部。C/C++语言标准只允许匿名union在这种情形下定义;如果是匿名struct、匿名class,则编译报错。对于此种情形的匿名union,编译器同样在此处定义一个该匿名联合的无名变量,并把该匿名联合的成员的名字提升到该匿名联合所在的作用域内,匿名联合只能有数据成员,不允许有成员函数,也不能包含私有或受保护的数据成员。在函数外的匿名联合只能在当前编译单元内可见,因此必须使用static关键字,或者必须放在匿名命名空间中。
例如:
int main(){
union{
int test;
char c;
};
test=5; //匿名union的成员的名字提升到定义了该匿名union的作用域内。
struct { int i;} v; //匿名struct,但是紧随其后声明了一个变量v
v.i=101; // 编译通过
i=102; //编译报错: 'i' wasnot declared in this scope|
return 0;
}
枚举作用域
枚举作用域(enumeration scope)是指枚举类型的成员(enumerator)的名字的作用域,起自其声明之处,终至枚举定义结束之处。
C语言规定,枚举类型的成员(enumerator)的可见范围被提升至该枚举类型所在的作用域内。这被认为有可能污染了外部的作用域。为此,C++11引入了枚举类(enum class)解决此问题。