c++之多态

来源:互联网 发布:微商城 知乎 编辑:程序博客网 时间:2024/06/03 15:21

多态:顾名思义就是具有多种形态。是C++的三大特性之一。
这里写图片描述
静态多态:编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
如下面代码为静态多态,根据实参类型调用函数。

#include <iostream>using namespace std;int add(int left, int right){    return left + right;}double add(double left, double right){    return left + right;}int main(){    cout << add(1, 2) << endl;    cout << add(1.0, 2.0) << endl;    return 0;}

这里写图片描述
动态多态:亦称动态绑定,是在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现该虚函数。编译器实现动态绑定。
如下面代码为动态多态,根据其实际类型调用函数。

#include <iostream>using namespace std;class B{public:    virtual void FunTest()    {        cout << "B::FunTest()" << endl;    }};class D :public B{public:    virtual void FunTest()    {        cout << "D::FunTest()" << endl;    }};void FunTest(B& b){    b.FunTest();}int main(){    B b;    D d;    FunTest(b);    FunTest(d);    return 0;}

这里写图片描述
【动态绑定条件】
1、必须是虚函数
2、通过基类类型的引用或者指针调用虚函数
静态多态是通过函数重载和运算符重载实现的。动态多态是通过继承和虚函数重写实现的。下面我们来看下继承体系同名函数关系。
这里写图片描述
上图说了协变除外,那什么是协变呢?
协变是指在派生类中重新实现虚函数时,返回值类型可以和基类不同,但基类和派生类中的返回值类型必须为该类型指针,仅此一项不同其余均与函数重写条件一致构成协变。

#include <iostream>using namespace std;class CBase{public:    virtual CBase* GetPtr()    {        cout << "CBase::GetPtr()" << endl;        return this;    }};class CDerived :public CBase{public:    virtual CDerived* GetPtr()    {        cout << "CDerived::GetPtr()" << endl;        return this;    }};void FunTest(CBase* p){    p->GetPtr();}int main(){    CBase b;    CDerived d;     FunTest(&b);    FunTest(&d);    return 0;}

这里写图片描述
纯虚函数
在成员函数的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化对象。纯虚函数在派生类中重新定义以后,派生类才能实例化对象。

class B{public:    virtual void FunTest() = 0; // 纯虚函数};// D也是一个抽象类class D :public B {public:    virtual void FunTest()    {}};

总结:
1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)。
2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
3、只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。友元函数不属于类的成员函数,不能被定义为虚函数。
4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。
5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆。
6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为。
7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)。
8、虚表是所有类对象实例共用的。

#include <iostream>using namespace std;class CTest{public:    CTest()    { iTest = 10;    cout << "this=" << this << endl;    }    virtual~CTest(){};private:    int iTest;};int main(){    CTest test;    cout << sizeof(CTest) << endl;    return 0;}

这里写图片描述
这里写图片描述
这里写图片描述
对于有虚函数的类,编译器都会维护一张虚表,在里面存放各个虚函数的地址,最后以0结尾,而对象的前四个字节就是指向虚表的指针。如上图中虚表指针地址为0x001bfbf8,虚函数存放于虚表指针所指向地址0x00b5dc74中。

虚函数在多态中的调用次序:
单继承:
(1).首先调用基类中的虚函数,按在基类中声明的顺序调用。
(2).派生类先调用基类中继承的虚函数,基类中继承的虚函数调用完后,按照在派生类中声明的顺序调用派生类自己的虚函数,放在基类中继承的虚函数的后面。如果派生类重写了基类中的某个虚函数,就会替换相同偏移量位置的基类虚函数。
多继承:
(1)基类中虚函数参照单继承中基类虚函数声明的顺序调用。
(2)派生类先调用基类中继承的虚函数,基类中继承的虚函数调用完后,自己的虚函数也参照单继承中派生类虚函数声明的顺序调用。派生类中重写的虚函数替换相同偏移量位置的基类虚函数。
(3)派生类自己的虚函数放在第一个继承基类的虚表后面调用。

补充说明:
1虚表在编译时产生。
2.相同类型对象共用一张虚表。
3.若调用声明为虚函数的析构函数时,总是先调用派生类的析构函数,再调用基类的析构函数。

0 0
原创粉丝点击