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

命名空间=给名字加"姓氏"——用来解决重名冲突。你写的 print() 和别人库里的 print() 可以同时存在,只要一个叫 my::print()、一个叫 lib::print()。这一节其实是在"回收旧账":从第二章起,我们每一份代码开头都写着 using namespace std;——当时只是照抄,现在终于知道它是什么、为什么需要它、以及为什么要慎用它。

📝 本节和前面几节的关系

这是 2.1.3「命名空间」的完整版。当时只讲到"std:: 是给名字加姓氏",本节把定义、使用、跨文件、嵌套、匿名五种情形一次说清。它和前面几节的关系是:10.4.5 的嵌套类解决"类里重名",本节解决"全局重名"——都是"用作用域把名字隔开"这同一个思路。另外,本节会用到 10.1.2 的 ::(作用域解析符),它在这里是主角。

一、问题从哪来

你写了一个 print():

void print() { cout << "我的 print" << endl; }

同事也写了一个 print(),而你们要一起用:

void print() { cout << "同事的 print" << endl; }   // ❌ 重定义!

编译错误:

error: redefinition of 'void print()'

更麻烦的是"库撞名":你用了两个第三方库,它们各有一个 init()——你甚至没法改人家的代码。

这时候需要什么?——一个能把名字隔开的"容器"。

二、命名空间解决什么

给名字加上"前置的姓氏":

namespace mine {                    // 我的地盘
    void print() { cout << "我的 print" << endl; }
}

namespace theirs {                  // 同事的地盘
    void print() { cout << "同事的 print" << endl; }
}

int main() {
    mine::print();                  // 我的 print
    theirs::print();                // 同事的 print
    return 0;
}

两个 print 同时存在,井水不犯河水。 调用时用 命名空间名::函数名 指明要哪一个。

这就是 2.1.3 说的"std::cout 是给名字加姓氏"——cout 的真名是 std::cout,它姓 std。std 是 C++ 标准库的命名空间,里面塞着 cout、cin、string、vector 等成千上万个名字。

三、using namespace std; 到底做了什么

先说结论:它把 std 里所有的名字"提升到当前作用域"——于是你写 cout 时,编译器会自动去 std 里找。

等价关系:

// 写法 A:不用 using,写全名
std::cout << std::endl;

// 写法 B:开头加 using namespace std;
using namespace std;
cout << endl;                    // 编译器自动补成 std::cout

它为什么让人又爱又恨:

好处 麻烦
少打很多字(cout 比 std::cout 短一半) 把 std 里的所有名字都"放出来"了——你自己的 count、swap、distance 都可能和标准库的撞名,报错还很难懂

所以工程上的建议是:

场合 建议
学习、练习、小程序 ✅ 用 using namespace std;——省事,教程也都这么写
真实项目、头文件里 ❌ 别用。改成写全名(std::cout),或者只放行你要的几个名字:using std::cout;
头文件(.h)里 ❌❌ 绝对别用——头文件会被别人包含,你的 using 会污染别人的代码

一句话:using namespace std; 是"练习场的便利",不是"工程的做法"。 以后看别人的代码,开头没写 using 而是满篇 std::,那不是啰嗦,是规范。

四、本节路线

小节 讲什么
10.6.1 使用命名空间 三种使用方式(写全名 / using 声明 / using namespace),以及各自的风险
10.6.2 定义命名空间 怎么写一个自己的命名空间;里面能放什么
10.6.3 在多个文件中定义命名空间 同一个命名空间可以"分几处写";头文件与源文件怎么配合
10.6.4 定义嵌套的命名空间 命名空间里再套命名空间(像"公司 → 部门 → 小组")
10.6.5 定义未命名的命名空间 无名命名空间:让名字"只在本文件可用"(替代老的 static 用法)

五、动手 Demo:三种写法一次对照

#include <iostream>

// ── 先定义两个自己的命名空间 ──
namespace math {
    int  square(int x) { return x * x; }
    const double PI = 3.14159;
}

namespace text {
    void greet() { std::cout << "你好,命名空间!" << std::endl; }
}

// ── 三种用法 ──

// 写法 A:什么都不 using,全部写全名(最清楚,工程推荐)
void useFullName() {
    std::cout << "A: 4 的平方 = " << math::square(4) << std::endl;
    std::cout << "A: PI = " << math::PI << std::endl;
    text::greet();
}

// 写法 B:using 声明,只放行"一个"名字(折中,推荐)
using math::square;                  // 只把 square 放出来
void useOneName() {
    std::cout << "B: 5 的平方 = " << square(5) << std::endl;
    // std::cout << PI;              // ❌ PI 没被放行,还得写 math::PI
    std::cout << "B: PI = " << math::PI << std::endl;
}

// 写法 C:整个命名空间放行(省事,但风险最大)
using namespace text;                // 把 text 里所有名字都放出来
void useWholeNamespace() {
    greet();                         // 可以直接叫
}

int main() {
    useFullName();
    useOneName();
    useWholeNamespace();

    // 注意:这里没写 using namespace std; —— 全篇用 std:: 前缀也完全能写
    std::cout << "程序结束" << std::endl;
    return 0;
}

看到什么算成功:

A: 4 的平方 = 16
A: PI = 3.14159
你好,命名空间!
B: 5 的平方 = 25
B: PI = 3.14159
你好,命名空间!
程序结束

读结果三句话:

  1. 三种写法都能跑,输出完全一样——差别只在"名字写多长、风险有多大"。
  2. 写法 B(using math::square;)只放行了 square——所以 PI 还得写 math::PI。这就是"部分放行"的意思。
  3. 整个程序没写一句 using namespace std;,却照常跑——证明它只是"省字"的工具,不是必需的。

30 秒实验:把 using namespace math; 加到文件开头,然后在 main 里写 cout << PI << endl;(不用前缀)。能跑。但再定义一个自己的 int PI = 0; 试试——立刻撞名报错。这就是"整个命名空间放行"的代价:名字变短了,也变挤了。

六、常见误区

  1. "不写 using namespace std; 就用不了 cout"——错。写 std::cout 一样能用,而且这才是工程里的规范写法。
  2. "using namespace std; 完全无害"——在小程序里基本无害,在项目里风险不小。它会把 std 里所有名字都放出来——count、swap、distance、size 这些你很可能也在用的名字都可能撞车。
  3. "头文件里写 using namespace std; 方便点"——最不能这么做。头文件会被很多人、很多文件包含,你的 using 会像蒲公英一样污染整个项目。这是 C++ 社区的共识级禁忌。
  4. "命名空间就是一种类"——不一样。命名空间只是"名字的分组",它不能被实例化、不能有对象、没有 this。它的唯一作用是给名字划定范围。
  5. "命名空间的名字必须很短"——看用途。库的命名空间通常很短(std、cv、boost),因为要频繁书写;自己项目里可以长一点、更有辨识度(如 mycompany::network::),反正有 using 可以局部简化。
自测三问(点开对答案) 1. 命名空间解决什么问题?——**重名冲突**。它给名字加了一层"姓氏",让**不同来源的同名标识符可以共存**(`mine::print()` 与 `theirs::print()`)。 2. `using namespace std;` 做了什么?为什么工程上不推荐?——它把 **`std` 里所有名字**都提升到当前作用域,于是写 `cout` 就等于 `std::cout`。**不推荐的原因**:它把**成千上万个名字一次性放出来**,你自定义的 `count`、`swap`、`distance` 等容易和标准库撞名,**报错还难懂**;**放在头文件里更是会污染所有包含它的代码**。 3. 三种使用方式各是什么?——① **写全名**(`std::cout`,最清楚,工程推荐);② **`using` 声明**(`using std::cout;`,只放行一个名字,折中推荐);③ **`using namespace`**(整个放行,最省字但风险最大,练习可用)。

相关笔记