作用域

来自cppreference.com
< cpp‎ | language

C++ 程序中出现的每个名称只在某些可能不连续的源码部分中合法,这些部分被称为其作用域

在作用域内,能用无限定名称查找将名称与其声明关联。

目录

[编辑] 块作用域

块(复合语句)中的声明所引入的变量的潜在作用域始于声明点,并终于块末尾。实际作用域同潜在作用域,除非有内嵌块带有引入等同名称的声明(该情况下,从外层声明的作用域排除掉嵌套声明的整个潜在作用域)。

int main()
{
    int a = 0; // 第一个 'a' 的作用域开始
    ++a; // 名称 'a' 在作用域中并指代第一个 'a'
    {
        int a = 1; // 第二个 'a' 的作用域开始
                   // 第一个 'a' 的作用域间断
        a = 42;    // 'a' 在作用域中并指代第二个 'a'                 
    } // 块结束,第二个 'a' 的作用域结束
      //       第一个 'a' 的作用域恢复
} // 块结束,第一个 'a' 的作用域结束
int b = a; // 错误:名称 'a' 不在作用域中

声明于异常处理块中的名称的潜在作用域始于声明点,并在异常处理块结束时结束,而且在其他异常处理块或外围块中不在作用域中。

try {   
    f();
} catch(const std::runtime_error& re) { // sre 的作用域开始
    int n = 1; // n 的作用开始
    std::cout << re.what(); // re 在作用域中
} // re 的作用域结束, n 的作用域结束
 catch(std::exception& e) {
    std::cout << re.what(); // 错误: re 不在作用域中
    ++n; // 错误: n 不在作用域中
}

for 循环初始化语句中、 for 循环条件中、范围 for 循环范围声明中、 if 语句switch 语句初始化语句中、 (C++17 起) if 语句while 循环switch 语句条件中声明的名称的潜在作用域,始于声明点,并终于控制语句结尾。

Base* bp = new Derived;
if(Derived* dp = dynamic_cast<Derived*>(bp))
{
    dp->f(); // dp 在作用域中
} // dp 的作用域结束
 
for(int n = 0; // n 的作用域开始
    n < 10;    // n 在作用域中
    ++n)       // n 在作用域中
{
    std::cout << n << ' '; // n 在作用域中
} // n 的作用域结束

[编辑] 函数形参作用域

函数形参(包含 lambda 表达式的形参)或函数局部与定义变量的作用域始于其声明点。

  • 若最内层的外围函数声明器不是函数定义的声明器,则潜在作用域终于该函数声明器的结尾。
  • 否则,其潜在作用域终于函数 try 块的最后异常处理块的末尾,或若不使用函数 try 块则为函数体的末尾。
const int n = 3;
 
int f1(int n,     // 全局 'n' 的作用域间断
                  // 参数 'n' 的作用域开始
       int y = n); // 错误:默认实参引用形参
 
int (*(*f2)(int n))[n]; // OK :函数参数 'n' 的作用域终于其函数声明器的末尾
                        // 数组声明器中,全局 n 在作用域中
// (这声明指向返回指向 3 个 int 的数组的指针的函数的指针)
 
// 相反
auto (*f3)(int n)->int (*)[n]; // 错误:以参数 'n' 为数组边界
 
int f(int n = 2)  // 'n' 的作用域开始
try // 函数 try 块
{         // 函数体开始
   ++n;   // 'n' 在作用域中并指代函数参数
   {
      int n = 2; // 局部变量 'n' 的作用域开始
                 // 函数参数 'n' 的作用域中断
      ++n; // 'n' 在此块中指代局部变量
    }            // 局部变量 'n' 的作用域结束
                 // 函数参数 'n' 的作用域恢复
} catch(...) {
   ++n; // n 在作用域中并指代函数参数
   throw;
} // 最后异常处理块结束,函数参数 'n' 的作用域结束
int a = n; // 错误:名称 'n' 不在作用域中

[编辑] 函数作用域

声明于函数内的标号(且只有标号)在该函数、所有内嵌块的所有位置都在作用域中,无论在其自身声明的前后。

void f()
{
   {   
       goto label; // label 在作用域中,尽管之后才声明
label:;
   }
   goto label; // label 忽略块作用域
}
 
void g()
{
    goto label; // 错误: g() 中 label 不在作用域中
}

[编辑] 命名空间作用域

命名空间中省迷顶点任何实体的潜在作用域始于声明,并由后续的同一命名空间名的所有命名空间定义的连接组成,再加上对于任何引入此名称或其整个命名空间到其他作用域的 using 指令 的剩余作用域。

翻译单元的顶层作用域(“文件作用域”或“全局作用域”)亦为命名空间,而被正式称作“全局命名空间作用域”。任何声明于全局命名空间作用域的实体的潜在作用域始于声明,并持续到翻译单元的结尾。

声明于无名命名空间或内联命名空间的实体的作用域包括外围命名空间;

namespace N { // N 的作用域开始(作为全局命名空间的成员)
    int i; // i 的作用域开始
    int g(int a) { return a; } // g 的作用域开始
    int j(); // j 的作用域开始
    void q(); // q 的作用域开始
    namespace {
        int x; // x 的作用域开始
    } // x 的作用域不结束
    inline namespace inl { // inl 的作用域开始
      int y; // y 的作用域开始
    } // y 的作用域不结束
} // i 、 g 、 j 、 q 、 inl 、 x 、 y 的作用域间断
 
namespace {
    int l=1; // l 的作用域开始
} // l 的作用域不结束(它是无名命名空间的成员)
 
namespace N { //  i 、 g 、 j 、 q 、 inl 、 x 、 y 的作用域持续
    int g(char a) {  // 重载 N::g(int)
        return l+a;  // 来自无名命名空间的 l 在作用域中
    }
    // int i; // 错误:重复定义( i 已在作用域中)
    int j(); // OK :允许重复的函数声明
    int j() { // OK :先前声明的 N::j() 的定义
        return g(i); // 调用 N::g(int)
    }
    int q(); // 错误: q 已在作用域中并有不同的返回类型
} // i 、 g 、 j 、 q 、 inl 、 x 、 y 的作用域间断
 
int main() {
    using namespace N; // i 、 g 、 j 、 q 、 inl 、 x 、 y 的作用域恢复
    i = 1; // N::i 在作用域中
    x = 1; // N::(匿名)::x 在作用域中
    y = 1; // N::inl::y 在作用域中
    inl::y = 2; // N::inl 亦在作用域中
} // i 、 g 、 j 、 q 、 inl 、 x 、 y 的作用域间断

[编辑] 类作用域

中声明的名称的潜在作用域始于声明点,并包含类体的剩余部分和所有函数体(即使定义于类定义外,或在名称声明之前)、默认参数、异常规定、类内花括号或等号初始化器契约条件 (C++20 起)及递归的嵌套类中的所有这些内容。

class X {
    int f(int a = n) { // X::n 在默认参数中在作用域
         return a*n;   // X::n 在函数体内在作用域中
    }
    int g();
    int i = n*2;   // X::n 在初始化器内在作用域中
 
//  int x[n];      // 错误: n 在类体内不在作用域中
    static const int n = 1;
    int x[n];      // OK : n 现在在类体内在作用域中
};
int X::g() { return n; } // X::n 在类外成员函数体内在作用域中

若在声明前于类体中使用名称,而该名称的另一声明在作用域中,则程序为病式,不要求诊断

typedef int c; // ::c
enum { i = 1 }; // ::i
class X {
    char v[i]; // 错误:在此点 i 指代 ::i ,但亦有 X::i
    int f() {
         return sizeof(c); // OK : X::c 而非 ::c 在成员函数体内在作用域中
    }
    char c; // X::c
    enum { i = 2 }; // X::i
};
 
typedef char* T;
struct Y {
    T a; // 错误:在此点, T 指代 ::T ,但亦有 Y::T
    typedef long T;
    T b;
};

任何类成员名只能用于四种语境中:

  • 在其自身的类作用域或在派生类的类作用域
  • 在应用到其类或自之派生的类类型的表达式的 . 运算符后
  • 在应用到指向其类或自之派生的类的指针类型表达式的 -> 运算符后
  • 在应用到其类或自之派生的类名称后的 :: 运算符后

[编辑] 枚举作用域

有作用域枚举中引入的枚举项名的作用域始于声明点,并终于 enum 指定符的末尾(相反,无作用域枚举项的作用域在 enum 指定符的结尾后仍在作用域中):

enum e1_t { // 无作用域枚举
  A,
  B = A*2
}; // A 与 B 的作用域不结束
 
enum class e2_t { // 有作用域枚举
    SA,
    SB = SA*2 // SA 在作用域中
}; // SA 与 SB 的作用域结束
 
e1_t e1 = B; // OK : B 在作用域中
// e2_t e2 = SB; // 错误: SB 不在作用域中
e2_t e2 = e2_t::SB; // OK

[编辑] 模板形参作用域

模板形参名的潜在作用域立即始于声明点,并持续到于其中引入它的最小模板声明的末位。具体而言,模板形参能用于后继模板形参的声明,及基类的指定,但不能用于前趋模板形参的声明。

template< typename T, // T 的作用域开始
          T* p,       // T 能用用于非类型形参
          class U = T // T 能用作默认类型
        >
class X : public Array<T> // T 能用于基类名
{
   // T 还能在体内使用
}; // T 与 U 的作用域结束, X 的作用域持续

模板模板形参的形参名的潜在作用域,是名称出现于其中的最小模板形参列表

template< template< // 模板模板形参
                    typename Y,     // Y 的作用域开始
                    typename G = Y // Y 在作用域中
                  > // Y 与 G 的作用域结束
          class T,
//          typename U = Y // 错误: Y 不在作用域中
          typename U
        >
class X
{
}; // T 与 U 的作用域结束

同其他嵌套作用域,模板形参名在其自身的持续期间隐藏来自外层作用域的相同名称:

typedef int N;
template< N X, // int 类型的非类型模板形参
          typename N, // 此 N 的作用域开始,打断 ::N 的作用域
          template<N Y> class T // 此处的 N 是模板形参,非 int
         > struct A;

[编辑] 声明点

作用域始于声明点,它定位如下:

对于简单声明所引入的变量和其他名称,声明点立即在该名称的声明器后,并在其初始化器前,若初始化器存在:

unsigned char x = 32; // 第一个 'x' 的作用域开始
{
    unsigned char x = x; // 第二个 'x' 的作用域在初始化器 (= x) 前开始
                         // 这不以值 32 初始化第二个 'x' ,
                         // 这以其自身的不确定值初始化第二个 'x'
}
std::function<int(int)> f = [&](int n){return n>1 ? n*f(n-1) : n;};
           // 函数对象名 'f' 在 lambda 中在作用域中,
           // 而且能正确地被以引用捕获,给出递归函数
const int x = 2; // 首个 'x' 的作用域开始
{
    int x[x] = {}; // 第二个 x 的作用域在初始化器 (= {}) 前开始,但在声明器 (x[x]) 后。
                   // 在声明器内,外层 'x' 仍在作用域中,这声明 2 个 int 的数组。
}

类或模板的声明点,立即在其类头中命名类名的标识符(或命名模板的模板标识)后,而在基类列表中已在作用域中:

// 名称 'S' 在其出现后立即在作用域中,
// 故它能用于基类列表
struct S: std::enable_shared_from_this<S> 
{
};

枚举的声明点,立即在命名它的标识符出现于 enum 指定符中,或不可见枚举声明之后,取决于先用何者:

enum E : int { // E 已在作用域中
    A = sizeof(E)
};

类型别名或别名模板的声明点立即在该别名所涉及的类型 id 之后:

using T = int; // T 的声明点在分号
using T = T;   // 同 T = int

枚举项的声明点立即在其定义后(不同于对于变量的在初始化器之前):

const int x = 12;
{
    enum {
        x = x + 1, // 声明点在逗号,初始化 x 为 13
        y = x + 1  // 枚举项 x 在作用域中,初始化 y 为 14
    };
}

injected-class-name 的声明点立即后随其类(或类模板)定义的开花括号

template<typename T>
struct Array
// : std::enable_shared_from_this<Array> // 错误:注入类名不在作用域中
   : std::enable_shared_from_this< Array<T> > // OK :模板名 Array 在作用域中
{ // 注入类名 Array 现在在作用域中,如同为公开成员名
    Array* p; // 指向 Array<T> 的指针
};

[编辑] 引用

  • C++11 standard (ISO/IEC 14882:2011):
  • 3.3 Scope [basic.scope]
  • C++98 standard (ISO/IEC 14882:1998):
  • 3.3 Declarative regions and scopes [basic.scope]

[编辑] 参阅

作用域C 文档