前言
今天我们来简单的介绍一下c++的继承,继承也是面向对象语言的三大特性之一。继承是实现代码复用的重要手段,它允许我们在保持原有类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称⼦类。继承呈现了⾯向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
引入
在没有学习过继承的情况下,如果我们想描述老师和学生这两个对象,可能会有以下实现
class Student
{ public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){// ...} // 学习void study(){// ...}protected:string _name = "peter"; // 姓名string _address; // 地址string _tel; // 电话int _age = 18; // 年龄int _stuid; // 学号
};
class Teacher
{ public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){// ...} // 授课void teaching(){//...}protected:string _name = "peter"; // 姓名string _address; // 地址string _tel; // 电话int _age = 18; // 年龄string _title; // 职称
};
不难发现,这两个类有大量的重复,如果我们可以把重复的部分抽象出来描述为一个类,学生和老师继承这个类的同时添加个性化的部分就可以实现代码的复用,我们把这个过程称为继承
class Person
{ public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){cout << "void identity()" <<_name<< endl;}protected:string _name = "张三"; // 姓名string _address; // 地址string _tel; // 电话int _age = 18; // 年龄
};class Student : public Person
{ public:// 学习void study(){// ...}protected:int _stuid; // 学号
};class Teacher : public Person
{ public:// 授课void teaching(){//...}protected:string title; // 职称
};
继承
定义
继承允许新类基于现有的类来创建,从而共享代码
语法
class 基类 {
public:// 基类的成员
};class 派生类 : [访问修饰符] 基类 {
public:// 派生类的成员
};
其中:
派生类
是从基类
继承而来的。[访问修饰符]
决定了基类成员在派生类中的可见性。常见的访问修饰符包括public
、protected
和private
。
继承方式
这里的可见性具体还是比较复杂的。我们简单的总结一下:
因为private的特性,无论何种继承方式子类都无法访问
protetced和public子类都可以访问,区别在于向外的表现,即类外的访问权限,共性表现为取继承方式和访问修饰限定符的小范围
不提倡使⽤protetced/private继承
关键字class时默认的继承⽅式是private,使⽤struct时默认的继承⽅式是public,不过最好显
⽰的写出继承⽅式
父类是类模板时需要注意按需实例化的问题
注意⽗类是类模板时,需要指定⼀下类域,这是因为模板遵从按需实例化,需要用才会被实例化,所有为了避免使用时未被实例化的情况,在使用父类的方法时我们建议指定类域
⽗类和⼦类对象赋值兼容转换
public继承的⼦类对象可以赋值给⽗类的对象/⽗类的指针/⽗类的引⽤。这⾥有个形象的说法叫切⽚或者切割。寓意把⼦类中⽗类那部分切来赋值过去。
注意方向只能是由子类向父类,简单理解就是切片肯定是切小
当然了,虽然我们不允许父类向子类转换,但当父类指针或者引用指向子类时的强制类型转换我们认为是安全的
隐藏
父类和子类都是独立的作用域,但当⼦类和⽗类中有同名成员,⼦类成员将屏蔽⽗类对同名成员的直接访问,这种情况叫隐藏,函数隐藏同理。隐藏的条件是名称相同,注意函数隐藏和函数重写做区分,重写的要求是函数名,返回值,参数三同,这部分的知识我们会在多态的部分再展开,这里先有个印象。注意,当构成隐藏时,子类函数可以通过⽗类::⽗类成员显示调用
默认成员函数
构造函数
在子类的构造函数中必须调用父类的构造函数完成对父类部分的初始化,如果没有调用系统会自动调用父类的默认构造函数,如果父类没有默认构造则需要在子类的初始化参数列表中完成调用
⼦类的拷⻉构造函数必须调⽤⽗类的拷⻉构造完成⽗类的拷⻉初始化
⼦类的operator=必须要调⽤⽗类的operator=完成⽗类的复制。需要注意的是⼦类的operator=隐
藏了⽗类的operator=,所以显⽰调⽤⽗类的operator=,需要指定⽗类作⽤域
⼦类的析构函数会在被调⽤完成后⾃动调⽤⽗类的析构函数清理⽗类成员。因为这样才能保证⼦类
对象先清理⼦类成员再清理⽗类成员的顺序
构造先父后子,析构先子后父
可以简单的把父类当成一个自定义对象处理。
实行一个不能被继承的类
构造函数私有化或者使用c++11引入的final修饰
友元关系无法被继承
如果一个函数和父类是友元,但继承它的子类和这个函数不是友元,友元类也一样
继承与静态成员
无论如何继承,父类中的静态成员只有一份,这是因为静态成员存放在全局/静态存储区(也称为数据段),这个区域的内存是在程序启动时分配的,并且在程序结束时释放。静态成员在程序的生命周期内只有一个副本,所有对象共享这一份。
多继承
c++是支持多继承的,在存储时先继承的⽗类在前⾯,后⾯继承的⽗类在后⾯,⼦类成员在放到最后⾯
此时会造成菱形继承
菱形继承有数据冗余和⼆义性的问题
反正我们在设计层面上强烈建议不适用,因为它会带来很大的问题
虚继承
虚继承是c++尝试解决菱形继承带来的数据冗余和二义性的解决方案
在菱形继承结构中,可以通过将 Base
类声明为虚继承,保证 Base
类在 D
中只有一个副本,而不会被重复继承。
#include <iostream>class Base {
public:int value;Base() : value(0) {}
};class A : public virtual Base {}; // 虚继承
class B : public virtual Base {}; // 虚继承class D : public A, public B {};int main() {D obj;obj.value = 10; // 现在只有一个 Base 实例std::cout << "Value: " << obj.value << std::endl; // 输出 10return 0;
}
继承和组合
继承和组合是两种设计模式,继承可以理解为is-a,组合可以理解为has-a
各有优劣和适用的场景,这里就不对比了
优先使⽤组合,⽽不是继承
结语
以上便是今天的全部内容。如果有帮助到你,请给我一个免费的赞。
因为这对我很重要。
编程世界的小比特,希望与大家一起无限进步。
感谢阅读!