一、什么是继承
1、当遇到问题时,先查看现有的类是否能解决一部分问题,如果有则继承该类,并在此基础上扩展以此解决问题,从而缩短解决问题的时间(代码复用)
2、当遇到一个大而复杂的问题时,可以把大问题拆分成若干个不同的小问题,然后为每个小问题设计一个类来解决,最后通过继承的方式把这些类汇总到一个类中,从而解决大问题,以此降低问题的难度,可以同时让多个程序员共同解决大问题
派生类继承基类 子类继承父类
二、继承语法
1、继承表
class Son : 继承表[继承方式 父类名1,继承方式 父类名2,···]
{
成员变量;
public:
成员函数;
};
2、继承方式
public
private
protected
三、继承的特点
1、C++中的继承可以有多个父类
2、子类会继承父类的所有内容,是否能用另说
3、子类对象可以向它的父类类型转换(缩小),但是父类对象不能向子类类型转换(放大)
父类指针或引用可以指向子类对象,但是子类指针或引用不能指向父类对象
前提:必须以public继承父类
Base* b = new Son; true
Base* b = son; true
Son* s = new Base; false
Son* s = base; false
4、子类会隐藏父类的同名成员(成员变量、成员函数),不构成函数重载,因为作用域不同
同名成员被隐藏后,在子类中直接访问到的是子类的同名成员
但是可以通过 父类名::同名成员名 的方式来指定访问父类同名成员
5、在执行子类的构造函数的初始化列表时,会按照继承表的顺序来执行父类的构造函数,默认执行的是父类的无参构造,但也可以在子类的初始化列表中显式地调用父类的有参构造,然后再执行类类型成员的构造函数,最后执行子类的构造函数
Son(int num):Base(val){} //调用Base的有参构造
Son(int num){} //调用Base的无参构造
6、在子类的析构函数执行后,再调用类类型成员的析构函数,最后按照继承表逆序调用父类的析构函数
7、当子类执行拷贝构造时,默认下只会调用父类的无参构造,这是有问题的,因此需要在子类的拷贝构造函数的初始化列表中显式地调用父类的拷贝构造函数
Son(const Son& that):Base(that){} //调用Base的拷贝构造 //父类引用可以指向子类对象
8、当子类指向赋值函数时,默认下不会调用父类的赋值函数,如果需要调用父类的赋值函数可以在子类的赋值操作函数中通过域限定符显式地调用父类的赋值操作函数
Son& operator=(const Son& that)
{
A::operator=(that); //调用Base的赋值操作函数
cout << "son的赋值操作" << endl;
}
四、继承方式与访问控制属性
访问控制属性:对成员的访问范围限制
public 可以在任意位置访问
protected 只能在类内或者子类中访问
private 只能类内访问
继承方式的影响:
1、父类的成员是否能在直接子类中访问取决于父类的访问控制属性,而不受继承方式影响
2、子类的继承方式能决定父类成员被子类继承后,在子类中变成什么样的访问控制属性
(*背*)关系表
3、只有以public方式继承父类,父类的指针或引用才能指向子类对象,这也是多态的基础
五、多重继承和钻石(菱形)继承
1、什么是多重继承:
当一个子类继承多个父类时称为多重继承,会按照继承表的顺序在子类中排列父类的内容,当使用父类指针指向子类对象,编译器会自动计算出该父类的内容在子类中的位置,并让父类指针指向该位置,所以可能会出现使用同一个子类指针给不同的父类指针赋值后,地址编号不同的情况
2、钻石(菱形)继承
假设有个类A,类B、C都分别继承类A,又有一个类D继承了类B和类C,一个类的父类有共同祖先类时,就形成了菱形继承
问题:
1、类B、类C中都各自都有类A的内容
2、类D会继承类B和类C的所有内容,就导致类D中继承了两份类A
3、当类D对象去访问类A的内容时就会有歧义,编译器不能确定访问的是哪份类A的内容,会报错
3、虚继承(常考)
使用 virtual 关键字去修饰继承表时,此时变成虚继承
此时子类中会多出一个成员变量(虚指针),用于指向父类的内容,并且当该子类被继承时该虚指针会一起被继承,如果此时形成钻石继承时,孙子类中就会有多个指向相同位置的虚指针,此时编译器会比较这些虚指针指向的内容是否一致,如果是则只继承一份
总结:通过虚继承可以解决钻石继承中的访问共同祖先类内容有歧义的问题