C++/作用域

维基教科书,自由的教学读本
< C++
跳到导航 跳到搜索

作用域(scope)是名字(name)与实体(entity)的绑定(binding)保持有效的那部分计算机程序。显然,这种名字绑定既可以是在编译时的静态绑定,也可使程序运行时的动态绑定,所产生的作用域分别称为静态作用域与动态作用域。C/C++是典型的静态绑定的作用域;而Emacs Lisp是典型的动态绑定的作用域。C++语言标准规定:使得特定名字保持有效的那些可能并不连续的程序文本就是该名字的作用域[參 1]名字的作用域通常从其声明之处开始,但要排除内部嵌套的声明区域中其他同名的名字的作用域。例如:

int j;         //全局变量j
int main() {
 int j;        //局部变量j。注意,从这个局部变量j的声明之处开始,直至该函数结束之处,被排除出全局变量j的作用域
 j = 42;
}

C++语言有下述名字作用域:

块作用域[编辑]

块作用域(block scope),也即局部作用域(local scope)。[參 2]声明区域典型为一对花括号{ }括起来的程序块。其内部声明的名字的作用域从首次声明之处至该块的结束之处。如:局部变量、局部类型等的名字。

for、while、if、switch等语句也构成了块作用域。

函数作用域[编辑]

函数作用域(function scope),只适用标签(label)的名字。标签名字可以在它所出现的函数中的任何位置被goto语句使用。也就是说,在函数内部,可以先通过goto语句使用一个标签名字,之后在该函数内部才有该标签名字的定义。

函数原型作用域[编辑]

函数原型作用域(function prototype scope)是指,函数原型中声明的名字只在该原型结束前可见,在原型结束处即为作用域结束之处。[參 3]例如:

 void foo(int a, float b, vector<int> c);

全局作用域[编辑]

全局命名空间作用域(global namespace scope),也称作全局作用域(global scope)是指编译单元的最外部的声明区域中声明的名字的作用域。[參 4]这些名字在程序的各个编译单元之间均可见。全局名字是链接器需要识别的名字。一般说来,全局名字对应的实体在程序中应仅有一份,而不同编译单元都可以通过同一名字绑定到该实体上。

典型的具有全局作用域的名字,包括:全局变量、全局函数等。

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};
 int main(){
   T v;
   v.i=101;
   return 0;
 }
 // foo.cpp
 struct T{float i};
 void foo(){
   T v;
   v.i=3.14;
 }

C++引入了无名命名空间(unnamed namespace),其作用域即为当前编译单元。例如:

namespace {int i;}

也可以通过前面加上::限定访问文件作用域中的名字。

命名空间作用域[编辑]

命名空间的成员名字具有命名空间作用域(namespace scope)。[參 5]该名字的作用域从在该命名空间内的声明点直至当前编译单元内该命名空间的结尾处。使用using namespace语句可以提升该命名空间此时已经声明的名字到该using语句的作用域。

使用::限定符,可以在命名空间名字的作用域内访问该命名空间的成员名字。

类作用域[编辑]

类(class、struct、union)内定义的名字的作用域称为类作用域(class scope)。[參 6]这些名字在当前类的定义内部,以及类定义词法范围外的类成员定义中是可见的。因此,在类内部,成员名字可以先使用后定义,不必前向声明(forward declaration)。

类静态数据成员具有外部链接属性

类的成员名字在其所在的类作用域内、或者派生类作用域内可见,或者通过 .运算符、->运算符、::限定符访问。

匿名类的作用域[编辑]

这里的匿名类是指匿名struct、匿名class、匿名union,且没有直接用这种类型定义了变量。如果紧随这些无名类型的定义之后,定义了该类型的变量,则类型的定义及使用与普通情况完全一样;严格说,这种情形可以不算是匿名类。

匿名类的作用域有特别含义:

  • 如果匿名类作为嵌套类,即匿名类在一个外部类的内部定义:则编译器就在此处定义一个该匿名类的无名变量,并把该匿名类的成员的名字提升到该类型定义所在的外部类的作用域内。由于匿名类不能使用点运算符访问其成员,所以匿名类只能有数据成员,不允许有成员函数,也不能包含私有或受保护的数据成员。如果匿名类的定义是连续嵌套,则最内部的匿名类的成员名字被提升至最外部的非匿名类或可用变量访问的成员类之处。
  • 如果匿名类不作为嵌套类定义,即匿名类定义在一个函数内部或者全局函数外部。在这种情形下C/C++语言标准只允许定义匿名union;如果定义匿名struct、匿名class,则编译报错。对于此种情形的匿名union,编译器同样在此处定义一个该匿名联合的无名变量,并把该匿名联合的成员的名字提升到该匿名联合所在的作用域内,匿名联合只能有数据成员,不允许有成员函数,也不能包含私有或受保护的数据成员。在函数外的匿名union只能在当前编译单元内可见,因此必须使用static关键字,或者必须放在匿名命名空间中(对于Visual C++在匿名命名空间中也必须有static关键字)。

例如:

int main(){
 union{
         int test;
         char c;
      };         
  test=5;  //匿名union的成员的名字提升到定义了该匿名union的作用域内。
  struct { int i;} v; //匿名struct,但是紧随其后声明了一个变量v
  v.i=101;  // 编译通过
  i=102;   //编译报错: 'i' was not declared in this scope| 
  return 0;
}

枚举作用域[编辑]

枚举作用域(enumeration scope)是指枚举类型的成员(enumerator)的名字的作用域,起自其声明之处,终至枚举定义结束之处。[參 7]

C语言规定,枚举类型的成员(enumerator)的可见范围被提升至该枚举类型所在的作用域内。这被认为有可能污染了外部的作用域。为此,C++11引入了枚举类(enum class)解决此问题。

模板参数作用域[编辑]

模板参数作用域(template parameter scope)是指,模板的形参从其声明之处直至模板定义结束之处可见。[參 8]

模板的模板参数自身也有模板参数(template parameter of a template template-parameter),其名字作用域是包含该名字的最小的模板形参列表。[1]

名字隐藏[编辑]

一个名字可以被隐藏(name hiding),这是通过在它的作用域内的嵌套的声明区域中或者派生类中声明一个同样的名字。

类名或枚举名可以被作用域中声明的变量、数据成员、函数、枚举成员的名字隐藏掉。而不论这两大类名字声明的先后顺序。例如:

  int i=101;
  struct i{double  k;};
  std::cout<<sizeof(i); //输出为4

使用using namespace语句提升作用域的名字,会被该using namespace所在的声明区域中的名字隐藏掉。例如:

#include <iostream>
namespace ns{int i=102;}
int main(){
   int i=101;
   using namespace ns;
   std::cout<<i;  //输出: 101
   return 0;
}

参考文献[编辑]

  1. C++11语言标准,3.3.1.1:Declarative regions and scopes: In general, each particular name is valid only within some possibly discontiguous portion of program text called its scope.
  2. C++11语言标准,§3.3.3:Block scope
  3. C++11语言标准,§3.3.4 Function prototype scope
  4. C++11语言标准,§3.3.6.3: The outermost declarative region of a translation unit is also a namespace, called the global namespace. A name declared in the global namespace has global namespace scope (also called global scope).
  5. C++11语言标准,§3.3.6 Namespace scope
  6. C++11语言标准,§3.3.7 Class scope
  7. C++11语言标准,§3.3.8 Enumeration scope
  8. C++11语言标准,§3.3.9 Template parameter scope

Category:C++

  1. C++11 3.3.9.1:The declarative region of the name of a template parameter of a template template-parameter is the smallest template-parameter-list in which the name was introduced.