多态是c++中很重要的一环。多态可以分为以下几个层面来剖析:
1.对象的类型
2.多态
3.虚表
先说第一点对象的类型,这个非常简单。比如说、
int a;
那么我就定义了一个int类型的变量a。再来看下面的代码
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的动态类型。
下面来说说第二点,多态。
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中到底有什么:
所以当我们要调用虚函数的时候,编译器是先找到我们的虚表地址,之后找到对应的虚函数。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。