多态
就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会 产生出不同的状态。
构成多态还有两个条件:
1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
虚函数
即被virtual修饰的类成员函数称为虚函数。
虚函数的重写(覆盖)
派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的 返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数
虚函数重写的两个例外:
1. 协变(基类与派生类虚函数返回值类型不同) 派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指 针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
2. 析构函数的重写(基类与派生类析构函数的名字不同) 如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字, 都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同, 看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处 理,编译后析构函数的名称统一处理成destructor。
C++11 override 和 final
1. final:修饰虚函数,表示该虚函数不能再被重写
2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
抽象类
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口 类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生 类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
接口继承和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实 现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成 多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
继承和组合 public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。 比特就业课 优先使用对象组合,而不是类继承 。
1.什么是姜形继承?姜形继承的问题是什么?
菱形继承的定义:
萎形继承是面向对象编程中多继承的一种特殊情况,具体指的是两个子类继承同一个父类,而又有另一个子类同
时继承这两个子类。这种继承关系形成了一个菱形的结构,因此被称为菱形继承。
菱形继承的问题:
菱形继承主要存在两个问题:
1.数据冗余:由于子类同时从两个不同的父类继承同一个基类,这可能导致在子类中存在多份基类成员变量的
副本。例如,如果A类有两个子类B和C,B和C都继承了A类的某个成员变量,而D类同时继承了B和C,那么
类中就会包含该成员变量的两份副本,造成数据几余。
2.数据二义性:在菱形继承中,由于子类继承了两个父类,而这两个父类又都继承了同一个基类,当子类尝试
访问基类中的某个成员时,编译器可能无法确定应该访问哪个父类中的那份成员,从而导致编译错误或运行
时错误。
2.什么是菱形虚拟继承?如何解决数据冗余和二义性的?
菱形虚拟继承的定义:
萎形虚拟继承是在萎形继承的基础上,通过在继承方式前加上 virtua! 关键字来解决的。虚拟继承的目的是确保在
菱形继承结构中,基类成员在最终派生类中只存在一份副本,从而解决数据冗余和数据二义性的问题。
解决数据冗余和二义性的方法:
1.使用虚拟继承:通过在菱形继承的分又点(即同时继承多个父类的子类)使用 virtual 关键字来继承共同的基类,可以确保基类成员在最终派生类中只有一份副本。这样,无论最终派生类通过哪条路径继承基类,都只
会访问到这份唯一的副本。
2.虚基表:虚拟继承的实现通常依赖于虚基表。虚基表是一个特殊的表,用于存储基类成员的偏移量信息。当
最终派生类需要访问基类成员时,它可以通过虚基表找到基类成员在内存中的实际位置,从而避免数据冗余
和数据二义性。
3.继承和组合的区别?什么时候用继承?什么时候用组合?
继承和组合的区别:
1.本质:继承体现的是一种”is-a”的关系,即子类是父类的一种;而组合体现的是一种"has-a”的关系,即一个类包含另一个类的对象。
2.耦合度:继承是一种紧耦合的关系,子类与父类之间紧密相连;而组合是一种松耦合的关系,组合类与被包含类之间相对独立。
3.复用方式:继承是接口的复用,子类可以继承父类的接口并实现自己的逻辑;组合是实现的复用,组合类可以使用被包含类提供的接口,但不需要继承其实现。
使用时机:使用继承的时机:
·当子类确实是父类的一种特殊形式时,可以使用继承。例如,学生和教师都是人的特殊形式,因此可以使用继承来表示这种关系。
当子类需要重用父类的接口并实现自己的逻辑时,也可以使用继承。例如,子类可以继承父类的某些方法,并在自己的实现中进行扩展或修改。
使用组合的时机:
当类之间存在"has-a"的关系时,应该使用组合。例如,一个汽车类包含一个发动机类的对象,此时应该使用组合来表示这种关系。
当需要保持类的独立性和封装性时,应该优先考虑使用组合。因为组合关系相对独立,不会破坏类的封装性,也不会导致子类与父类之间的紧密耦合。
综上所述,继承和组合各有其适用场景。在实际编程中,应根据具体的需求和类的关系来选择合适的复用方式。