C++中的继承
继承是 C++ 面向对象三大特性(封装、继承、多态)的核心,核心价值是代码复用和层次化类设计。本文全面覆盖继承的语法、对象模型、构造析构、同名成员处理、多继承及菱形继承等关键知识点,并附注意事项和示例。
一、继承的基本语法
- 语法格式
// 基类(父类):被继承的类class 基类名 {// 成员(属性、方法)};// 派生类(子类):继承基类class 派生类名 : 继承方式 基类名 {// 子类扩展的成员(可新增/重写)};- 核心概念
基类(父类):提供通用属性 / 方法的类(如Person);
派生类(子类):复用基类成员,同时扩展自身功能的类(如Student);
继承的本质:子类拥有基类的所有成员(private成员虽不可直接访问,但仍占用内存)。 - 基础示例
#include<iostream>#include<string>usingnamespace std;// 基类:PersonclassPerson{public: string name;int age;voidshowInfo(){ cout <<"姓名:"<< name <<",年龄:"<< age << endl;}};// 派生类:Student(public继承Person)classStudent:publicPerson{public:int studentId;// 子类扩展属性voidshowStudentInfo(){// 复用基类成员 cout <<"姓名:"<< name <<",年龄:"<< age <<",学号:"<< studentId << endl;}};intmain(){ Student s; s.name ="张三";// 访问基类public成员 s.age =20; s.studentId =1001; s.showInfo();// 调用基类方法 s.showStudentInfo();// 调用子类方法return0;}二、继承方式(3 种)
继承方式决定基类成员在子类中的访问权限,C++ 支持 3 种继承方式,默认是private。
- 关键规则
基类private成员:无论哪种继承方式,子类都不可直接访问(可通过基类public/protected方法间接访问);
继承方式仅修改 “基类成员在子类中的访问权限”,不改变基类自身的权限;
实际开发中优先使用public继承(避免权限过度收缩,符合代码可读性)。 - 示例:不同继承方式的权限差异
权限对照表

classBase{public:int pub;protected:int pro;private:int pri;};// public继承classPubDerive:publicBase{public:voidtest(){ pub =1;// 合法(public→public) pro =2;// 合法(protected→protected)// pri = 3; // 非法(基类private不可访问)}};// private继承classPriDerive:privateBase{public:voidtest(){ pub =1;// 合法(public→private) pro =2;// 合法(protected→private)}};intmain(){ PubDerive pd; pd.pub =10;// 合法(public继承后仍为public)// pd.pro = 20; // 非法(protected) PriDerive prd;// prd.pub = 10; // 非法(private继承后变为private)return0;}三、继承中的对象模型
- 核心结论
子类对象的内存 = 基类成员(包括private,仅不可直接访问) + 子类自身成员;
编译器会将基类的所有成员(无论权限)都继承到子类内存中,private成员只是被隐藏,并非不存在。 - 示例:验证对象模型(通过sizeof)
#include<iostream>usingnamespace std;classBase{public:int a;protected:int b;private:int c;// 虽不可访问,但占用内存};classDerive:publicBase{public:int d;};intmain(){// Base:int a + int b + int c = 12字节(3*4)// Derive:Base(12) + int d = 16字节 cout <<"Base大小:"<<sizeof(Base)<< endl;// 输出:12 cout <<"Derive大小:"<<sizeof(Derive)<< endl;// 输出:16return0;}- 内存布局(简化)
Derive对象内存:[a (public)] → [b (protected)] → [c (private)] → [d (public)]
四、继承中的构造和析构函数
- 核心规则
构造函数 / 析构函数不可继承,但子类构造时会自动调用基类构造,析构时自动调用基类析构;
调用顺序:
构造:基类构造 → 子类构造(先初始化父类部分,再初始化子类部分);
析构:子类析构 → 基类析构(先清理子类部分,再清理父类部分);
若基类无默认构造(仅带参构造),子类需在初始化列表显式调用基类构造。 - 示例 1:默认构造的调用
#include<iostream>usingnamespace std;classBase{public:Base(){ cout <<"Base构造函数"<< endl;}~Base(){ cout <<"Base析构函数"<< endl;}};classDerive:publicBase{public:Derive(){ cout <<"Derive构造函数"<< endl;}~Derive(){ cout <<"Derive析构函数"<< endl;}};intmain(){ Derive d;// 输出顺序:Base构造 → Derive构造 → Derive析构 → Base析构return0;}- 示例 2:显式调用基类带参构造
#include<iostream>usingnamespace std;classBase{public:int a;// 基类只有带参构造,无默认构造Base(int a_):a(a_){ cout <<"Base带参构造:a="<< a << endl;}};classDerive:publicBase{public:int b;// 子类初始化列表:先调用基类带参构造,再初始化自身成员Derive(int a_,int b_):Base(a_),b(b_){ cout <<"Derive带参构造:b="<< b << endl;}};intmain(){ Derive d(10,20);// 输出:Base带参构造:a=10 → Derive带参构造:b=20return0;}五、继承中的同名成员处理
当子类与基类有同名成员(属性 / 方法)时,默认访问子类成员,需通过作用域解析符:: 访问基类同名成员。
- 同名属性
#include<iostream>usingnamespace std;classBase{public:int num =100;};classDerive:publicBase{public:int num =200;// 同名属性voidshow(){ cout <<"子类num:"<< num << endl;// 访问子类:200 cout <<"基类num:"<< Base::num << endl;// 访问基类:100}};intmain(){ Derive d; d.show();// 外部访问:默认子类,基类需加作用域 cout <<"外部访问子类num:"<< d.num << endl;// 200 cout <<"外部访问基类num:"<< d.Base::num << endl;// 100return0;}- 同名方法
#include<iostream>usingnamespace std;classBase{public:voidfunc(){ cout <<"Base::func()"<< endl;}voidfunc(int a){ cout <<"Base::func(int):"<< a << endl;}// 重载};classDerive:publicBase{public:voidfunc(){ cout <<"Derive::func()"<< endl;}// 重写};intmain(){ Derive d; d.func();// 调用子类:Derive::func()// d.func(10); // 非法!子类重写后,基类重载的func被隐藏 d.Base::func(10);// 显式调用基类重载方法:Base::func(int):10return0;}关键注意
子类重写基类同名方法后,基类的所有重载版本都会被隐藏(需显式加作用域访问)。
六、继承中的同名静态成员处理
静态成员属于类(而非对象),同名处理规则与普通成员一致,但有 2 种访问方式(对象 / 类名)。
示例:同名静态成员
#include<iostream>usingnamespace std;classBase{public:staticint num;staticvoidfunc(){ cout <<"Base::func()"<< endl;}};int Base::num =100;// 静态成员类外初始化classDerive:publicBase{public:staticint num;staticvoidfunc(){ cout <<"Derive::func()"<< endl;}};int Derive::num =200;intmain(){// 方式1:通过对象访问 Derive d; cout <<"子类num(对象):"<< d.num << endl;// 200 cout <<"基类num(对象):"<< d.Base::num << endl;// 100// 方式2:通过类名访问(推荐,静态成员专属) cout <<"子类num(类名):"<< Derive::num << endl;// 200 cout <<"基类num(类名):"<< Derive::Base::num << endl;// 100// 静态方法Derive::func();// 子类:Derive::func() Derive::Base::func();// 基类:Base::func()return0;}七、多继承语法
多继承指子类同时继承多个基类,语法简单但易引发二义性,实际开发需慎用。
- 语法格式
class 子类名 : 继承方式1 基类1, 继承方式2 基类2,...{// 子类成员};- 示例:多继承基础
#include<iostream>usingnamespace std;classBase1{public:int a =10;voidfunc1(){ cout <<"Base1::func1()"<< endl;}};classBase2{public:int a =20;// 与Base1同名voidfunc2(){ cout <<"Base2::func2()"<< endl;}};// 多继承:public继承Base1和Base2classDerive:publicBase1,publicBase2{public:int b =30;};intmain(){ Derive d; d.func1();// 调用Base1:无歧义 d.func2();// 调用Base2:无歧义// cout << d.a << endl; // 非法!二义性(Base1::a 和 Base2::a) cout << d.Base1::a << endl;// 10(指定基类,解决二义性) cout << d.Base2::a << endl;// 20return0;}- 多继承的二义性解决
同名成员:通过基类名::成员名指定访问的基类;
若多个基类继承自同一祖先(菱形继承),需用虚继承解决(见下文)。
八、菱形继承(钻石继承)
- 定义
子类间接继承同一个基类(如A → B、A → C、B → D、C → D),导致D拥有A的两份拷贝,引发二义性和内存冗余。 - 问题示例(未用虚继承)
#include<iostream>usingnamespace std;// 顶层基类classA{public:int num =10;};// 中间基类B:继承AclassB:publicA{};// 中间基类C:继承AclassC:publicA{};// 最终子类D:多继承B和CclassD:publicB,publicC{};intmain(){ D d;// cout << d.num << endl; // 非法!二义性(B::A::num 和 C::A::num) cout << d.B::num << endl;// 10 cout << d.C::num << endl;// 10(两份A的拷贝,冗余)return0;}- 解决:虚继承(virtual)
语法:中间基类继承顶层基类时,加virtual关键字;
效果:让最终子类只保留一份顶层基类的拷贝(共享基类成员),消除二义性和冗余。 - 虚继承示例
#include<iostream>usingnamespace std;// 顶层基类classA{public:int num =10;};// 中间基类:虚继承A(关键)classB:virtualpublicA{};classC:virtualpublicA{};// 最终子类:多继承B和CclassD:publicB,publicC{};intmain(){ D d; cout << d.num << endl;// 10(无歧义,仅一份A的拷贝) d.B::num =20; cout << d.C::num << endl;// 20(共享同一份num)return0;}- 虚继承的底层
虚继承会为中间基类添加虚基类指针(vbptr),指向虚基类表(vbtable),表中存储顶层基类成员的偏移量,确保最终子类只访问一份顶层基类成员。
九、继承的使用注意事项
1、优先使用 public 继承:protected/private 继承会收缩权限,导致代码扩展性差,仅特殊场景使用;
2、避免多继承:多继承易引发二义性,可通过 “组合” 替代(如子类中包含其他类的对象);
3、菱形继承必须用虚继承:否则会有二义性和内存冗余,虚继承是唯一解决方案;
4、构造函数的初始化列表:若基类无默认构造,子类必须显式调用基类带参构造;
5、不要重定义继承的非虚函数:易导致语义混乱,如需重写,应将基类函数声明为virtual(多态);
6、const 正确性:若子类调用基类的 const 方法,需保证自身方法的 const 修饰一致;
7、析构函数建议加 virtual:若通过基类指针删除子类对象,基类析构函数非 virtual 会导致子类析构不执行(内存泄漏);
8、避免继承模板类的实现细节:模板类的继承需关注实例化后的类型匹配,避免隐式转换问题;
9、基类 private 成员的访问:子类不可直接访问,需通过基类的 public/protected 方法间接访问,符合封装原则;
10、静态成员的继承:静态成员被所有子类共享,修改一个子类的静态成员会影响所有相关类。
十、总结
1、继承的核心是代码复用,但需平衡 “复用” 与 “耦合”:
2、单继承简单安全,优先使用;
3、多继承尽量避免,确需使用时注意二义性;
4、菱形继承必须用虚继承;
5、构造 / 析构、同名成员的处理是继承的高频考点,需熟练掌握作用域解析符的使用;
实际开发中,继承需配合多态(虚函数)使用,才能发挥面向对象的最大价值。