类的继承
什么是继承
1、当遇到问题时,先考虑现在的类是否能解决一部分问题,如果可以则继承现有的类,然后再此基础上进行扩展,达到解决问题的目的,同时缩短解决问题的时间(代码复用)。
2、当遇到一些复杂问题时,可以先把问题进行分解成若干个小问题,然后为每个小8问题设计一个解决问题的类,最终通过继承把这些类汇总到一个类中,达到解决问题的目的,以此降低解决问题的难度,同时也可以让多个程序员协作解决问题。
继承的语法
1、继承表
class Test : <继承方式 父类>,... <>是不写出来的
{
成员变量;
public:
成员函数;
};
2、继承方式
父类不同的权限的成员以不同的方式被子类继承后会变成什么权限:
父类 public继承到子类 protected继承到子类 private继承到子类
public public protected private
protected protected protected private
private private private private
注意:成员在父类的权限决定它能否在子类中访问权限,继承方式影响的是父类成员继承到子类后会变成会样的访问权限。
继承的特点
1、C++中的继承语法可以有多个父类。
2、子类会继承父类中所有内容,但能否在子类访问取决于父类中的成员是什么权限。
3、子类对象可以父类对象作隐式转换(缩小),但父对象不能向子类对象隐式转换(放大)。
4、子类对象会隐藏父类中的同名成员,所以子类对象无法直接访问,父类中的同名成员。
可以使用域限定符指定访问父类中的同名成员。
5、父子类之间同名不会构成重载,因为作用域不同。
6、当创建子类对象时,会根据继承表的顺序隐式调用父类的无参构造函数,也可以在子类构造函数的初始化列表中显式调用。
7、当子类对象销毁时会自动调用父类析构函数。
多重继承、钻石继承、虚继承
1、多重继承
C++中的类可以继承多个父类,子类对象中会按照继承表的顺序排列父类对象,当把子类对象指针赋值给父类指针时,编译器会自动计算父类在子类中的偏移位置转换后赋值给父类指针。
2、什么是钻石继承
当子类有多个父类,而这些父类有共同的祖先类,这种继承关系叫钻石继承。
按照默认的继承方式,祖先类中的内存会在子类存在多份。
1、会造成资源浪费
2、当子类对象访问祖先类中的成员时会有冲突。
3、虚继承
当继承时使用 virtual 修饰继承时,子类中就会多个指针用于指向从父类中继承到的成员,当进行多重继承时,编译器会比较每个父类的虚表指针所指向的资源,如果它们的来源相同,则只保留一份。继承多个父类时,这些父类继承租父类时加入virtual,共同属性继承最后的那个父类的。
这种继承方式可以解决钻石继承造成资源浪费,访问冲突的问题。
虚函数、覆盖
1、成员函数的前面加上 virtual 类中就会多个指针(虚函数表指针),这种函数被称为虚函数。
2、该指针存储在对象的前4个字节,指向一个数组,数组中存储着所有的虚函数地址。
int* p = (int*) new A;
int* ptr = (int*)*p;
((void(*)(void))ptr[0])();
((void(*)(void))ptr[1])();
((void(*)(void))ptr[2])();
3、覆盖(重写)
当继承父类时,如果子类有与父类的虚函数同名的函数,编译器会比较函数的格式,如果满足条件编译器会把子类的函数地址替换掉虚函数表中的函数地址,这种现象叫函数覆盖。
4、构成函数覆盖的条件:
1、在父子类之间
2、父类中的函数为虚函数
3、函数的签名相同
函数名相同
参数相同
常属性相同
返回值相同或子类函数返回值类型可以向父类函数的返回值类型作隐式转换且有继承关系。
什么是:重载、隐藏、覆盖、重写、重定义。
重载:同名,不同参数,同作用域
隐藏/重定义:子隐藏父的同名(不需要父类的函数是虚函数),子想要调用父的同名函数得用域限定符
覆盖/重写:父的虚函数与子类某函数同名,若满足条件,用子类函数地址替换虚函数地址
多态
什么是多态:
指令的多种形态,当调用一个指令时它可以根据参数、环境的不同作出相应的操作,这种情况叫多态。
编译时多态:
编译时指令决定执行什么操作,这种多态叫编译时多态,如:调用重载过的函数。
运行时多态:
程序在运行时指令才决定执行什么操作,这种多态叫运行时多态,如:通过父类的指针或引用调用覆盖过了函数。
运行时多态的原理?
1、子类会继承父类的所有内容。
2、子类会隐藏父类的同名成员。
3、子类的指针或引用可以隐式转换成父类的指针或引用,并且编译器会自动计算偏移值。
4、当通过父类的指针或引用访问同名成员时,默认情况应该访问的是父类中的成员。
5、当父类中的函数是虚函数时,父类中会有一个虚指针,它指向了一个虚函数表,该表中存储了父类中的所有虚函数地址。
6、子类会继承虚指针,如果子类中的函数与父类的函数签名相同,则编译器会去替换虚函数表的函数指针,这种情况叫函数覆盖。
7、当通过父类的指针或引用访问覆盖过的函数时,如果指针或引用的目标是父类对象则会调用父类的成员函数,如果目标是子类对象则会调覆盖后的子类成员函数。