📌 阅读时长:30分钟 | 关键词:C++、继承、多态、虚函数、纯虚函数、抽象类、虚继承、菱形继承
引言
如果说封装让代码更安全,那继承让代码更可复用,而多态让代码真正有了"智能"——同一个接口,不同对象表现出不同行为。这两者是面向对象编程最闪亮的两颗明珠。内容量大,但理解透彻后,C++ 的大门就算真正推开了。
一、继承:站在巨人的肩膀上
1.1 基本语法
classAnimal{// 基类(父类)public:std::string name;Animal(conststd::string&n):name(n){}voideat(){std::cout<<name<<" 在吃东西"<<std::endl;}};classBird:publicAnimal{// 派生类(子类),公有继承public:intwingspan;Bird(conststd::string&n,intw):Animal(n),wingspan(w){}voidfly(){std::cout<<name<<" 在飞"<<std::endl;}};intmain(){Birdsparrow("麻雀",20);sparrow.eat();// 继承自 Animalsparrow.fly();// Bird 自己的}1.2 三种继承方式
| 继承方式 | 基类 public → | 基类 protected → | 基类 private → |
|---|---|---|---|
public继承 | 保持 public | 保持 protected | 不可访问 |
protected继承 | 变 protected | 变 protected | 不可访问 |
private继承 | 变 private | 变 private | 不可访问 |
classBase{public:intpub;protected:intprot;private:intpriv;// 无论什么继承,子类都不可访问};1.3 类的大小(sizeof)
classMyClass{public:inta;// 4 字节voidfoo(){}// 成员函数不计入大小private:intb;// 4 字节staticintc;// 静态成员不计入大小};// sizeof(MyClass) = 8(只有两个 int)💡 类的大小 = 所有非静态成员变量大小之和 + 可能的对齐填充。成员函数和静态变量不影响。
1.4 final 关键字
classShapefinal{/* ... */};// 不能被继承// class Circle : public Shape {}; // ❌ 编译错误classBase{public:virtualvoiddraw()final{}// 阻止派生类重写此函数};二、继承进阶:菱形、歧义与虚继承
2.1 多重继承与歧义
C++ 允许一个类继承多个父类,但同名成员会产生歧义:
classA{public:voidprint(){std::cout<<"A"<<std::endl;}};classB{public:voidprint(){std::cout<<"B"<<std::endl;}};classC:publicA,publicB{};// C c; c.print(); ❌ 歧义!编译器不知道调哪个// c.A::print(); ✅ 用作用域符明确指定2.2 菱形继承问题
Person / \ Student Teacher \ / TeachingAssistantclassPerson{public:intage;};classStudent:publicPerson{};classTeacher:publicPerson{};classTA:publicStudent,publicTeacher{};// TA ta; ta.age; ❌ 歧义!Person 的 age 被继承了两次2.3 虚继承:解决菱形继承
classStudent:virtualpublicPerson{};// 虚继承classTeacher:virtualpublicPerson{};// 虚继承classTA:publicStudent,publicTeacher{};// Person 只被继承一次TA ta;ta.age=25;// ✅ 无歧义⚠️ 虚继承时,最底层派生类必须负责初始化虚基类。
2.4 函数的重载与覆盖
classBase{public:voidprint(inta){std::cout<<"Base int: "<<a<<std::endl;}voidprint(inta,intb){std::cout<<"Base 2 int"<<std::endl;}intdata=10;};classDerived:publicBase{public:voidprint(inta){std::cout<<"Derived int: "<<a<<std::endl;}// 覆盖 Base 所有 printintdata=20;// 覆盖 Base::data};// d.print(5); → 调 Derived::print// d.Base::print(5,10); → 调 Base::print(int,int)| 重载 (Overload) | 覆盖/隐藏 (Override/Hide) | |
|---|---|---|
| 作用域 | 同一个类内 | 不同作用域(基类/派生类) |
| 条件 | 参数列表不同 | 名字相同即可 |
| 函数名相同 + 参数不同 | ✅ | 也覆盖 |
三、多态:同一个接口,不同的行为
3.1 静态多态 vs 动态多态
// 静态多态:编译时确定(函数重载、模板)intadd(inta,intb){returna+b;}doubleadd(doublea,doubleb){returna+b;}// 动态多态:运行时确定(虚函数)3.2 虚函数:多态的基石
classShape{public:virtualvoiddraw()const{// virtual 关键字std::cout<<"画一个形状"<<std::endl;}};classCircle:publicShape{public:voiddraw()constoverride{// override 显式声明重写std::cout<<"画一个圆"<<std::endl;}};classSquare:publicShape{public:voiddraw()constoverride{std::cout<<"画一个正方形"<<std::endl;}};intmain(){Shape*s1=newCircle();Shape*s2=newSquare();s1->draw();// "画一个圆" ← 运行时绑定!如果没 virtual 会输出 "画一个形状"s2->draw();// "画一个正方形"deletes1;deletes2;}3.3 虚函数原理(简版)
有虚函数的类,编译器会添加一个隐藏的「虚函数表」指针 (vptr), 指向该类的虚函数表 (vtable),表中存放各个虚函数的地址。 运行时通过 vptr→vtable→函数地址 实现动态绑定。3.4 虚析构函数:多态 delete 的救星
classBase{public:virtual~Base(){std::cout<<"~Base"<<std::endl;}// 虚析构!};classDerived:publicBase{public:~Derived(){std::cout<<"~Derived"<<std::endl;}};intmain(){Base*p=newDerived();deletep;// 输出 "~Derived" 再 "~Base"// 如果 ~Base() 不是 virtual,只输出 "~Base",资源泄漏!}⚠️ 铁律:只要类可能被继承,析构函数就声明为 virtual。
四、纯虚函数与抽象类
4.1 纯虚函数:强制子类实现
classShape{public:virtualdoublegetArea()=0;// = 0 声明纯虚函数};classCircle:publicShape{private:doubleradius;public:Circle(doubler):radius(r){}doublegetArea()override{return3.14159*radius*radius;}};// Shape s; ❌ 不能实例化抽象类!Circlec(5);// ✅ OK4.2 抽象类(接口类)
包含至少一个纯虚函数的类就是抽象类:
- 不能实例化
- 用于定义接口,约束子类行为
- 其他语言中也叫"接口类"
4.3 C++ 中"虚"的家族
| 概念 | 关键字 | 用途 |
|---|---|---|
| 虚函数 | virtual void f() | 实现运行时多态 |
| 纯虚函数 | virtual void f() = 0 | 定义接口,强制子类实现 |
| 虚继承 | class B : virtual public A | 解决菱形继承 |
| 虚基类 | 被虚继承的基类 | 虚继承中的共同祖先 |
| 虚析构 | virtual ~X() | 正确的多态释放 |
| 抽象类 | 含纯虚函数的类 | 不能实例化 |
小结
| 序号 | 知识点 | 一句话总结 |
|---|---|---|
| 1 | 公有继承 | 子类有父类非私有成员,public/protected/private 控制访问级别 |
| 2 | sizeof(类) | 只计算非静态成员,与函数和静态变量无关 |
| 3 | 菱形继承 | 多重继承导致重复继承→用虚继承解决 |
| 4 | 虚函数 | virtual 实现运行时绑定,基类指针调用子类方法 |
| 5 | 纯虚函数 | =0 声明,强制子类实现,含纯虚函数=抽象类不能实例化 |
| 6 | 虚析构 | 基类析构用 virtual,确保多态 delete 链式调用 |
下一篇文章,我们将学习运算符重载——让你自定义的类也能用+、==、<<操作,让代码更直观优雅。
本文是「C++ 从基础到项目实战」系列的第 7 篇。关注我,不错过后续更新。