-
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) { // re 的作用域开始
int n = 1; // n 的作用开始
std::cout << re.what(); // re 在作用域中
} // re 的作用域结束, n 的作用域结束
catch(std::exception& e) {
std::cout << re.what(); // 错误: re 不在作用域中
++n; // 错误: n 不在作用域中
}
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 的作用域结束
函数形参作用域
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; // OK :全局 'n' 在作用域中
函数作用域
void f()
{
{
goto label; // label 在作用域中,尽管之后才声明
label:;
}
goto label; // label 忽略块作用域
}
void g()
{
goto label; // 错误: g() 中 label 不在作用域中
}
命名空间作用域
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 的作用域间断
类作用域
class X {
int f(int a = n) { // 默认实参内的 X::n 在作用域中
return a*n; // 函数体内的 X::n 在作用域中
}
using r = int;
r g();
int i = n*2; // 初始化器内的 X::n 在作用域中
// int x[n]; // 错误:类体内的 X::n 不在作用域中
static const int n = 1;
int x[n]; // OK : 类体内的 X::n 现在在作用域中
};
//r X::g() { // 错误:类外成员函数内的 r 不在作用域中
auto X::g()->r { // OK :尾随返回类型内的 X::r 在作用域中
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;
};
隐藏
隐藏名称
可通过在封闭块中声明名称来隐藏该名称。比如下图中,在内部块中重新声明i
,从而隐藏与外部快范围中的i管理的变量:
隐藏类名
通过声明同一范围内的函数、对象或者变量或者枚举名,可以隐藏类名称。但是,当使用关键字作为前缀时,仍可以访问类名class
// hiding_class_names.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
// Declare class Account at global scope.
class Account
{
public:
Account( double InitialBalance )
{ balance = InitialBalance; }
double GetBalance()
{ return balance; }
private:
double balance;
};
double Account = 15.37; // Hides class name Account
int main()
{
class Account Checking( Account ); // Qualifies Account as
// class name
cout << "Opening account with a balance of: "
<< Checking.GetBalance() << "\n";
class Account *Checking1 = new class Account( Account );
cout << "Opening account with a balance of: "
<< Checking1->GetBalance() << "\n";
}
//Output: Opening account with a balance of: 15.37
注意:
- 为调用类名称 () 的任何位置 Account ,都必须使用关键字类将其与全局范围的变量帐户区分开来。 当类名出现在范围解析运算符
(::)
的左侧时,此规则不适用。 在范围解析运算符的左侧的名称始终被视为类名称。 - 不推荐这么用
隐藏具有全局范围的名称
可以通过在块范围内显示声明相同的名称来英寸具有全局作用域的名称。但是,可以使用范围解析运算符 () 来访问全局范围名称::
。
#include <iostream>
int i = 7; // i has global scope, outside all blocks
using namespace std;
int main( int argc, char *argv[] ) {
int i = 5; // i has block scope, hides i at global scope
cout << "Block-scoped i has the value: " << i << "\n";
cout << "Global-scoped i has the value: " << ::i << "\n";
}