🔷 第十章 类和对象
📄 一句话概括

嵌套命名空间="命名空间里面再放命名空间",像公司 → 部门 → 小组。访问时要写全链:公司::部门::小组::东西。它的价值是"更细的分类"——把一堆东西按层级归纳,既能分层又不牺牲可读性。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 分

读结果三句话:

  1. school::grade::cls::Student 能一层层点下去——和 Outer::Inner::...(10.4.5)是同一个语法。
  2. sc::Student 和全名指的是同一个类——别名只是"换称呼"(10.6.1)。
  3. 类里用了 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 更稳。

五、常见误区

  1. "嵌套命名空间里,内层能直接用外层的名字"——能(作用域从里往外找,6.4)。但反过来不行:外层不能直接用内层的名字(要写 外层::内层::名字)。
  2. "namespace A::B { } 所有编译器都支持"——不是。这是 C++17 的新写法,老编译器不认。看到语法错误就换回"一层层套"的老写法。
  3. "嵌套越深越好"——恰恰相反。三层已经不少了,再深就变成"打名字比赛"了。如果非要深,就用别名把它缩回来(本节第二节的解药)。
  4. "using namespace A::B; 和 using namespace A; using namespace B; 一样"——不一样。前者只放行"A 里那个 B"的内容;后者把整个 A 和整个 B 都放行了(范围大得多,风险也大得多)。要放行就放行得精确。
  5. "嵌套命名空间和嵌套类必须配套使用"——没有关系。它们互不依赖:命名空间里可以有类(本节就是),类里也可以有命名空间?(不常见,实际也很少这么写)。两套"分层"机制各管各的。
自测三问(点开对答案) 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);**外层不能直接用内层**(必须写 `外层::内层::`)。

相关笔记