一、多态概念。
多态顾名思义是多种形式,它分为。多态(编译时;静态多态)xff0;和。多态运行(动态多态),编译时多态(静态多态)xff0;就是。函数重载模板。等,不同函数的调用(通过不同的参数完成;生成多种形式)而这个过程。已完成编译阶段。。
动态多态是在。运行时。根据对象的实际类型确定哪个版本的调用函数,完成不同的⾏为。
二、多态构成条件。
1.虚函数。
添加类成员函数的返回类型。virtual关键词。即虚函数,注意:虚拟函数只能定义为普通成员函数,虚函数不能定义为构造函数和类外函数。
2.重写虚拟函数。
虚拟函数重写是指子类(#xfff09;对父亲基类)的重写。重写的要求是子类虚拟函数的返回值,函数名,参数类型必须与父类完全相同。但是函数的实现逻辑不一样。
如果虚拟函数的重写不添加virtual,但是,如果父类添加virtual,子类仍然保持virtual的性质,也可构成重写。
。注意:虚拟函数重写不需要相同的缺失参数,但这里强烈建议将缺失参数设置为相同值,否则会给你带来很大的弊端和误导性。接下来,我将讨论它。
3.调用方法。
实现多态效果,第⼀必须是基类。指针或引⽤。,因为只有基类指针或引用⽤才能。既指向派⽣类对象也可以指向基类。;第⼆派⽣类必须重写/覆盖基类的虚拟函数,重写或覆盖,派⽣类可以有不同的函数,只有达到多态不同形态的效果。
⽐例如,火车购票操作,当普通⼈买票c;全价买票;学⽣买票c;优惠买票;军⼈买票时优先买票,我们可以使用多态来实现,以下:
#includeusing namespace std;class ticket{ public: virtual void func() { cout << "普通票" << endl; }private:};class student:public ticket{ public: virtual void func() { cout << "学生票" << endl; }private:};void fm(ticket& pu){ pu.func();}int main(){ ticket tk; student stu; fm(tk); fm(stu); return 0;}。
4.装饰override和final。
override关键字:因为实现多态的细节要求太多,尤其是虚拟函数的重写,因此C++11提供override,可以帮助⽤⼾检查虚拟函数的重写是否正确,需要放在重写函数参数列表后面。
final关键字:如果子类不想重写虚拟函数,可以使用final关键字,放在函数名后面。


final关键字:如果子类不想重写虚拟函数,可以使用final关键字,放在函数名后面。
5.协变 。
刚才我们说虚拟函数的重写必须满足子类虚拟函数的返回值,函数名,参数类型必须与父类相同。协变是个例外。当子类重写父类虚拟函数时,#xff0c;如果父类虚函数的返回值类型不同,即父类虚拟函数返回父类对象的指针或引用,子类虚函数返回子类对象的指针或引用,此时称为协变。协变的现实意义并非如此⼤,所以我们知道⼀下即可。
#xff1代码示例a;
class A{ };class B :public A{ };class ticket{ public: virtual ticket* func()//ticket也可以是A { cout << "普通票" << endl; return this; }private:};class student:public ticket{ public: virtual student* func() override//student也可以是B { cout << "学生票" << endl; return this; }private:};
重写分析函数。 父类的分析函数是虚拟函数,此时派⽣只要定义了类析构函数,⽆是否添加virtual关键字,都与基类分析函数构成重写,尽管基类和派⽣不同的类析构函数名称似乎不符合重写规则,事实上,编译器对分析函数的名称进行了特殊处理,编译后析构函数的名称统一⼀处理成destructor。,因此,vialtual修饰添加了基类的分析函数c;派⽣类的析构函数构成重写。
,
因此,vialtual修饰添加了基类的分析函数c;派⽣分析函数构成重写。
。
A* p1 = new A;A* p2 = new B;delete p1;delete p2; 假设B是A的子类⾯如果代码~A(),不加virtual,所以delete p2时只调⽤A的分析函数,没有调⽤B分析函数,会导致内存泄漏。三、纯虚函数和抽象类。三、纯虚函数和抽象类。 在虚拟函数的参数列表后⾯写上=0,这个函数是纯虚函数,纯虚函数不需要定义和实现(实现没有意义,因为被子类重写但是语法上可以实现,只需声明即可。包含纯虚函数的类称为抽象类,
抽象类不能实例化对象。
,
子类继承后不重写纯虚函数,所以子类也是抽象的。。在某种程度上,纯虚函数强制子类重写虚函数,因为不重写实例化的对象。四、多态原理。 在分析对象的存储空间时,我们谈到了同一类实例化的不同对象,这些对象使用相同的函数,不同之处在于它们的成员变量。因此,每个对象只需要存储成员变量,不需要存储成员函数#xff0c;每个对象都使用这类公共成员函数。 。
c++为虚拟函数设置一个单独的区域来存储虚拟函数的地址,叫做虚表。
,事实上,这个区域 是函数指针数组。也就是一个用来存储函数指针的数组。那么父子之间就有了自己的虚表,当对象实例化时,它将隐含(隐含:类似于成员函数中看不见的this指针)指针-。
虚表指针。
,指向虚表。
#includeusing namespace std;class A{ public: virtual void func(){ }};class B{ public: void func(){ }};int main(){ A a; B b; cout << "a:" << sizeof(a) << endl; cout << "b:" << sizeof(b) << endl; return 0;}。
虚表指针也需要占用空间。您可以自行操作上述代码,输出结果为:
a:4(或8也就是说,32位和64位机器的区别)。
虚表指针也需要占用空间。您可以自行操作上述代码,输出结果为:a:4(或8)c;也就是说,32位和64位机器的区别)。
b:1。
因此,在调用对象的虚拟函数时,与以何种形式调用无关,但与实例化对象的具体类型有关。
- #includeusing namespace std;class ticket{ public: virtual void func() { cout << "普通票" << endl; }};class student :public ticket{ public: virtual void func() { cout << "学生票" << endl; }};int main(){ ticket* tk = new student; tk->func(); return 0;}。
- 以上输出结果为“学生票”。
- 以上输出结果为“学生票”。
- 注意:
- 根据切片原理子类可以强制类型转化为父类,父亲不能强迫类型转化为子类。
- 五、练习。

以下程序输出结果是什么()
A:A->0。
B:B->1。
C:A->1。D:B->0。E:编译出错。F:以上都不正确。 虽然B类func成员没有写virtual关键词,但它是由A继承的,仍然保留着virtual的性质,然后,因为重写和所需参数的缺失值相同,因此,这里构成函数的重写。再来看看主函数main,test调用test&c;test是a的成员函数,它隐含了一个constt A* (this指针)参数类型,test函数满足多态,所以这里调用的是B的func。但是这里有一个坑这个问题的输出结果不是“B->0",而是“B->1”。 注意重写只是重写函数的实现,也就是说,实现多态相当于调用父类接口声明和子类函数实现,并且不关心子类的函数接口声明。 所以当我们自己写虚拟函数时,最好将缺失参数设置为相同的值否则会给你带来很大的误导。