admin 管理员组文章数量: 893597
漫谈虚函数
首先写这篇文章之前,想要说明的是这篇文章告诉了我很多细节性的知识点,所以在此感谢该文的作者v_JULY_v,有读者想访问其主页可以点击这里
C++的虚函数大家比较熟悉,说到虚函数就必须提及多态性,C++的多态性能有效提高基类的可操作性,利用指向基类的指针或者基类的引用,根据运行时编译确定所需要执行的子类的方法,大大提高了代码的可重用性以及类的可扩展性与灵活性。这里多提一句:虚函数与多态性,涉及到的是父类与子类之间方法的重载,这里的重载可以理解为方法的覆盖,这与同一个类内部方法的重载是不一样的。那么有无虚函数到底有什么样的区别呢?我们先看一个例子:
一、一段代码告诉你虚函数的奇特
我们先来看下面一段代码:#include<iostream>
using namespace std;
class A
{public:virtual void fun{cout<<"A"<<endl;}
};
class B:public A
{public:virtual void fun{cout<<"B"<<endl;}
};
int main(int argc,char* argv)
{A* a = new A();A* b = new B();a->fun();b->fun();
}
请问输出的结果是什么?恩,答案是A和B。现在对上面的代码中A类和B类的fun函数去掉virtual关键字,执行同样的main函数,答案又是如何呢?这次的答案是A和A。
为什么会产生如上的不同结果呢?
由于基类在派生出派生类的时候,会把自己的成员变量与成员函数一并的派生过去(这里认为是public继承,protected和private继承先不讨论)。所以派生类在构造自身对象的时候会先调用基类的构造函数,再调用自己的构造函数,所以说派生类的一部分是属于基类的。当派生类对象调用非虚函数的时候,函数与对象之间的绑定已经在编译期就已经静态确定了,而调用虚函数方法的时候却要到运行时才能动态确定绑定的对象。归结来说, 无论基类的指针是指向基类对象还是派生类对象,只要调用的方法不是虚方法,就无视子类的方法,直接调用基类自己的方法(同名),不知道这样说大家明白了不,一个是静态绑定,另一个则是动态绑定。
二、虚函数的本质
关于虚函数的本质, 先用一句话做开头:每个类都有一个虚函数表,虚函数表里记录了该类中对应的虚函数的函数地址。类在产生对象的时候,对象会带有一个vptr指针,指针用来指向虚函数表。 OK,来看一个例子:class Demo {
public: virtual ~Demo(); virtual Demo& fun( float ) = 0; float a() const { return _a; } //非虚函数,不作存储virtual float b() const { return 0; } virtual float c() const { return 0; } // ...
protected: Demo( float x = 0.0 ); float _a;
};
在这段代码里有5个函数,其中虚函数有4个,分别是虚析构函数(类在被定义为派生类的时候析构函数请定义为虚),fun(),a(),b(),c()。所以在该类产生的虚表中存放了这四个虚函数的地址,而由该类产生的对象则有两部分内容,第一个是成员变量_a,另一个则是指向虚函数表的指针vptr。当Demo类被继承时,子类一般会派生出属于自己的新成员函数与成员变量,这时候会发生什么呢?
我们假设在Demo类的基础上派生出新的成员变量float _b;当然了派生的时候可以重写虚方法,所以在派生类的对象中,新添加了新的部分_b,同时vptr指向了新的虚表。所以在一个类的生成过程中,会产生一定的内存分配来给虚表占用,这就会联想出一个问题:如果一个类反复的一层层继承下去,会不会导致大量的虚表产生而导致内存浪费呢?
请我们一起思考下吧...这也是MFC采用消息映射的原因之一。
三、虚函数表的原理
谈及虚函数表的内容已经在上文提过,现在来讨论下当继承发生的时候,虚函数表会发生什么变化? 首先明确一点,只有继承发生时,父类与子类存在virtual函数,虚函数指针才会产生。 看下图: 派生类从基类产生,基类有虚函数f(),g(),h()而派生类有虚函数f1(),g1(),h1(),那么这整个过程在虚函数表里如何体现? 当Derive生成对象时,有一个指向虚函数表的指针vptr,虚表里的函数地址是按照类中虚函数的声明顺序排放的,假设虚函数表的以栈的结构存储,那么最上面放的先是Base的虚函数而之后才存放Derive的虚函数,注意,此时两个类的虚函数是没有继承关系的,你是你的,我是我的,我比你大,所以在虚函数里我摆前面~ 那么当Base在派生出派生类Derive时虚函数被重写了会发生什么? 答案是同名函数,Derive的放在了Base类的前面,如下图: 所以只要虚函数不被重写,谁归属的类级别大谁就放在虚函数的前面,只有被重写之后,子类的虚函数会被置于虚表中基类同名虚函数的前面。 此时我们再联想开头的例子,两个方法都是虚方法,被重写的时候子类的fun其实是摆放在基类的fun之前的(虚表中),所以尽管是指向基类对象的指针,指针在调用函数的时候最先找到的是子类的虚函数地址,所以调用了。(什么是运行时?运行的时候才发现调用那个函数,什么是运行的时候,产生对象的时候,产生对象的时候意味产生虚表指针,有了虚表指针就可以找到虚表,找到虚表就知道了虚函数的顺序,就可以调用了,这就是运行时调用,请允许我那么狗血的理解)。虚函数表的地址存放在对象内存的前四个字节,虚函数表只存放虚函数的指针数组,只放虚函数的地址哟。最后多说一些,想多了解更多细节的还是请回头看看JULY的文章吧(开头有说明),谢谢大家。
本文标签: 漫谈虚函数
版权声明:本文标题:漫谈虚函数 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1687604154h120135.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论