多态是c++中很重要的一环。多态能够分为如下几个层面来剖析:c++
1.对象的类型编程
2.多态ide
3.虚表函数
先说第一点对象的类型,这个很是简单。好比说、this
int a;
那么我就定义了一个int类型的变量a。再来看下面的代码3d
class Base { }; class Derive:public Base { };
这里我写了一个Base类和一个Derive类,而且Derive类是派生于Base类指针
Base b; Derive d; Base* pb=&b; pb=&d;
上面的代码实例化了一个Base类类型的对象b,Derive类类型的对象d,Base*类型的指针pb。
对象
pb的静态类型就是Base*类型,咱们也可让pb指向d,Derive*就是pb的动态类型。blog
下面来讲说第二点,多态。继承
int Add(int left,int right) { return left+right; } double Add(double left,double right) { return left+right; }
上面这两个函数构成了函数的重载,传进去int类型的参数就调用上面的,double类型的参数就调用下面的。这也是一种多态,称为静态的多态。还有一种泛型编程也是静态的多态。静态多态就是在编译器编译期间完成的,编译器根据函数的实参的类型(可能进行隐式的类型转换),可推断出到底要调用哪一个函数,若是有对应的函数就调用该函数,不然就会出现编译错误。
那么动态多态(也叫动态绑定)就是指在程序执行期间判断所引用对象的实际类型,根据其实际类型调用相应的方法。
使用virtual关键字来修饰函数,指明该函数为虚函数,派生类须要从新实现,编译器将实现动态绑定。
所谓虚函数就是指在类中被声明为virtual的成员,基类但愿这种成员在派生类中重定义。除了构造函数外,任意非static成员均可觉得虚成员。保留字 virtual 只在类内部的成员函数声明中出现,不能用在类定义体外部出如今函数定义上。
看一段代码:
class Base { public: virtual void FunTest() { cout<<"Base::FunTest()"<<endl; } }; class Derive :public Base { public: void FunTest() { cout<<"Derive::FunTest()"<<endl; } }; int main() { Derive d; d.FunTest(); Base b; b.FunTest(); Base *pb=&b; pb->FunTest(); pb=&d; pb->FunTest(); return 0; }
在这一段代码里面,我定义了两个类一个是Base,另外一个是他的派生类Derive。Base类里面有一个虚函数FunTest(),Derive类里面也有一个FunTest()。而且在主函数里面实例化了两个类的对象,而且调用了FunTest函数,下面也定义了Base*类型的指针,先指向b,而后调用了FunTest函数,以后指向d,而后调用FunTest函数。这段代码运行结果会是什么样呢?
正如咱们所看到的调用派生类里面的函数他有他就调用他本身的,他没有再去基类里面找。
那么动态绑定实现的条件是什么呢?第一,必需要是虚函数。第二,要经过基类类型的引用或者指针调用。
class CBase { public: virtual void FunTest1(int _iTest) { cout << "CBase::FunTest1()" << endl; } void FunTest2(int _iTest) { cout << "CBase::FunTest2()" << endl; } virtual void FunTest3(int _iTest1) { cout << "CBase::FunTest3()" << endl; } virtual void FunTest4(int _iTest) { cout << "CBase::FunTest4()" << endl; } }; class CDerive:public CBase { public: virtual void FunTest1(int _iTest) { cout << "CDerive::FunTest1()" << endl; } virtual void FunTest2(int _iTest) { cout << "CDerive::FunTest2()" << endl; } void FunTest3(int _iTest1) { cout << "CDerive::FunTest3()" << endl; } virtual void FunTest4(int _iTest1,int _iTest2) { cout << "CDerive::FunTest4()" << endl; } }; int main() { CBase* pBase = new CDerive; pBase->FunTest1(0); pBase->FunTest2(0); pBase->FunTest3(0); pBase->FunTest4(0); return 0; }
上面是一个例子,CBase类是CDerive类的基类。以后FunTest1()是一个虚函数,FunTest2()不是一个虚函数,FunTest3()也是虚函数,FunTest4()虽然是虚函数可是在子类里面从新实现给了两个参数。因此运行结果是这样的:
假如咱们想调用CDerive里面的FunTest4(),咱们就要用CDerive类的对象了。就像下面这样:
CDerive d; d.FunTest4(0, 0);
这里还须要注意:构造函数是不能够定义为虚函数的,由于构造函数是用来构建咱们的对象 的,构造函数没有执行完咱们的对象就是不完整的。假如咱们要调用构造函数,是须要经过咱们的基类对象来调用的,可是咱们的对象都没有构造完,因此是不能这样的。
静态函数和友元函数也一样不能够用virtual来修饰。由于这两种函数都没有this指针。
这里还有一个东西:
class test { virtual void Test() = 0; };
这段代码定义的类叫抽象类。它不可以实例化产生对象,它只是提供一些接口。它里面的那个函数后面跟了一个=0,表示它是纯虚函数,它表示它的派生类要对它这个函数进行重写。
最后来讲虚表和虚指针。
当咱们求sizeof(test)时,咱们得出的结果是4。为何呢?对类求大小的时候不该该是它的成员的大小吗?这里就是有一个虚指针。那这个虚指针指向那里呢?是指向的虚表。虚表里面存的就是虚函数的地址。
举一个例子:
class test { public: virtual void FunTest1() {} virtual void FunTest2() {} virtual void FunTest3() {} virtual void FunTest4() {} };
这个类里面只有四个虚函数,那么sizeof(test)等于多少呢?
再来看看t中到底有什么:
因此当咱们要调用虚函数的时候,编译器是先找到咱们的虚表地址,以后找到对应的虚函数。