嵌套命名空间="命名空间里面再放命名空间",像公司 → 部门 → 小组。访问时要写全链:公司::部门::小组::东西。它的价值是"更细的分类"——把一堆东西按层级归纳,既能分层又不牺牲可读性。C++17 起可以写成一句话:namespace A::B::C { ... }(老编译器不支持,看到两种写法都不慌)。
这是 10.4.5「嵌套类」的同类概念——一个在"类里套类",一个在"命名空间里套命名空间",目的都是"用作用域分层,把名字隔开"。本节会把 10.6.1 的命名空间别名用上(嵌套最大的麻烦就是名字长,别名是解药)。
一、基本写法
老写法:一层一层套(所有编译器都支持)
namespace company {
namespace network {
namespace protocol {
class HttpClient { // 全名:company::network::protocol::HttpClient
public:
void send() { std::cout << "发送请求" << std::endl; }
};
const int MAX_RETRY = 3;
} // protocol
} // network
} // company
访问:
company::network::protocol::HttpClient client;
client.send();
std::cout << company::network::protocol::MAX_RETRY << std::endl;
新写法:一句话搞定(C++17 起)
namespace company::network::protocol { // ★ C++17:一行套三层
class HttpClient {
public:
void send() { std::cout << "发送请求" << std::endl; }
};
const int MAX_RETRY = 3;
}
两种写法完全等价。老编译器(比如较旧的 Dev-C++ 版本)不认新写法——如果报"expected '{'"之类的语法错误,就改用老写法。
二、名字太长的解药:别名(回收 10.6.1)
嵌套命名空间最大的问题就是"打起来累":
company::network::protocol::HttpClient c1;
company::network::protocol::HttpClient c2; // 每行都要打一长串
用别名(10.6.1 第四节的工具):
namespace net = company::network::protocol; // 一层外号全解决
net::HttpClient c1;
net::HttpClient c2;
std::cout << net::MAX_RETRY << std::endl;
或者用 using 声明(只放行要用的那一个):
using company::network::protocol::HttpClient; // 只放行这一个
HttpClient c1; // 直接写短名
| 工具 | 写法 | 特点 |
|---|---|---|
| 别名 | namespace net = ...; |
保留归属信息(要写 net::),最推荐 |
using 声明 |
using A::B::C; |
可以裸写名字,但只限本作用域 |
using namespace |
using namespace A::B::C; |
整段放行,风险最大,练习里用 |
三、为什么要分层:三个真实理由
| 理由 | 说明 |
|---|---|
| 避免重名 | company::graphics::Point 和 company::geometry::Point 可以同时存在——两个不同用途的"点" |
| 表达分类 | 看名字就知道这个东西属于哪个模块(net::protocol::HttpClient 一眼看出是网络协议层的) |
| 便于发布 | 库越大越需要分层——使用者可以只关心自己要的那一层(用别名一缩,就退回到"短名"的体验) |
不这么做的后果:所有东西挤在一个命名空间里,名字变成 HttpClientV2、HttpClientForUpload、HttpClientOld……靠"加班"来避免重名。
四、动手 Demo:三层命名空间 + 别名
#include <iostream>
using std::cout; // 只放行两个常用的(10.6.1 方式②)
using std::endl;
// ══════ 三层嵌套命名空间 ══════
namespace school {
namespace grade { // 年级
namespace cls { // 班级
const char *CLASS_NAME = "三年二班";
class Student {
private:
char name[20];
int score;
public:
Student(const char *n = "未命名", int s = 0) : score(s) {
// 简化:不用 strcpy,直接记分数
for (int i = 0; i < 19 && n[i]; i++) name[i] = n[i];
name[19] = '\0';
}
void show() const {
cout << name << "(" << CLASS_NAME << "):"
<< score << " 分" << endl;
}
};
}
}
}
// ══════ 别名:把三层缩成一层 ══════
namespace sc = school::grade::cls;
int main() {
// 写法 A:写全链(最清楚)
school::grade::cls::Student a("张三", 92);
a.show();
// 写法 B:用别名(最省字)
sc::Student b("李四", 78);
b.show();
// 还能用别名访问常量
cout << "班级:" << sc::CLASS_NAME << endl;
// 写法 C:using 声明(只放行一个名字)
{
using school::grade::cls::Student;
Student c("王五", 85); // 裸名可用
c.show();
}
return 0;
}
看到什么算成功:
张三(三年二班):92 分
李四(三年二班):78 分
班级:三年二班
王五(三年二班):85 分
读结果三句话:
school::grade::cls::Student能一层层点下去——和Outer::Inner::...(10.4.5)是同一个语法。sc::Student和全名指的是同一个类——别名只是"换称呼"(10.6.1)。- 类里用了
CLASS_NAME(不在同一个命名空间层级里)——它能直接用吗?在本例里它写在外层cls里、类也定义在cls里,所以可以直接用(内层能看见外层的名字,这就是作用域的"从里往外找",6.4)。
30 秒实验:把 namespace sc = school::grade::cls; 改成 using namespace school::grade::cls;——再把 sc::Student 改成 Student、sc::CLASS_NAME 改成 CLASS_NAME。能跑,而且更省字。 但请注意:这一整段 using namespace 放行的是"整段 cls 里的所有名字"——如果外面还有个同名的 Student,"谁赢"就要靠作用域规则决定了(容易出意外)。这就是为什么别名比 using namespace 更稳。
五、常见误区
- "嵌套命名空间里,内层能直接用外层的名字"——能(作用域从里往外找,6.4)。但反过来不行:外层不能直接用内层的名字(要写
外层::内层::名字)。 - "
namespace A::B { }所有编译器都支持"——不是。这是 C++17 的新写法,老编译器不认。看到语法错误就换回"一层层套"的老写法。 - "嵌套越深越好"——恰恰相反。三层已经不少了,再深就变成"打名字比赛"了。如果非要深,就用别名把它缩回来(本节第二节的解药)。
- "
using namespace A::B;和using namespace A; using namespace B;一样"——不一样。前者只放行"A里那个B"的内容;后者把整个A和整个B都放行了(范围大得多,风险也大得多)。要放行就放行得精确。 - "嵌套命名空间和嵌套类必须配套使用"——没有关系。它们互不依赖:命名空间里可以有类(本节就是),类里也可以有命名空间?(不常见,实际也很少这么写)。两套"分层"机制各管各的。
自测三问(点开对答案)
1. 嵌套命名空间怎么访问里面的名字?C++17 有没有简写方式?——**写全链**:`A::B::C::名字`。**C++17 起定义时可以一行写 `namespace A::B::C { }`**(定义简写);**访问还是要写全链**(或者用别名/`using` 简化)。 2. 嵌套命名空间名字太长,有哪几种解药?——① **命名空间别名**(`namespace net = A::B::C;`,**推荐**,保留归属信息);② **`using` 声明**(只放行一个名字);③ **`using namespace`**(整段放行,**风险最大**)。 3. 内层命名空间能直接用外层的名字吗?反过来呢?——**内层可以直接用外层**(从里往外找,6.4);**外层不能直接用内层**(必须写 `外层::内层::`)。- ☐ 10.6.5 定义未命名的命名空间(下一节)
相关笔记
- 在多个文件中定义命名空间 —— 上一节:命名空间跨文件
- 定义未命名的命名空间 —— 下一节:让名字只在本文件可用
- 使用命名空间 —— 别名与
using的完整对比 - 嵌套类 —— 同一个"分层"思路,用在类上
- 变量作用域 —— "内层能看外层"的规矩来源
- 🔷 C++基础 · 索引 —— 全课路线图