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

未命名的命名空间="不给名字的命名空间":namespace { ... }。里面定义的所有东西只在本文件内可见——别的文件看不见、也不会撞名。它的用途就一句话:"这是本文件自己的私事,别外传。" 它是老的 static 全局函数/变量的现代替代品(能装的东西更多:变量、函数、类、模板都行),也是""两个 .cpp 里同名函数互不干扰"的标准做法。

📝 本节和前面几节的关系

它是命名空间的最后一种形态(前面讲了命名、使用、跨文件、嵌套)。用到的旧知识:6.7.2 的 static("内部链接"这个概念第一次出现的地方——static 全局变量也是"只在本文件可见")、10.6.3 的多文件结构(本节解决"两个 .cpp 里辅助函数重名"的问题)。看懂它,你就能解释一个常见现象:为什么两个 .cpp 里都写一个同名的 helper() 却不冲突?

一、写法与效果

// ── file1.cpp ──
#include <iostream>

namespace {                       // ★ 未命名命名空间
    void helper() {               // 只能在本文件用
        std::cout << "file1 的 helper" << std::endl;
    }
    int  counter = 0;             // 也只能在本文件用
}

int main() {
    helper();                     // ✅ 本文件内直接用,不用前缀
    counter++;
    std::cout << counter << std::endl;
    return 0;
}
// ── file2.cpp ──
#include <iostream>

namespace {                       // 另一个文件里也有个未命名命名空间
    void helper() {               // ★ 同名!但不冲突
        std::cout << "file2 的 helper" << std::endl;
    }
}

void other() {
    helper();                     // 调的是 file2 自己的那个
}

关键效果:两个 .cpp 里都有一个 helper(),但链接时不会冲突——因为它们各在各的"无名空间"里,名字被内部链接(internal linkage)限制住了。

如果不用未命名命名空间:两个文件里都写 void helper()(都不加 static)→ 链接时 multiple definition of 'helper()'。

二、为什么"不用写前缀"就能用

因为编译器偷偷帮你做了两件事:

步骤 编译器做的事
① 起个内部唯一的名字 给这个未命名命名空间一个只有编译器知道的名字(比如 _anonymous_namespace_1),而且每个 .cpp 里的都不一样
② 加一条隐式的 using 相当于在你文件开头写了 using namespace <那个无名空间>;——所以本文件里可以直接用短名

所以:

namespace {
    void helper() { }
}

// 等价于(概念上):
// namespace _anon_1 { void helper() { } }
// using namespace _anon_1;

int main() {
    helper();               // 直接用短名(靠那条隐式 using)
    _anon_1::helper();      // 理论上也能这么写,但没人这么干
}

一句话:"不写名字"是为了让外面的人没法写 xxx::名字——它从设计上就拒绝被外部访问。

三、它和 static 的关系

在很久以前(C 语言时代),让一个函数"只在本文件可见"的写法是:

static void helper() { ... }        // 老写法:static 全局函数
static int  counter = 0;            // 老写法:static 全局变量

C++ 里有了未命名命名空间之后:

namespace {
    void helper() { ... }           // 新写法:能装的东西更多
    int  counter = 0;
    class Helper { ... };           // ★ static 做不到这个
    template <class T> void f(T);   // ★ 也不行
}
static 全局 未命名命名空间
函数、变量 ✅ 能 ✅ 能
类、结构体 ❌ 不能(static class 不是这个意思) ✅ 能
模板 ❌ 不能 ✅ 能
现代推荐度 老式,但仍常见 ✅ 更推荐(更通用)

两句实用建议: ① 看到 static 修饰全局函数,知道它的意思是"只在本文件可见"(不是"只初始化一次"——那是 6.7.2 讲的局部 static,两种 static 意思不同,别混)。 ② 自己写代码时,用未命名命名空间——它表达得更清楚,能装的东西也更多。

四、动手 Demo:两个文件里的"同名助手"互不打架

这个 Demo 需要两个 .cpp。 在 Dev-C++ 里建一个工程(10.6.3 的步骤),加两个文件。

文件 main.cpp:

#include <iostream>

// ── 本文件私有的助手 ──
namespace {
    void printTag() {
        std::cout << "[main 的 tag]" << std::endl;
    }

    int secretTotal = 100;             // 本文件私有
}

// 声明另一个文件里的公开函数
void report();

int main() {
    printTag();                        // 调本文件的
    std::cout << "secretTotal = " << secretTotal << std::endl;

    report();                          // 调另一个文件

    std::cout << "程序结束" << std::endl;
    return 0;
}

文件 other.cpp:

#include <iostream>

// ── 这个文件也有同名的助手(不冲突!)──
namespace {
    void printTag() {
        std::cout << "[other 的 tag]" << std::endl;
    }

    int secretTotal = 999;             // 另一个世界里的变量
}

void report() {                        // 公开函数,可以被 main 调用
    printTag();                        // 调的是 other.cpp 自己的那个
    std::cout << "other 的 secretTotal = " << secretTotal << std::endl;
}

看到什么算成功:

[main 的 tag]
secretTotal = 100
[other 的 tag]
other 的 secretTotal = 999
程序结束

读结果三句话:

  1. 两个文件各有一个 printTag() 和 secretTotal——同名却完全不冲突,输出各是各的([main 的 tag] 与 [other 的 tag]、100 与 999)。
  2. main.cpp 里的 printTag() 调到的是自己的那个——未命名命名空间的名字,优先在本文件里匹配。
  3. report() 能跨文件被调用(它在全局作用域,是"公开"的);而 printTag() 只能在自己文件里用。同一份代码里,一个"公开"、一个"私事",分得清清楚楚。

30 秒实验:把两个文件里的 namespace { ... } 都删掉(让 printTag 和 secretTotal 变成普通全局的),重新编译。你会立刻看到 multiple definition of 'printTag()' 和 multiple definition of 'secretTotal'——证明未命名命名空间确实在"隔离名字"。

五、常见误区

  1. "未命名命名空间里的东西是静态的、不占内存"——错。"未命名"指的是名字不对文件外可见,和内存、生命周期完全无关。它里面的变量依然是全局变量(程序启动就存在,直到程序结束)。
  2. "未命名命名空间和 static 全局函数是一回事,都该淘汰"——作用相同,但推荐度不同。未命名命名空间更通用(能装类、模板),现代 C++ 推荐用它。但看到 static 全局函数不要觉得奇怪——老代码里到处都是。
  3. "未命名命名空间应该放进头文件"——不要。头文件会被多个 .cpp 包含,每个包含者都会得到自己独立的一份——于是"你以为共享的东西"其实是各有一份,容易产生"为什么我在 A 里改了,B 里没变"的困惑。头文件里请老老实实用具名命名空间 + 声明。
  4. "未命名命名空间里能放 main"——不能。main 必须在全局作用域(10.6.2 的误区 2),放进任何命名空间都会导致"找不到程序入口"。
  5. "用了未命名命名空间,就完全不会重名了"——在文件之间不会,但在同一个文件内部还是会。同一个文件里写两个未命名命名空间,它们仍然是同一个(会自动合并,10.6.2 特性 1)——所以在一个文件里定义两个同名 helper(),照样重定义报错。
自测三问(点开对答案) 1. 未命名命名空间的作用是什么?里面的名字能被别的文件访问吗?——作用是**让名字"只在本文件可见"**(内部链接)。**别的文件访问不到**,也因此**不会和别的文件的同名标识符冲突**。本文件内可以**直接用短名**(编译器加了隐式 `using`)。 2. 它和 `static` 全局函数是什么关系?——**作用相同**(都让名字只在本文件可见),但**未命名命名空间能装的东西更多**:**变量、函数、类、结构体、模板都可以**,而 `static` **只能用于函数和变量**。**现代 C++ 更推荐未命名命名空间。** 3. 为什么不建议把未命名命名空间写进头文件?——因为**头文件会被多个 `.cpp` 包含**,**每个包含者都会得到自己独立的一份**——于是"看起来共享"的东西实际上**各有一份**,容易造成"改了没生效"的困惑。**头文件里应该用具名命名空间 + 声明。**

相关笔记