使用声明与使用指令

在许多教材与教程中,你或许见过如下程序:

#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 等。
  • 非限定名:不含作用域前缀,如 coutx

(进阶)限定名亦可由类名或对象通过 . / -> 限定,如 C::memberobj.xptr->y

三、使用声明(using-declaration)

语法:using 命名空间::标识符;
效果:在当前作用域内,将非限定名作为限定名的别名。示例:

#include <iostream>

int main()
{
    using std::cout; // 告诉编译器:cout 即 std::cout
    cout << "Hello world!\n"; // 无需 std:: 前缀
    return 0;
} // using 声明在此处作用域结束

使用声明需为每个标识符单独书写,例如 std::cinstd::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. 引入全部名称
    导入整个命名空间,包含大量未用标识符,增加冲突概率。
  2. 无优先级机制
    使用指令不会对来自该命名空间的名称给予优先选择,导致歧义。

示例 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 关键字亦用于定义类型别名(与使用语句无关),参见《类型别名》。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"