ISO/IEC 14882:2011之条款3.3——作用域
3.3 作用域
3.3.1 声明区域和作用域
1、每个名字在被称为一个声明区域的程序文本的某个部分中被引入,声明区域是程序的最大部分,在那个声明区域中名字是有效的,也就是说,在那个声明区域中,那个名字可以被用作为一个非限定的名字,来引用同一个实体。通常,每个特定的名字只在被称为其作用域的程序文本的某些可能不连续的部分内有效。为了确定一个声明的作用域,有时引用一个声明的潜在作用域是方便的。一个声明的作用域与其潜在的作用域相同,除非潜在的作用域包含同一个名字的另一个声明。在那种情况下,在内部(所包含的)声明区域中的声明的潜在作用域从在外部(包含)声明区域中的声明的作用域排除。
2、[例:在
int j = 24; int main() { int i = j, j; j = 42; }
中,标识符j作为一个名字被声明两次(并被使用两次)。第一个j的声明区域包含了整个例子。第一个j的潜在区域紧跟在j之后开始,并延伸到程序末尾,但其(实际的)作用域排除了在,和}之间的文本区域。第二个j的声明区域(在分号之前的j)包含了在{和}之间的所有文本,但其潜在作用域排除了i的声明[译者注:即排除了j的逗号之前的区域],j的第二个声明的作用域与其潜在作用域相同。 ——例结束]
3、由一个声明所声明的一个名字被引入到该声明发生的作用域中,除了存在一个friend指定符(11.3),elaborated-type-specifier(7.1.6.3),以及using-directive(7.3.4)改变此通用行为。
4、在一单个声明区域中给定一组声明,其中每个指定了相同的非限定名字,
——它们都应该引用同一个实体,或都引用函数或函数模板;或
——恰好一个声明应该声明一个类名或枚举名,这些名字并不是typedef名字,并且其余声明都应该引用同一个变量或枚举符,或都引用函数或函数模板;在这种情况下,类名或枚举名并隐藏(3.3.10)。[注:一个名字空间的名字或一个类模板的名字在其声明区域中必须是唯一的(7.3.2,条款14)。 ——注结束]。
[注:这些限制应用到一个名字被引入的声明区域中,该名字声明区域不必要与声明发生的区域相同。特别地,elaborated-type-specifier(7.1.6.3)以及友元声明(11.3)可以将一个(可能不可见的)名字引入到一个封闭的名字空间中;这些限制应用到那个区域。局部的外部声明(3.5)可以将一个名字引入到声明区域,声明出现在那个声明区域,并也将一个(可能不可见的)名字引入到封闭的名字空间中;这些限制应用到全部这两个区域。 ——注结束]
5、[注:名字查找规则在3.4中概述。 ——注结束]
3.3.2 声明点
1、一个名字的声明点立即跟在其完整的声明符(条款8)之后以及初始化器(如果有)之前,除了以下说明的[例:
int x = 12; { int x = x; }
这里,第二个x用其自己的(未确定的)值来初始化。 ——例结束]
2、[注:来自一个外部作用域的一个名字一直到隐藏它的那个名字的声明点一直可见。[例:
const int i = 2; { int i[i]; }
声明了含有两个整型的一个语句块作用域的数组。
——例结束] ——注结束]
3、由一个class-specifier第一次所声明的一个类或类模板的声明点立即在其class-head(条款9)中的identifier或simple-template-id之后(如果有的话)。一个枚举的声明点立即在其enum-specifier(7.2)或其第一个opaque-enum-declaration(7.2)中的identifier(如果有的话)之后。一个别名或别名模板的声明点立即跟在type-id之后,该别名引用此type-id。
4、一个枚举符的声明点立即在其enumerator-definition之后。[例:
const int x = 12; { enum { x = x }; }
这里,枚举符x用常量x的值来初始化,命名为12. —— 例结束]
5、在一个类成员的声明点之后,成员名可以在其类的作用域中查找。[注:这是真的,即使类是一个不完整的类。比如,
struct X { enum E { z = 16 }; int b[X::z]; // OK };
—— 注结束]
6、在一个elaborated-type-specifier中首次声明的一个类的声明点如下:
——对于如下形式的声明
class-key attribute-specifier-seqopt identifier;
identifier被声明为含有此声明的作用域中的一个class-name,否则
——对于如下形式的一个elaborated-type-specifier
class-key identifier
如果elaborated-type-specifier被用在decl-specifier-seq中或在名字空间作用域中定义的一个函数的parameter-declaration-clause中,那么identifier被声明为在含有那个声明的名字空间中的一个class-name;否则,identifier被声明在含有那个声明的最小名字空间或语句块作用域中。[注:这些规则也应用在模板里。 ——注结束][注:elaborated-type-specifier的其它形式并不声明一个新的名字,并从而必须引用一个已存在的type-name。见3.4.4和7.1.6.3。 ——注结束]
7、一个injected-class-name(条款9[译者注:条款9的第二条])的声明点立即跟在类定义的大括号的后面。
8、一个函数局部预定义变量(8.4)的声明点立即放在一个函数定义的函数体之前。
9、一个模板形参的声明点立即跟在其完整的template-parameter之后。[例:
typedef unsigned char T; template<class T = T // 查找发现了unsigned char的typedef的名字 , T // 查找发现了模板形参 N = 0> struct A { };
——例结束]
10、[注:友元声明引用作为最近封闭的名字空间的函数或类,但它们并不将新的名字引入到那个名字空间中(7.3.1.2)。语句块作用域中的函数声明以及语句块中带有extern指定符的变量声明引用作为一个封闭的名字空间的成员的声明,但它们并不将新的名字引入到那个作用域中。 ——注结束]
11、[注:对于一个模板的实例化点,见14.6.4.1。 ——注结束]
3.3.3 语句块作用域
1、一个语句块(6.3)中声明的一个名字对那个语句块是局部的;该名字具有块作用域。其潜在作用域起始于其声明点(3.3.2)并结束于其语句块的末尾。声明在一个语句块作用域中的一个变量是一个局部变量。
2、一个函数形参名字(包括出现在一个lambda-declarator中的)的潜在作用域,或在一个函数定义(8.4)中的一个函数局部预定义的变量的潜在作用域起始于其声明点。如果函数具有一个function-try-block,那么一个形参的潜在作用域或一个函数局部预定义变量的潜在作用域结束于最后相关联的处理例程的末尾,否则它结束于函数定义的最外部语句块的末尾。一个形参名字不应该在函数定义的最外部语句块中重新声明,也不应该与一个function-try-block相关联的任一处理例程的最外部语句块中重新声明。
3、在一个exception-declaration中声明的名字对该处理例程是局部的,并且不应该在此处理例程的最外部语句块中被重新声明。
4、在for-init-statement,for-range-declaration,以及if,while,for和switch语句的条件中所声明的名字,对于if,while,for或switch语句(包括受控制的语句)是局部的,并且不应该在那条语句的一个后续条件中被重新声明,也不应该在受控语句的最外部语句块(或,对于if语句,任一最外部的语句块)中重新声明;见6.4。
3.3.4 函数原型作用域
1、 在一个函数声明,或在任意一个函数声明符——除了一个函数定义的声明符(8.4)以外——中,形参的名字(如果提供的话)具有函数原型作用域,而此作用域终结于最近的封闭的函数声明符的末尾处。
3.3.5 函数作用域
1、标签(6.1)具有函数作用域,并且可以被使用在它们所被声明的函数中的任一地方。只有标签具有函数作用域。
3.3.6 名字空间作用域
1、一个namespace-definition的声明区域是其namespace-body。由一个original-namespace-name所指示的潜在作用域是由每个带有original-namespace-name的同一声明区域中的namespace-definition所建立的声明区域的联结。在一个namespace-body中所声明的实体被称为是名字空间的成员,并且由这些在名字空间的声明区域中的声明所引入的名字被称为是名字空间的成员名。一个名字空间成员名具有名字空间作用域。其潜在作用域包括其名字空间,从名字的声明点(3.3.2)一直向后;并且对于每个支配成员的名字空间的using-directive(7.3.4),成员的潜在作用域包括跟在成员的声明点之后的using-directive的潜在作用域的那个部分。[例:
namespace N { int i; int g(int a) { return a; } int j(); void q(); } namespace { int l = i; } // l的潜在作用域从其声明点到翻译单元的结束 namespace N { int g(char a) { // 重载了N::g(int) return l + a; // l来自未命名的名字空间 } int i; // 错误:重复定义 int j(); // OK:重复的函数声明 int j() { // OK:对N::j()的定义 return g(i); // 调用N::g(int) } int q(); // 错误:不同的返回类型 }
——例结束]
2、一个名字空间成员也可以在::作用域解决操作符(5.1)之后被引用,该操作符应用于其名字空间的名字,或是在一个using-directive中指定成员的名字空间的一个名字空间的名字;见3.4.3.2。
3、一个翻译单元的最外部声明区域也是一个名字空间,被称为全局名字空间。在全局名字空间中声明的一个名字具有全局名字空间作用域(也被称为全局作用域)。这么一个名字的潜在作用域起始于其声明点(3.3.2)并结束于作为其声明区域的翻译单元的末尾。带有全局名字空间作用域的名字被称为全局名字。
3.3.7 类作用域
1、以下规则描述了在类中所声明的名字的作用域。
1)在一个类中声明的一个名字的潜在作用域不仅仅由跟在名字声明点后面的声明区域构成,而且也由在那个类中的所有函数体,非静态数据成员的brace-or-equal-initializer[译者注:见附录A.7的最后部分],以及默认实参构成(包括嵌套类中的那些东西)。
2)用在一个类S中的一个名字N应该引用在其上下文中的同一个的声明,以及当N在S的完整的作用域中被重新计算时。对于这个规则的违背不要求具有诊断信息。
3)如果在一个类中重新编排成员声明而产生了一个在规则(1)和(2)下交替的有效的程序,那么程序是不良形式的,不要求具有诊断信息。[译者注:
class A { int a = c + b; const int c = 100; int b = c + a; public: A(void) { cout << "a = " << a << endl; cout << "b = " << b << endl; } };
对于上述代码,支持C++2011的Apple LLVM3.0编译器没有任何warning,输出为:a = 0, b = 100。如果将a和b的声明换个位置,那么它们的值也会被交换。这个程序是不良形式的,但编译器不需要给出诊断信息。
]
4)在一个成员函数内声明的一个名字隐藏相同名字的一个声明,该名字的作用域延伸到或越过成员函数的类的末尾。
5)延伸至或越过一个类定义的末尾的一个声明的潜在作用域也延伸至由其成员定义所定义的区域,即使这些成员在类的外部在词法上被定义(这包括了静态数据成员定义,嵌套类定义,成员函数定义(包括成员函数体和这些定义的声明符部分的任一部分,这些定义跟在declarator-id,包含一个parameter-declaration-clause以及任意默认实参(8.3.6))。)[例:
typedef int c; enum { i = 1 }; class X { char v[i]; // 错误:i引用::i,但当被重新计算时引用X::i int f() { return sizeof(c); } // OK:X::c char c; enum { i = 2 }; }; typedef char* T; struct Y { T a; // 错误:T引用::T,但当被重新计算时引用Y::T typedef long T; T b; }; typedef int I; class D { typedef I I; // 错误:即使没有涉及到重新编排 };
——例结束]
2、一个类成员的名字应该仅被用于以下情况:
——在其类的作用域中(如上述描述的)或一个从其类派生的一个类(条款10),
——在应用于其类类型或从其类派生的一个类类型的一个表达式的 . 操作符之后,
——在应用于指向其类(5.2.5)的一个对象或从其类派生的一个类的对象的一个指针的 -> 操作符之后,
——在应用于其类的名字或从其类派生的一个类的名字的 :: 作用域解决操作符(5.1)之后。
3.3.8 枚举作用域
1、在作用域范围内的一个枚举符的名字具有枚举作用域。其潜在作用域起始于其声明点并结束于enum-specifier的末尾。
3.3.9 模板形参作用域
1、一个模板template-parameter的一个模板形参的名字的声明区域是最小的template-parameter-list,该名字在此区域中被引入。
2、一个模板的一个模板形参的名字的声明区域是最小的template-declaration,该名字在这个区域中被引入。只有模板形参名属于此声明区域;由一个template-declaration的declaration所引入的任一其它种类的名字被引入到同一个声明区域,在那个声明区域中该名字作为同一个名字的一个非模板声明的一个结果而被引入。[例:
namespace N { template<class T> struct A { }; // #1 template<class U> void f(U) { } // #2 struct B { template<class V> friend int g(struct C*); // #3 }; }
T、U和V的声明区域分别是行#1、#2和#3的template-declaration。 但A,f和g都属于同一个声明区域——也就是N的namespace-body。(g仍然被认为是属于此声明区域,尽管在被限定的和非限定的名字查找期间它被隐藏。)
——例结束]
3、 一个模板形参名的潜在作用域起始于其声明点(3.3.2)并结束于其声明区域的末尾。[注:这暗示了一个template-parameter可以被用在后面的template-parameter和它们的默认实参的声明中,但不能用在前面的template-parameter或其默认的实参中。例如,
template<class T, T* p, class U = T> class X { } template<class T> void f(T* p = new T);
这也暗示了一个template-parameter可以被用在基类的说明中。比如,
template<class T> class X : public Array<T> { } template<class T> class Y : public T { }
一个模板形参作为一个基类而使用,暗示了被用作为一个模板实参的一个类必须被定义,而不只是被声明,当该类模板被实例化时。 ——注结束]
4、 一个模板形参的名字的声明区域被嵌套在立即封闭的声明区域的内部。[注:作为一个结果,一个template-parameter隐藏了在一个封闭的作用域(3.3.10)中带有相同名字的任一实体。[例:
typedef int N; template<N X, typename N, template<N Y> class T> struct A;
这里,X是一个类型int的非类型模板形参,而Y是一个具有与A的第二个模板形参相同类型的非类型模板形参。 ——例结束] ——注结束]
5、[注:因为一个模板形参的名字不能在其潜在的作用域内被重新声明(14.6.1),所以一个模板的形参的作用域经常是它的潜在作用域。然而,对于一个模板形参名来说,仍然有可能被隐藏;见14.6.1。 ——注结束]
3.3.10 名字隐藏
1、一个名字可以通过在一个嵌套的声明区域或派生类中的相同名字的一个显式声明而被隐藏。(10.2)
2、一个类名(9.1)或枚举名(7.2)可以被在同一个作用域中所声明的一个变量名,数据成员名,函数名,或枚举符名隐藏。如果一个类或枚举名与一个变量、数据成员、函数、或枚举符在同一个作用域中(以任一次序)被声明,带有相同的名字,那么该类或枚举名被隐藏,只要在变量、数据成员、函数或枚举符名可见的地方。
3、在一个成员函数定义中,在语句块作用域中的一个名字的声明隐藏了带有相同名字的类的一个成员的声明;见3.3.7。在一个派生类中的一个成员的声明(条款10)隐藏了相同名字的一个基类的一个成员的声明;见10.2
4、在被一个名字空间名限定的一个名字的查找期间,通过一个using-directive而变得可见的声明可以在包含using-directive的名字空间中带有相同名字的声明所隐藏;见(3.4.3.2)。
5、如果一个名字在作用域中,并且不被隐藏,那么它被称为可见的。