嵌套类="定义在另一个类里面的类"——像抽屉里的抽屉。外面引用它要写成 外层类::内层类。它的价值是"把只给外层用的辅助类藏起来"——不污染全局命名空间、也让读者一眼看出"这个类是给谁服务的"。两条关系必须记清:① 嵌套类的作用域跟着外层走(外面要带前缀);② 它不自动获得外层对象的特殊权限——想访问外层的普通成员,得把外层对象传进来。
这是"类也是一种作用域"(6.4)的进一步体现——类里面可以再套类,作用域也再套一层。本节要和 10.4.4 的 this 联系起来理解:嵌套类里没有外层类的 this,所以它看不到外层对象的普通成员——这一点和"静态成员函数没有 this"是同一类问题。另外,10.5 的友元会和嵌套类配合使用(嵌套类要碰外层私有成员时的另一条路)。
一、写法与引用方式
class Outer { // 外层类
public:
class Inner { // ★ 嵌套类:定义在 Outer 里面
private:
int v;
public:
Inner(int x) : v(x) { }
void show() { cout << "Inner: " << v << endl; }
};
void test() {
Inner in(5); // 外层内部用,**不用写前缀**
in.show();
}
};
int main() {
Outer::Inner a(10); // ★ 外面用,必须写 Outer::
a.show();
Outer::Inner b(20); // 再造一个
b.show();
return 0;
}
| 在哪儿用 | 写法 |
|---|---|
| 外层类内部 | Inner(直接写,不用前缀) |
外面(包括 main) |
Outer::Inner(必须带外层名,就像"某部门的小组") |
作用域链:
Outer::Inner这个写法和 10.1.2 的类名::函数名、2.1.3 的std::cout用的是同一个符号(::)——它就是在说"这个名字属于谁"。套一层就多写一段前缀。
二、嵌套类能"看到"外层什么
这是本节最容易搞混的地方。结论先给:
| 外层的东西 | 嵌套类能直接访问吗 |
|---|---|
外层的类型名(如外层的 typedef、外层定义的另一个类) |
✅ 能 |
| 外层的静态成员(10.4.3) | ✅ 能(要带 Outer::) |
| 外层的普通成员变量/函数 | ❌ 不能——除非你把外层对象传进来 |
| 外层的私有成员 | 看情况(见下) |
为什么不能访问外层的普通成员? 因为嵌套类的对象和外层类的对象是两个独立的东西——它没有"外层对象的 this"(10.4.4)。
class Outer {
private:
int outerValue; // 外层的普通成员
public:
class Inner {
public:
void check() {
// cout << outerValue; // ❌ 报错:不知道是"哪个 Outer 的"
}
void checkBetter(Outer &o) { // ✅ 正确做法:把外层对象传进来
// 这时才能访问 o 的成员(能不能访问私有成员,见下)
}
};
};
关于"外层私有成员"这一格:
- 较老的 C++ 标准:嵌套类不能访问外层类的私有成员。
- C++11 及以后:可以访问(标准后来放宽了)。
- 教材时代差异会导致说法不一。实用建议:别依赖"嵌套类能碰外层私有成员"这件事——要么把需要的东西当参数传进来,要么用 10.5 的友元明确写清楚。
三、它的实际价值:把辅助类藏起来
典型场景:一个链表类,需要一个"节点"类。
不用嵌套的写法(节点类裸露在全局):
class ListNode { // 谁都能看见,还能被随便乱用
public:
int value;
ListNode *next;
};
class LinkedList {
private:
ListNode *head;
// ...
};
用嵌套的写法(干净):
class LinkedList {
private:
class Node { // ★ 藏在里面:外界看不到,也不会误用
public:
int value;
Node *next;
};
Node *head;
public:
LinkedList() : head(nullptr) { }
void push(int v) { /* ... */ }
void print() { /* ... */ }
};
好处三条:
| 好处 | 说明 |
|---|---|
| 不污染命名空间 | 全局里少一个 Node 这样的通用名字,不会和别人的 Node 撞名 |
| 表达从属关系 | 看代码的人一眼知道:Node 是给 LinkedList 用的,不是通用零件 |
| 可以反复用同名 | 另一个类里也能有个自己的 Node(Stack::Node、Tree::Node),互不干扰 |
一句话:嵌套类是"内部零件"——外面不需要知道它、也不该用它的地方,就别让它露在外面。
四、动手 Demo:一个"订单 + 订单明细行"的模型
#include <iostream>
#include <cstring>
using namespace std;
class Order {
public:
// ── 嵌套类:订单里的一行明细(外界用不着单独用它)──
class Item {
private:
char name[20];
int qty;
double price;
public:
Item() : qty(0), price(0) { name[0] = '\0'; }
void set(const char *n, int q, double p) {
strcpy(name, n);
qty = q;
price = p;
}
double amount() const { return qty * price; } // 小计
void show() const {
cout << " " << name << " × " << qty
<< " = " << amount() << endl;
}
};
private:
char customer[20];
Item items[5]; // 用嵌套类当成员类型
int count;
static double totalAmount; // 静态成员(10.4.3)
public:
Order(const char *c) : count(0) {
strcpy(customer, c);
totalAmount = 0;
}
// 外层内部用 Item:不用写 Order::
void addItem(const char *n, int q, double p) {
if (count < 5) {
items[count].set(n, q, p);
totalAmount += items[count].amount();
count++;
}
}
void show() const {
cout << "客户:" << customer << endl;
for (int i = 0; i < count; i++) items[i].show();
cout << "合计:" << totalAmount << endl;
}
static double getTotal() { return totalAmount; }
};
double Order::totalAmount = 0; // 静态成员类外定义
int main() {
Order o("张三");
o.addItem("笔记本", 2, 5.5);
o.addItem("签字笔", 3, 2.0);
o.show();
// 外面也能单独用嵌套类,但要写全前缀
Order::Item extra;
extra.set("橡皮", 1, 1.5);
cout << "单独建一行:" << endl;
extra.show();
return 0;
}
看到什么算成功:
客户:张三
笔记本 × 2 = 11
签字笔 × 3 = 6
合计:17
单独建一行:
橡皮 × 1 = 1.5
读结果三句话:
- 外层里用
Item不用写前缀(Item items[5];)、外面用要写Order::Item——作用域链一目了然。 Item有自己独立的show()——每个类管自己的事(职责单一,10.1.1)。totalAmount是外层的静态成员(10.4.3):所有订单共用一份——这在本例里是"演示需要",真写订单系统时它应该按订单分开(正好说明:static 用错了地方会带来麻烦,用之前先问"这个数据该不该全类共享")。
30 秒实验:把 class Item 从 Order 里面搬到外面(变成两个平级的类),代码还需要改哪里?——你会发现 Order::Item 要全部改成 Item,而 Item 这个名字从此占用了全局。这就是嵌套"把零件藏起来"的实际效果。
五、常见误区
- "嵌套类的对象里包含一个外层对象"——不包含。
Order::Item只是一个普通类,它和外层对象之间没有任何自动的联系。"住在里面"只是"名字的定义位置在里面",不是"内存里包含关系"。 - "嵌套类能随便访问外层的私有成员"——要小心。C++11 起可以,但老编译器/老教材说不行。稳妥做法:需要就把外层对象当参数传进来,或者用 10.5 的友元写清楚。别把"标准放宽了"当成可以随便用的理由。
- "嵌套类的好处是省内存"——不是。它的好处是作用域与可读性:藏起辅助类、避免命名冲突、表达从属关系。它和内存没关系。
- "嵌套层次越深越高级"——恰恰不好。两三层已经够用,再深就难读、难维护了(和 9.3.3 说"类图别套太多层"是一个道理)。
- "定义在外层里的类,一定得用嵌套写法引用"——在外层内部不用(直接写
Inner),在外面才要(Outer::Inner)。别在类里也写全前缀——虽然通常能编译,但没必要,还容易看错。
自测三问(点开对答案)
1. 嵌套类在外层类内部和外部,分别怎么写?——**外层内部**直接写 `Inner`;**外部**(包括 `main`)必须写 **`Outer::Inner`**——这就是作用域链,"套一层就多写一段前缀"。 2. 嵌套类能直接访问外层类的普通成员吗?为什么?——**不能**。因为**它没有外层对象的 `this`**(10.4.4),不知道自己该访问哪个外层对象的数据。**正确做法:把外层对象作为参数传进来。**(外层的**静态成员**和**类型名**可以直接用。) 3. 用嵌套类最主要的好处是什么?——**把"只给外层用的辅助类"藏起来**:① 不污染全局命名空间(少一个通用名如 `Node`);② 一眼看出从属关系(`Node` 服务于 `LinkedList`);③ 不同外层类可以各自有一个同名嵌套类,互不冲突。- ☐ 10.4.6 局部类(下一节)