C++ 用命名空间把函数、类、变量的名字圈在一组里,避免两个库都提供 log 时互相覆盖。本质是给名字加前缀:app::log 与 util::log 可以并存。标准库的名字都在 std 里,所以教程里写 std::cout 而不是裸的 cout。C 语言没有命名空间,习惯用前缀区分(如 gtk_widget_show);C++ 把前缀写成语法。本节先演示同名并存,再说明 using、嵌套与匿名命名空间。头文件中不要写 using namespace std;,见头文件与多文件。

namespace app void log(); namespace util void log(); 调用时写 app::log() 或 util::log(),二者可以并存
同一个短名 log 放进不同命名空间后不再冲突。

最简单的例子

若在全局作用域写两个 void log(),程序无法通过编译。放进不同命名空间后,用 :: 写明要哪一个:

C++ 两个命名空间中的 log运行
#include <iostream>

namespace app {
void log() {
    std::cout << "app\n";
}
}

namespace util {
void log() {
    std::cout << "util\n";
}
}

int main() {
    app::log();   // 调用 app 里的 log
    util::log();  // 调用 util 里的 log
    return 0;
}

输出:

app
util

std::cout 也是同一规则:cout 定义在命名空间 std 中。

using 声明与 using 指令

using app::log; 只引入一个名字,此后在当前作用域可直接写 log(),仍等于 app::log()。using namespace std; 则把 std 里大量名字一并引入,容易与自己的 count、list 等撞名。头文件一旦写后者,所有包含该头文件的代码都会被污染,因此公开头文件应写全 std::。

C++ using 声明运行
#include <iostream>

namespace app {
void log() {
    std::cout << "app\n";
}
}

namespace util {
void log() {
    std::cout << "util\n";
}
}

int main() {
    using app::log;  // 只引入 app::log
    log();
    util::log();     // 另一个仍须写限定名
    return 0;
}

输出:

app
util

实现文件的函数体内写 using std::cout; 可以接受;不宜在全局作用域长期 using namespace std;。C++11 的 using Id = int; 是类型别名,与这里的 using 声明不是同一件事,见以后别名与模板章节。

嵌套命名空间

命名空间可以套在另一层里面,调用时写两层限定名。C++17 允许写成 namespace app::util { ... },与先写 namespace app { namespace util { ... } } 等价:

namespace app::util {
void log(const char* msg) {
    std::cout << "[log] " << msg << std::endl;
}
}

int main() {
    app::util::log("started");
    return 0;
}

匿名命名空间

不写名字的 namespace { ... } 表示这些名字只给本文件用。其他 .cpp 即使出现同名变量,也不是同一个对象。C 语言里文件内 static 全局变量起类似作用。

C++ 匿名命名空间运行
#include <iostream>

namespace {
int n = 0;  // 仅本文件可见
}

void bump() {
    ++n;
}

int main() {
    bump();
    bump();
    std::cout << n << "\n";
    return 0;
}

输出:

2

不要在头文件里用匿名命名空间定义普通对象:每个包含该头文件的 .cpp 都会得到一份独立副本。

与头文件、std

头文件中声明的函数和类应放在项目自己的命名空间内,减少与第三方库及 std 的冲突。头文件不要写 using namespace std; 或 using namespace app;。本站在线编译一次只处理一份源码,多文件工程仍须在本地把声明放进命名空间、在实现文件里用限定名定义,见头文件与多文件。

了解:内联命名空间与 ADL

inline namespace 常用于给库做版本,调用时可以不写版本那一层名字,初学以普通命名空间为主。参数依赖查找(ADL)是:写 swap(a, b) 且未加 std:: 时,编译器还会到 a、b 所属类型的命名空间里找 swap。泛型库会用到这一点,入门程序把名字写全即可。

本节说明了用命名空间组织名字、避免冲突的基本写法。下一章从函数模板与类模板开始,介绍泛型编程入门。