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

定义命名空间的写法极简:namespace 名字 { 内容 };——大括号不用加结尾分号(这点和 class、struct 不同,别顺手写)。里面能放什么:变量、函数、类、结构体、模板、甚至另一个命名空间——几乎所有"能被命名的东西"都能放。两条重要特性:① 同一个命名空间可以"分几处写",编译器自动合并;② 命名空间里的类,成员函数可以写到命名空间外面(但要写 命名空间::类名::函数名)。

📝 本节和前面几节的关系

10.6.1 讲了"怎么用别人的命名空间",本节讲"怎么建自己的"。这里是 10.1.2「类里声明、类外实现」的升级版——那时我们把函数实现写到类外(Point::getX),现在再套一层命名空间(lib::Point::getX)。你会发现 :: 这个符号可以链起来用,每一层代表一级作用域。

一、基本写法

namespace mylib {                 // 命名空间名
    int    version = 1;           // 变量
    double PI = 3.14159;          // 常量

    int square(int x) {           // 函数
        return x * x;
    }

    class Point {                 // 类
    private:
        int x, y;
    public:
        Point(int a = 0, int b = 0) : x(a), y(b) { }
        void show() const;
    };
}                                 // ★ 注意:这里**不写分号**!

四个语法要点:

要点 说明
末尾不加分号 } 后面直接结束。这和 class C { };(10.1.2)、struct S { };(8.1.1)不一样——别顺手写分号(写了不会报错,但属于坏习惯)
不写名字也能定义 那就是"未命名命名空间"(10.6.5)
里面可以套命名空间 见 10.6.4
不能写在函数里 命名空间只能定义在全局或另一个命名空间里,不能定义在函数内部(这点和"类"不同,10.4.6 的局部类是允许的)

访问里面的成员:命名空间名::成员名

cout << mylib::version << endl;
int r = mylib::square(5);
mylib::Point p(3, 4);

二、特性 1:同一个命名空间可以"分几处写"

这是命名空间非常好用的一点:

namespace mylib {
    int a = 1;
}

// ... 中间隔了很多代码,甚至有别的命名空间 ...

namespace mylib {                 // ★ 再开一次,同名的
    int b = 2;
}

结果:两段内容被合并成同一个 mylib。所以:

cout << mylib::a << endl;         // 1
cout << mylib::b << endl;         // 2

为什么这个特性重要?因为它让"库可以由多个文件共同组建"(10.6.3 就是讲这个)——每个文件都往 mylib 里放自己那块东西。

对比一下:类不能这样"分几处定义"(类必须一处写完整,10.1.2);命名空间可以。这是两者的重要差别。

三、特性 2:命名空间里的类,实现可以写到外面

写法比 10.1.2 多一层前缀:

namespace mylib {

    class Point {
    private:
        int x, y;
    public:
        Point(int a = 0, int b = 0) : x(a), y(b) { }
        void show() const;                    // 只声明
        int  getX() const;                    // 只声明
    };

}                                 // 命名空间到此结束

// ── 在命名空间外面写实现:前缀是"命名空间::类名::函数名" ──
void mylib::Point::show() const {
    cout << "(" << x << ", " << y << ")" << endl;      // 依然能直接读私有成员
}

int mylib::Point::getX() const {
    return x;
}

看到没有——:: 可以"链"起来:

mylib  ::  Point  ::  show
 ↑         ↑          ↑
命名空间   类        函数

读法:"mylib 里的 Point 里的 show"。中间少一层都不行。

也可以"再开一次命名空间"来写实现(另一种常见写法):

namespace mylib {                 // 重开一次

    void Point::show() const {
        cout << "(" << x << ", " << y << ")" << endl;
    }

}                                 // 这样就**不用**写 mylib:: 前缀了

两种写法都行,看团队习惯。第一种(全前缀)更常见、也更明确。

如果忘了写命名空间前缀会怎样:

void Point::show() const { ... }        // ❌ 忘了 mylib::

报错通常是 'Point' has not been declared 或者 no declaration matches——因为全局作用域里根本没有 Point 这个名字(它住在 mylib 里)。

四、命名空间里该放什么、不该放什么

建议放 说明
类、结构体、枚举 最典型的用法
函数、变量 尤其是"不应该暴露给全局"的实现细节
常量 如 PI、MAX_SIZE
模板 后面章节学
另一个命名空间 10.6.4
不建议放 为什么
main 函数 main 必须在全局作用域,放进命名空间里程序找不到入口
using namespace 指令 会让这个命名空间的"出口"变宽(10.6.1 的误区 5)
一堆互不相干的东西 命名空间也是"分类"——别做成垃圾抽屉。东西多了就该再分一层(10.6.4)

五、动手 Demo:一个"自己造的小工具库"

#include <iostream>
using std::cout;                  // 只用 using 声明(10.6.1 方式②,风险小)
using std::endl;

// ══════ 定义命名空间:分两处写,验证"自动合并" ══════
namespace mytool {

    const double PI = 3.14159;    // 常量

    double circleArea(double r) { // 函数
        return PI * r * r;
    }

}                                 // 第一段结束(不加分号)

namespace mytool {                // ★ 第二段:同名,会自动合并

    class Counter {               // 类
    private:
        int value;
    public:
        Counter(int v = 0) : value(v) { }           // 构造函数
        void add(int n = 1) { value += n; }
        int  get() const { return value; }
    };

    void banner(const char *title) {
        cout << "=== " << title << " ===" << endl;
    }

}                                 // 第二段结束

// ── 命名空间里类的成员函数,写到命名空间外面 ──
//    这里没有需要外写的函数(Counter 都在类里写完了),
//    但下面演示一个"外写"的辅助函数
namespace mytool {
    int doubleIt(int v) {         // 第三段也可以
        return v * 2;
    }
}

int main() {
    mytool::banner("开始");

    cout << "半径 2 的圆面积 = " << mytool::circleArea(2) << endl;

    mytool::Counter c(10);
    c.add();                      // 默认加 1
    c.add(5);
    cout << "计数器 = " << c.get() << endl;          // 16

    cout << "doubleIt(21) = " << mytool::doubleIt(21) << endl;

    mytool::banner("结束");
    return 0;
}

看到什么算成功:

=== 开始 ===
半径 2 的圆面积 = 12.5636
计数器 = 16
doubleIt(21) = 42
=== 结束 ===

读结果三句话:

  1. mytool 分了三段写(常量/函数一段、类一段、辅助函数一段)——它们自动合并成一个命名空间,main 里都能访问。
  2. c.add() 用了默认参数——add(int n = 1) 走的还是 6.2.3 的老规矩(从右往左连续)。
  3. 开头只用了 using std::cout; 和 using std::endl;——没有整段放行 std。这是"既省字、又安全"的写法。

30 秒实验:把第一段 namespace mytool { ... } 后面的 } 改成 };(加个分号)。你会发现编译照样过——但这不是"正确的写法",属于"碰巧能过"。记住:命名空间结尾不加分号(和 class/struct 刚好相反)。

六、常见误区

  1. "命名空间定义末尾要加分号"——不要。namespace X { ... } 后面不写分号。(class、struct 才要写——这是三者的显著差别,最容易顺手写错。)
  2. "命名空间里能放 main"——不能。main 必须在全局作用域,程序入口只认它。放进命名空间,链接时会报"找不到 main"。
  3. "命名空间必须一次写完"——不必。同名的命名空间可以分几处写,自动合并(特性 1)。但"类"不行,类必须一处定义完(10.1.2)。
  4. "命名空间里类的成员函数,实现也必须写在命名空间里"——不必。写到外面也行,但前缀要写全:命名空间::类名::函数名。
  5. "命名空间只是个语法糖,没什么用"——它是工程工具。库都是成组发布的:std、boost、cv(OpenCV)……命名空间就是"给一整包东西贴一个牌子",让使用者一眼知道名字的来源,也让不同库能共存。
自测三问(点开对答案) 1. 命名空间定义末尾要不要写分号?和 `class`/`struct` 比呢?——**命名空间不要写分号**(`namespace X { ... }`);**`class` 和 `struct` 必须写**(`class X { ... };`)。**别顺手写错。** 2. 同一个命名空间能分几次写?结果是什么?——**可以分多处写**(同一文件里、或不同文件里都行),**编译器会把它们合并成同一个命名空间**。**这是"库由多个文件共同组建"的基础**(10.6.3)。(注意:**类不能这样**——类必须一处定义完整。) 3. 命名空间里类的成员函数,实现写到外面时前缀怎么写?——**`命名空间名::类名::函数名`**(如 `mylib::Point::show`)。**`::` 可以链起来,每层代表一级作用域**;漏了命名空间前缀会报"`Point` 未声明"。

相关笔记