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

嵌套类="定义在另一个类里面的类"——像抽屉里的抽屉。外面引用它要写成 外层类::内层类。它的价值是"把只给外层用的辅助类藏起来"——不污染全局命名空间、也让读者一眼看出"这个类是给谁服务的"。两条关系必须记清:① 嵌套类的作用域跟着外层走(外面要带前缀);② 它不自动获得外层对象的特殊权限——想访问外层的普通成员,得把外层对象传进来。

📝 本节和前面几节的关系

这是"类也是一种作用域"(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 的成员(能不能访问私有成员,见下)
        }
    };
};

关于"外层私有成员"这一格:

三、它的实际价值:把辅助类藏起来

典型场景:一个链表类,需要一个"节点"类。

不用嵌套的写法(节点类裸露在全局):

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

读结果三句话:

  1. 外层里用 Item 不用写前缀(Item items[5];)、外面用要写 Order::Item——作用域链一目了然。
  2. Item 有自己独立的 show()——每个类管自己的事(职责单一,10.1.1)。
  3. totalAmount 是外层的静态成员(10.4.3):所有订单共用一份——这在本例里是"演示需要",真写订单系统时它应该按订单分开(正好说明:static 用错了地方会带来麻烦,用之前先问"这个数据该不该全类共享")。

30 秒实验:把 class Item 从 Order 里面搬到外面(变成两个平级的类),代码还需要改哪里?——你会发现 Order::Item 要全部改成 Item,而 Item 这个名字从此占用了全局。这就是嵌套"把零件藏起来"的实际效果。

五、常见误区

  1. "嵌套类的对象里包含一个外层对象"——不包含。Order::Item 只是一个普通类,它和外层对象之间没有任何自动的联系。"住在里面"只是"名字的定义位置在里面",不是"内存里包含关系"。
  2. "嵌套类能随便访问外层的私有成员"——要小心。C++11 起可以,但老编译器/老教材说不行。稳妥做法:需要就把外层对象当参数传进来,或者用 10.5 的友元写清楚。别把"标准放宽了"当成可以随便用的理由。
  3. "嵌套类的好处是省内存"——不是。它的好处是作用域与可读性:藏起辅助类、避免命名冲突、表达从属关系。它和内存没关系。
  4. "嵌套层次越深越高级"——恰恰不好。两三层已经够用,再深就难读、难维护了(和 9.3.3 说"类图别套太多层"是一个道理)。
  5. "定义在外层里的类,一定得用嵌套写法引用"——在外层内部不用(直接写 Inner),在外面才要(Outer::Inner)。别在类里也写全前缀——虽然通常能编译,但没必要,还容易看错。
自测三问(点开对答案) 1. 嵌套类在外层类内部和外部,分别怎么写?——**外层内部**直接写 `Inner`;**外部**(包括 `main`)必须写 **`Outer::Inner`**——这就是作用域链,"套一层就多写一段前缀"。 2. 嵌套类能直接访问外层类的普通成员吗?为什么?——**不能**。因为**它没有外层对象的 `this`**(10.4.4),不知道自己该访问哪个外层对象的数据。**正确做法:把外层对象作为参数传进来。**(外层的**静态成员**和**类型名**可以直接用。) 3. 用嵌套类最主要的好处是什么?——**把"只给外层用的辅助类"藏起来**:① 不污染全局命名空间(少一个通用名如 `Node`);② 一眼看出从属关系(`Node` 服务于 `LinkedList`);③ 不同外层类可以各自有一个同名嵌套类,互不冲突。

相关笔记