定义命名空间的写法极简: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
=== 结束 ===
读结果三句话:
mytool分了三段写(常量/函数一段、类一段、辅助函数一段)——它们自动合并成一个命名空间,main里都能访问。c.add()用了默认参数——add(int n = 1)走的还是 6.2.3 的老规矩(从右往左连续)。- 开头只用了
using std::cout;和using std::endl;——没有整段放行std。这是"既省字、又安全"的写法。
30 秒实验:把第一段 namespace mytool { ... } 后面的 } 改成 };(加个分号)。你会发现编译照样过——但这不是"正确的写法",属于"碰巧能过"。记住:命名空间结尾不加分号(和 class/struct 刚好相反)。
六、常见误区
- "命名空间定义末尾要加分号"——不要。
namespace X { ... }后面不写分号。(class、struct才要写——这是三者的显著差别,最容易顺手写错。) - "命名空间里能放
main"——不能。main必须在全局作用域,程序入口只认它。放进命名空间,链接时会报"找不到main"。 - "命名空间必须一次写完"——不必。同名的命名空间可以分几处写,自动合并(特性 1)。但"类"不行,类必须一处定义完(10.1.2)。
- "命名空间里类的成员函数,实现也必须写在命名空间里"——不必。写到外面也行,但前缀要写全:
命名空间::类名::函数名。 - "命名空间只是个语法糖,没什么用"——它是工程工具。库都是成组发布的:
std、boost、cv(OpenCV)……命名空间就是"给一整包东西贴一个牌子",让使用者一眼知道名字的来源,也让不同库能共存。
自测三问(点开对答案)
1. 命名空间定义末尾要不要写分号?和 `class`/`struct` 比呢?——**命名空间不要写分号**(`namespace X { ... }`);**`class` 和 `struct` 必须写**(`class X { ... };`)。**别顺手写错。** 2. 同一个命名空间能分几次写?结果是什么?——**可以分多处写**(同一文件里、或不同文件里都行),**编译器会把它们合并成同一个命名空间**。**这是"库由多个文件共同组建"的基础**(10.6.3)。(注意:**类不能这样**——类必须一处定义完整。) 3. 命名空间里类的成员函数,实现写到外面时前缀怎么写?——**`命名空间名::类名::函数名`**(如 `mylib::Point::show`)。**`::` 可以链起来,每层代表一级作用域**;漏了命名空间前缀会报"`Point` 未声明"。- ☐ 10.6.3 在多个文件中定义命名空间(下一节)
相关笔记
- 使用命名空间 —— 上一节:三种使用方式与别名
- 在多个文件中定义命名空间 —— 下一节:多文件协作
- 类的声明与定义 ——
::与"声明定义分家"的基础版 - 类的实现 —— 头文件与源文件的分工
- 🔷 C++基础 · 索引 —— 全课路线图