在许多教材与教程中,你或许见过如下程序:
#include <iostream>
using namespace std;
int main()
{
cout << "Hello world!\n";
return 0;
}
若见此写法,请立即远离;该教程极可能已过时。本课将阐明原因。
提示
部分 IDE 在创建新 C++ 项目时也会生成类似代码,以便即刻编译运行,而非从空白文件起步。
一、简短历史回顾
在 C++ 引入命名空间之前,如今位于 std
命名空间的所有标识符均直接位于全局命名空间。这导致程序标识符与标准库标识符频频冲突,旧版 C++ 程序在新版环境下可能因命名冲突而无法编译。
1995 年,命名空间被标准化,标准库整体迁入 std
命名空间。为兼容旧代码,需将每一处无 std::
前缀的名称手动添加前缀,风险极高,于是提出了折中方案。
时至今日,若频繁使用标准库,std::
前缀显得冗长,C++ 便引入了 using-语句 以解决此问题。
二、限定名与非限定名
- 限定名:包含作用域前缀。常见形式为命名空间加作用域解析运算符
::
,如std::cout
、::foo
等。 - 非限定名:不含作用域前缀,如
cout
、x
。
(进阶)限定名亦可由类名或对象通过 .
/ ->
限定,如 C::member
、obj.x
、ptr->y
。
三、使用声明(using-declaration)
语法:using 命名空间::标识符;
效果:在当前作用域内,将非限定名作为限定名的别名。示例:
#include <iostream>
int main()
{
using std::cout; // 告诉编译器:cout 即 std::cout
cout << "Hello world!\n"; // 无需 std:: 前缀
return 0;
} // using 声明在此处作用域结束
使用声明需为每个标识符单独书写,例如 std::cin
、std::endl
需各自声明。
使用声明一般被认为在源文件(.cpp)内安全可接受,但下文将指出例外。
四、使用指令(using-directive)
语法:using namespace 命名空间;
效果:将命名空间内所有标识符在当前作用域内变为非限定可见。示例:
#include <iostream>
int main()
{
using namespace std; // 所有 std 内名称均可不加前缀
cout << "Hello world!\n";
return 0;
}
使用指令最初用于解决旧代码迁移难题:在文件顶部添加一行 using namespace std;
,即可保持旧代码无修改运行。
五、使用指令的问题(应避免 using namespace std;
)
现代 C++ 中,使用指令风险远大于收益,原因如下:
- 引入全部名称
导入整个命名空间,包含大量未用标识符,增加冲突概率。 - 无优先级机制
使用指令不会对来自该命名空间的名称给予优先选择,导致歧义。
示例 1:命名冲突
#include <iostream>
namespace A { int x{10}; }
namespace B { int x{20}; }
int main()
{
using namespace A;
using namespace B;
std::cout << x << '\n'; // 歧义:A::x 还是 B::x?编译错误
}
示例 2:与全局函数冲突
#include <iostream>
int cout() { return 5; }
int main()
{
using namespace std;
cout << "Hello\n"; // 歧义:std::cout 还是 ::cout()
}
示例 3:库更新导致行为变化
若第三方库更新后新增同名函数,即使原代码不变,也可能因重载解析规则改变而静默切换行为。
示例 4:作用域污染
使用指令后,阅读者难以分辨名称来源,降低代码可读性。
六、使用语句的作用域规则
- 若位于块内,则仅在该块有效(遵循块作用域)。
- 若位于命名空间或全局作用域,则对整个文件剩余部分有效。
- 严禁在头文件或
#include
之前放置使用语句,以免污染其他翻译单元。
示例:头文件中放置 using namespace std;
将使所有包含该头文件的源文件均被污染。
七、无法取消或替换使用语句
一旦声明,无法在相同作用域内取消或替换。最佳做法是利用块作用域限制其影响范围:
int main()
{
{
using namespace Foo;
// 仅此处使用 Foo 中名称
}
{
using namespace Goo;
// 仅此处使用 Goo 中名称
}
}
八、最佳实践
- 优先使用显式命名空间限定符。
- 完全避免使用指令(除
using namespace std::literals
以使用字面量后缀)。 - 在源文件(.cpp)内,于所有
#include
之后可使用 使用声明; - 绝不在头文件(尤其是全局命名空间)中使用使用语句。
阅读相关阅读
using
关键字亦用于定义类型别名(与使用语句无关),参见《类型别名》。