C++ 继承与多态:代码复用与设计模式核心 一、引言面向对象如何解决 “重复造轮子”在前面一篇中我们学习了类、对象、封装、构造与析构已经能够把现实事物抽象成类写出安全、规范、模块化的 C 代码。但在实际开发中我们经常会遇到这样的问题多个类拥有相同的属性和行为。 例如教师姓名、年龄、工号、吃饭、睡觉、讲课学生姓名、年龄、学号、吃饭、睡觉、学习工人姓名、年龄、工号、吃饭、睡觉、工作如果每个类都重复写 “姓名、年龄、吃饭、睡觉”代码会大量冗余难以维护修改一处需要改所有类无法体现 “共性与差异”为了解决这个问题C 面向对象提供了第二大特性继承。 继承的核心价值代码复用 扩展。而在继承的基础上C 实现了最强大、最核心的特性多态。 多态让程序可以 **“一个接口多种实现”**是设计模式、框架、组件化开发的灵魂。本篇文章将系统、深入、完整讲解继承的概念、语法、方式基类、派生类、构造析构顺序继承中的成员访问、同名处理多态的原理虚函数、重写纯虚函数与抽象类虚析构、菱形继承与虚继承多态的经典应用与设计思想本篇是 C 面向对象最难、最重要、最值钱的一篇。 掌握继承与多态你才算真正进入 “工程级 C” 开发。二、继承抽取共性实现复用一什么是继承继承让一个类派生类 / 子类拥有另一个类基类 / 父类的属性和行为。 子类可以直接使用父类成员也可以增加新成员或重写父类行为。例如父类Person姓名、年龄、eat ()、sleep ()子类Student : Person新增学号、study ()子类Teacher : Person新增工号、teach ()子类自动拥有父类所有可继承成员。二继承语法cpp运行class 子类 : 继承方式 父类 { 新增成员; };示例cpp运行class Person { public: string name; int age; void eat() { cout 吃饭 endl; } }; class Student : public Person { public: int id; void study() { cout 学习 endl; } };使用cpp运行Student s; s.name 小明; // 来自父类 s.age 18; // 来自父类 s.id 1001; // 子类自己 s.eat(); // 父类方法 s.study(); // 子类方法三三种继承方式public 公共继承最常用父类 public → 子类 public父类 protected → 子类 protected父类 private → 不可访问protected 保护继承private 私有继承工程规范99% 情况使用 public 继承。四继承中的访问权限父类 private 成员无论如何继承子类都不能直接访问。若想让子类能访问但外部不能访问 → 使用protected。三、继承中的构造与析构顺序继承关系中先构造父类再构造子类先析构子类再析构父类。执行顺序父类构造子类构造子类析构父类析构示例cpp运行class A { public: A() { cout A构造 endl; } ~A() { cout A析构 endl; } }; class B : public A { public: B() { cout B构造 endl; } ~B() { cout B析构 endl; } }; int main() { B b; return 0; }输出plaintextA构造 B构造 B析构 A析构意义父类资源先初始化子类才能安全使用。四、继承中的同名成员处理一子类与父类成员同名子类成员会隐藏父类同名成员。若要访问父类同名成员必须加父类::。示例cpp运行来源0d.mzjlrdb.cn来源0i.mzjlrdb.cn来源5e.mzjlrdb.cn来源2f.mzjlrdb.cn来源6t.mzjlrdb.cn来源6c.mzjlrdb.cn来源k2.mzjlrdb.cn来源0b.mzjlrdb.cn来源5i.mzjlrdb.cn来源a1.mzjlrdb.cnclass Father { public: int a 10; }; class Son : public Father { public: int a 20; void show() { cout a endl; // 子类 20 cout Father::a endl; // 父类 10 } };二同名函数子类函数会隐藏父类所有同名函数包括重载版本。 想调用父类版本必须写Father::func(...)。五、多态面向对象最强大的特性一什么是多态多态同一接口不同实现。同一个方法调用由于对象不同表现出不同行为。多态分类静态多态函数重载、运算符重载—— 编译时确定动态多态虚函数重写—— 运行时确定工程中说的 “多态” 一律指动态多态。二动态多态形成条件有继承关系子类重写父类虚函数父类指针 / 引用指向子类对象三虚函数与重写父类函数加virtual→ 虚函数。 子类重新实现该函数函数签名完全相同→重写override。示例cpp运行class Animal { public: virtual void speak() { cout 动物叫 endl; } }; class Dog : public Animal { public: void speak() override { cout 汪汪汪 endl; } }; class Cat : public Animal { public: void speak() override { cout 喵喵喵 endl; } };四多态调用父类指针指向子类对象cpp运行Animal *p; p new Dog; p-speak(); // 输出 汪汪汪 p new Cat; p-speak(); // 输出 喵喵喵同一个指针、同一个调用行为不同 → 这就是多态六、多态的底层原理多态依靠虚函数表vtable 虚指针vptr实现。当类中有虚函数编译器会生成虚表对象中会增加一个虚指针指向虚表调用虚函数时通过指针找到对象 → 查虚表 → 调用对应函数运行时才确定调用哪个函数 →动态绑定。这就是多态能 “晚绑定、灵活扩展” 的根本原因。七、纯虚函数与抽象类一纯虚函数没有实现只做接口声明cpp运行virtual void func() 0;二抽象类包含纯虚函数的类称为抽象类。特点不能实例化对象强制子类必须重写纯虚函数用于定义统一接口。示例cpp运行class Shape { public: virtual double area() 0; // 纯虚函数 }; class Circle : public Shape { double r; public: double area() override { return 3.14*r*r; } };抽象类是设计模式的核心面向接口编程。八、虚析构解决多态对象释放问题问题父类指针指向 new 出来的子类对象delete 时如果父类析构不是虚析构只会调用父类析构不会调用子类析构 → 内存泄漏解决方案把父类析构写成virtual 虚析构cpp运行class Base { public: virtual ~Base() { cout 父类析构 endl; } };只要父类析构是虚析构delete 子类对象时会完整析构。工程铁律只要类里有虚函数析构函数一律加 virtual。九、菱形继承与虚继承一菱形继承问题A 是父类 B、C 继承 A D 继承 B、CD 对象中会有两份 A导致二义性、数据冗余。二虚继承解决cpp运行class B : virtual public A {}; class C : virtual public A {};虚继承让最终子类只保留一份共同基类成员。十、多态经典实战案例计算器类cpp运行来源qd3.mzjlrdb.cn来源0q.qd3.mzjlrdb.cn来源4g.qd3.mzjlrdb.cn来源f6d.qd3.mzjlrdb.cn来源e5.qd3.mzjlrdb.cn来源h0.qd3.mzjlrdb.cn来源4f.qd3.mzjlrdb.cn来源9a.qd3.mzjlrdb.cn来源2i.qd3.mzjlrdb.cn来源d0.qd3.mzjlrdb.cn#include iostream using namespace std; // 抽象类接口 class Calculator { public: int num1, num2; virtual int getResult() 0; }; // 加法 class Add : public Calculator { public: int getResult() override { return num1 num2; } }; // 减法 class Sub : public Calculator { public: int getResult() override { return num1 - num2; } }; int main() { Calculator *c new Add; c-num1 10; c-num2 20; cout c-getResult() endl; // 30 delete c; c new Sub; c-num1 50; c-num2 20; cout c-getResult() endl; // 30 delete c; return 0; }优点新增乘法、除法完全不用修改旧代码符合开闭原则对扩展开放对修改关闭这就是框架与设计模式的基础十一、继承与多态工程规范优先使用public 继承父类中提供protected让子类访问多态必须依赖虚函数重写抽象类用纯虚函数定义接口有虚函数必须写虚析构避免复杂菱形继承必要时使用虚继承子类重写函数加上override不要把继承用于 “简单组合”优先组合优于继承。十二、常见错误面试高频重写时函数签名不一致 → 不是重写是重载忘记写 virtual → 不构成多态调用父类版本抽象类试图实例化 → 编译失败多态对象不使用虚析构 → 内存泄漏菱形继承不使用虚继承 → 成员二义性子类隐藏父类同名函数调用出错。十三、本章总结本篇文章系统、深入、完整讲解了 C 面向对象最核心的两大特性继承抽取共性、代码复用、扩展类功能public/protected/private 继承构造析构顺序同名成员处理、父类作用域访问多态同一接口、不同实现动态绑定虚函数、重写、纯虚函数、抽象类虚析构防止多态对象泄漏虚继承解决菱形继承问题多态是设计模式、框架、组件化、接口化的基石。继承解决复用多态解决扩展。掌握它们你就掌握了大型 C 项目的设计灵魂。下一篇预告第 8 篇《C 运算符重载、友元与对象模型深入》4000 字以上将带你彻底理解运算符重载 - * / -- ! [] 友元函数、友元类对象模型、内存布局、this 底层实现常对象、常成员、常函数