继承方式与访问属性
访问控制属性决定了成员的访问范围:
public 可以在任何位置访问
private 只能在类内访问
protected 只能在类内和子类中访问
继承方式的影响:
1、父类的成员能否在子类中访问,是设计父类时访问控制属性决定的;继承方式能决定父类成员被子类继承后,在子类中变成什么样的访问控制属性,见下表:
父类中属性 public继承 provite继承 private继承
public public protected private
protected protected protected private
private private private private
2、只有以public方式继承父类,父类的指针或引用才可以指向子类对象(该语法是多态的基础)
多重继承和钻石继承
1、多重继承
当一个类继承多个父类时,会按照继承表中的顺序在子类中排列父类,子类会标记每个父类的位置,当把子类指针转换成父类指针时,编译器会自动计算父类所在的位置,指针会自动偏移
2、钻石继承
假如有一个类A,类B和类C都继承了类A,类D又继承了类B和类C,当子类的父类有共同的祖先,这种继承被称为钻石继承
1、类B和类C中都有类A的内容
2、类D会继承类B和类C中的所有内容,也就是说会继承两份类A中的内容
3、当类D对象访问类A中的成员时,此时就会冲突,因为类A中的成员在类D中有多份,编译器无法区别就会出现编译错误
3、虚继承
当使用 virtual 修饰继承时子类中会多出一个虚指针用于指向父类中的内容,当这个子类被继承时,孙子类会通过父类中的虚指针比较是否有多份祖先类,如果有多份则只保留一份
这样的钻石继承在访问祖先成员时就不会冲突
虚函数、覆盖
1、当成员函数前加 virtual 关键字后,这样的函数就被称为虚函数,该类中就会像虚继承一样多了一个虚指针
2、虚函数表
虚指针中记录的是一个表格的首地址,而该表格中记录了类中所有虚函数的地址
((void(*)(void))**(int**)p(); 调用的就是虚函数表中的第一个虚函数
3、覆盖(重写)
如果子类中有与父类虚函数同名的成员函数,编译器会比较这两个同名函数的格式;如果格式相同就把子类中同名的地址覆盖虚函数表中的记录(那么子类中的函数就会覆盖父类中的函数,这种情况就叫做函数覆盖或重写);如果不同则构成隐藏(子类隐藏·父类同名成员)
4、构成函数覆盖的条件
1、在父子类之间
2、父类中的函数为虚函数
3、函数名、参数列表、常属性相同
4、返回值类型相同,或者子类函数的返回值可以向父类函数的返回值做隐式转换(有继承关系)
多态
什么是多态:
指的是指令的多种形态;当调用一个指令时,它能够根据参数、环境的不同做出相应的操作,这种情况就叫做多态
根据确定操作的时间,多态分为:编译时多态,运行时多态
编译时多态:
当调用重载函数,编译器根据参数的类型,在编译时就能确定执行哪个版本的重载函数;模板技术等
运行时多态:
当子类覆盖了父类中的同名函数,然后父类指针或引用访问虚函数时,它即可能调用父类中函数也可能调用子类中函数,具体调用哪个版本时根据指针和引用的目标决定的,而这需要在运行期间才能确定,因此这种叫做运行时多态
构成运行时多态的条件:
1、父子类之间有覆盖关系
2、子类是以 public 方式继承的
3、通过父类指针或引用访问虚函数
构造函数和析构函数是否可以是虚函数,为什么?
虚构造和虚析构
虚构造:
构造函数不能是虚函数
假如构造函数定义为虚函数,子类的构造函数就会自动覆盖父类的构造;当创建子类对象时,子类构造执行前会先调用父类构造,而父类对象已经被覆盖,此时会调用覆盖版本(子类构造),就会形成死循环,因此编译器把禁止构造函数定义为虚函数
虚析构:
析构函数可以定义为虚函数
当使用类多态时,通过父类指针或引用释放子类对象时,默认情况不会调用子类析构
只有把父类的析构函数定义为虚函数(子类析构会自动覆盖它),当通过父类指针或引用释放子类对象时,会先调用子类的析构(多态),子类析构执行完成后会自动调用父类析构(继承的规则)
注意:当使用多态时且子类的析构函数中有需要释放的资源时,父类的析构函数一定要设置为虚析构
纯虚函数和纯抽象类
纯虚函数的格式:
virtual 返回值 函数名(参数列表) = 0;
1、纯虚函数可以不实现(一般人都不会去实现)
2、有纯虚函数的类不能创建对象
3、父类中如果有纯虚函数子类必须覆盖,否则也无法创建对象
4、纯虚函数是强制子类实现某些功能的方法
5、有纯虚函数的类叫抽象类
6、析构函数不能定义为纯虚函数
纯抽象类:
所有的成员函数都是纯虚函数,这种类叫纯抽象类,这种类一般用于设置功能接口,也叫做接口类型