【C++ 从基础到项目实战】C++(七):继承与多态——面向对象的核心灵魂
2026/6/3 7:34:36 网站建设 项目流程

📌 阅读时长: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 \ / TeachingAssistant
classPerson{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);// ✅ OK

4.2 抽象类(接口类)

包含至少一个纯虚函数的类就是抽象类:

  • 不能实例化
  • 用于定义接口,约束子类行为
  • 其他语言中也叫"接口类"

4.3 C++ 中"虚"的家族

概念关键字用途
虚函数virtual void f()实现运行时多态
纯虚函数virtual void f() = 0定义接口,强制子类实现
虚继承class B : virtual public A解决菱形继承
虚基类被虚继承的基类虚继承中的共同祖先
虚析构virtual ~X()正确的多态释放
抽象类含纯虚函数的类不能实例化

小结

序号知识点一句话总结
1公有继承子类有父类非私有成员,public/protected/private 控制访问级别
2sizeof(类)只计算非静态成员,与函数和静态变量无关
3菱形继承多重继承导致重复继承→用虚继承解决
4虚函数virtual 实现运行时绑定,基类指针调用子类方法
5纯虚函数=0 声明,强制子类实现,含纯虚函数=抽象类不能实例化
6虚析构基类析构用 virtual,确保多态 delete 链式调用

下一篇文章,我们将学习运算符重载——让你自定义的类也能用+==<<操作,让代码更直观优雅。


本文是「C++ 从基础到项目实战」系列的第 7 篇。关注我,不错过后续更新。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询