C/C++笔记--多态及虚表分析

来源:互联网 发布:手机节拍器软件 编辑:程序博客网 时间:2024/06/05 02:48

关键字:继承,虚函数,虚表,动态绑定,指针,引用
什么是多态
多态性是面向对象设计语言的基本特征。仅仅将数据和函数捆绑到一起,进行类的封装,使用一些简单的继承,还不能算是真正应用了面向对象的设计思想。多态性是面向对象的精髓。多态性可以概括为“一个接口,多种方法”,函数重载就是一种简单的多态,一个函数名(调用接口)对应着几个不同的函数原型(方法)。
更通俗的说,多态性是指同一个操作作用于不同的对象就会产生不同的响应;多态性分为静态多态性和动态多态性,其中函数重载和运算符重载属于静态多态性,虚函数属于动态多态性。C++依靠虚函数来实现动态多态。
联编或绑定
编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数,称为联编或绑定。编译器可以在编译过程中完成绑定,称为静态绑定,反之则为动态绑定。和普通函数一样,虚函数一样可以通过函数名来调用,此时编译器采用的是静态绑定。通过这种形式调用哪个类的函数取决于定义对象名的类型。使用指针或引用就是动态绑定,编译器根据指针所指对象的类型确定调用那一个函数。

问题拓展
Q: 哪些函数不能声明为虚函数?
A: 普通函数(非成员函数)、静态成员函数、构造函数、友元函数等,而内联函数、赋值操作符重载函数即使声明为虚函数也没有意义
分析
1)为什么C++不支持普通函数为虚函数?
因为普通函数只能重载,不能覆盖,声明为虚函数也没有意义,反正编译器会在编译时就绑定函数,没有多态的效果。
2)为什么C++不支持构造函数为虚函数?
根据继承的性质,构造函数的执行顺序是先父后子,根据虚函数的性质,如果父构造为虚函数,创建子类不会执行父类构造。前后矛盾。另外,虚函数是不同对象产生不同的动作,现在对象没有产生,如何使用虚函数?
3)为什么C++不支持静态成员函数为虚函数?
静态成员函数对于类的每一个对象来说只有一份代码,属于所有对象共享,所以不能为虚。
4)为什么C++不支持友元函数为虚函数?
因为友元函数不能被继承,声明为虚函数没有意义。

总结
编译器在编译的时候,发现父类中有虚函数,此时编译器会为每个包含虚函数的类创建一个虚表(即vtable),该表是一个一维数组,在这个数组中存放每个虚函数的地址。父类和子类都包含了一个虚函数,因此编译器会为这两个类都建立一个虚表,(即使子类里面没有virtual函数,但是其父类里面有,所以子类中也有了)。

那么如何定位虚表呢?编译器另外还为每个类的对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表,从而在调用虚函数时,就能够找到正确的函数(动态绑定)。由于指针实际指向的对象类型是子类,因此vptr指向的子类的vtable,当调用此虚函数时,根据虚表中的函数地址找到的就是子类的虚函数。
正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的。换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数。那么虚表指针在什么时候,或者说在什么地方初始化呢?
答案是在构造函数中进行虚表的创建和虚表指针的初始化。还记得构造函数的调用顺序吗,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否后还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表。当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。当子类的对象构造完毕后,其内部的虚表指针也就被初始化为指向子类自身的虚表。在类型转换后,调用指向子类的父类指针指向的虚函数,由于指针实际指向的是子类的对象,该对象内部的虚表指针指向的是子类的虚表,因此最终调用的是子类的函数。
要注意:对于虚函数调用来说,每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。

C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。

使用多态的好处:
  把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。 赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。也就是说,父亲的行为像儿子,而不是儿子的行为像父亲。参考设计模式的简单工程模式。

0 0
原创粉丝点击