C++多态分类
1.静态多态性
C++中的多态性包括静态多态性和动态多态性两类。静态多态性通常称为编译时多态性,通过函数重载来实现。动态多态性通常称为运行时多态,通常用虚函数来实现。函数的重载包括普通函数的重载和类的成员函数的重载两种。运算符的重载可以归类为函数的重载。用虚函数来实现动态多态性只有在类的继承中才有意义。
多态性与虚函数静态多态性(也叫编译时多态性),在C++中是通过函数重载来实现的,运算符重载可以认为是特殊的函数重载。
所谓的函数重载,是指函数名相同,但是函数参数的类型、个数、顺序有所不同,当调用函数时,编译器会根据所给的参数的类型、个数、顺序的不同情况来决定具体调用的函数。对函数的重载不允许二义性,有些函数重载表面上看起来没有问题,实际上会引起二义性,也是不被允许的。
Type1func1(int*a);
Type1func1(int a[]);
看起来参数的类型不一样,实际上是重复的两个函数,不允许被重载。
Type1func2(int a);
Type1func2(int&a);
看起来参数不一样,但在调用的时候同样会出现二义性,所以不允许重载。
Type1func3(int&a);
Type1func3(const int&a);
调用会引起二义性。
函数的重载和函数的带默认参数值也会引起二义性,如:
Type1func4(int x,int y=0,int z=0);
Type1func4(int a,int b);
Type1func4(int k);
三个函数的参数的个数不同,但是在调用的时候会引起二义性,所以原则上是不被允许重载的。
若有多个重载函数同时被定义,则在调用时不出现二义性是允许的,但在原则上会出现二义性的重载函数其实都不是好的处理方法,尽可能避免这样的函数
重载。很多情况下函数重载的定义是允许的,知识调用的时候才发现出现了二义性,如上面的func2()其实是允许的,只要调用的时候不出现二义性不出现二义性是可以的,比如下面的常量定义及相应的调用是可以的:
const Type2a=const Exp1;
func2(a);
但是如果定义了下面的变量和相应的调用,则出现了二义性
Type2b=Exp1;
func2(a);
因为这个调用两个函数都解释的通,既然如此,就无法确定调用的是哪一个,因此,就不被允许。运算符的重载属于函数重载的特殊情形,因为运算符解释为运算符函数,例如:A+B通常解释成operator+(A,B),如果是类的成员函数则解释成A.operator+(B).运算符函数可以定义成类的成员函数,也可以定义
成类的友元函数,同时也可以定义成普通函数,这个普通函数既不是类的成员函数,也不是类的友元函数。此时类的成员函数的要求比较多,效率不够高。
2动态多态性
动态多态性(也叫运行时多态性),通过虚函数来实现。这种多态只有出现类的继承和派生时才能起作用,如果没有类的继承和派生,虚函数是没有任何意义的。动态多态性有两个条件,一是在基类中必须使用虚函数或者纯虚函数,二是调用函数时要使用基类的指针或者引用。
在类的成员函数之前加上virtual关键字就可以使该成员函数变成虚函数。从基类派生出的类的同名成员函数(虚函数),不管前面是否含有virtual,都是虚函数。虚函数的实现时,前面不能加virtual 关键字。
普通函数的处理:一个特定的函数会被映射到特定的代码,无论是编译阶段还是连接阶段,编译器都能计算出这个函数的地址,调用即可。
虚函数的处理:
被调用的函数不仅依据调用的特定函数,还依据调用的对象的种类。通常是由虚函数表(vtable)来实现的。
虚函数表的结构:
它是一个函数指针表,每一个表项都指向一个函数。任何一个包含虚函数的类都会有这样一张表。需要注意的是vtable只包含虚函数的指针,没有函数体。实现上是一个函数指针的数组。
虚函数表既有继承性又有多态性
每个派生类的vtable继承了它各个基类的vtable,如果基类vtable中包含某一项,则其派生类的vtable中也将包含同样的一项,但是两项的值可能不同。如果派生类重载(override)了该项对应的虚函数,则派生类vtable的该项指向重载后的虚函数,没有重载的话,则沿用基类的值。
每一个类只有唯一的一个vtable,不是每个对象都有一个vtable,每个对象都有一个指针,这个指针指向该类的vtable(当然,前提是这个类包含虚函数)。
那么,每个对象只额外增加了一个指针的大小,一般说来是4字节。
在类对象的内存布局中,首先是该类的vtable指针,然后才是对象数据。
在通过对象指针调用一个虚函数时,编译器生成的代码将先获取对象类的vtable指针,然后调用vtable中对应的项。
对于通过对象指针调用的情况,在编译期间无法确定指针指向的是基类对象还是派生类对象,或者是哪个派生类的对象
但是在运行期间执行到调用语句时,这一点已经确定,编译后的调用代码能够根据具体对象获取正确的vtable,调用正确的虚函数,从而实现多态性。下面是通过基类的指针来调用虚函数时,所发生的一切:
step1:开始执行调用pA->run();(pA为基类指针,这里能判断到底是哪个对象)step2:取得对象的vtable的指针
step3:从vtable那里获得函数入口的偏移量,即得到要调用的函数的指针step4:根据vtable的地址到函数,并调用函数。
step1和step4对于一般函数是一样的,虚函数只是多了step2和step3。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。