C++之 “虚函数” 详解
来源:互联网 发布:淘宝新版质保金好退吗 编辑:程序博客网 时间:2024/05/24 04:00
C++之 “虚函数” 详解
原文见:www.louhang.xin
虚函数在C++中有着十分重要的作用,通过虚函数可以实现多态(polymorphism)机制。
在看《C++ primer plus》时,发现作者将虚函数放在类继承的一章之中,和动态/静态联编一起进行了讲解。我也就顺着复习了继承,便再剖析一下虚函数。
虚函数--类的成员函数前面加virtual关键字,则这个成员函数称为虚函数。
在之前的博客《多态及其对象模型》中我剖析虚函数在其中的作用。
底层原理:
当存在虚函数时,编译器会给每个对象添加一个隐藏成员,此隐藏成员保存的是指向虚函数地址数组的指针。而这个数组就叫虚函数表(virtual function table)。
虚函数表中存放的正是为类对象进行声明的虚函数的地址。
需要注意的是,在继承之中,如果基类存在虚表,那么基类对象将存在一个指向其虚表的指针;而派生类对象中将包含的是一个指向独立虚表的指针。这个独立虚表中不仅包含从父类继承的虚函数地址,还包括派生类定义的新的虚函数的地址。
当调用虚函数时,程序将查看存储在对象中的虚表的地址,进而去虚表中查找相应的虚函数。
代码如下:
#include<iostream>using namespace std;//单继承模型class Father{public:virtual void fun1(){cout << "Father::fun1()" << endl;}virtual void fun2(){cout << "Father::fun2()" << endl;}protected:int _a = 10;};class Son : public Father{public:virtual void fun2(){cout << "Son::fun2()" << endl;}virtual void fun3(){cout << "Son::fun3()" << endl;}protected:int _b = 20;};void Fun(Father& f){f.fun2();}void test(){Father a;Son b;Fun(a);Fun(b);}int main(){test();system("pause");return 0;}
在监视窗口查看:
我们可以看到在基类内部存在着一个地址,而这个地址指向了一张表,既虚表,虚表内部存储的正是虚函数。但是我们会发现派生类虚函数fun3却不在此表内部。实际上,fun3也在此表内部,只不过编译器做了优化,没有在监视窗口显示出来而已。
我们可以在内存中观察,也可以通过书写函数将虚表内的地址打印出来。
内存中观察:
通过函数来将虚表及其内存放的地址打印出来,如下:
#include<iostream>using namespace std;class Father{public:virtual void fun1(){cout << "Father::fun1()" << endl;}virtual void fun2(){cout << "Father::fun2()" << endl;}private:int _a = 10;};class Son : public Father{public:virtual void fun2(){cout << "Son::fun2()" << endl;}virtual void fun3(){cout << "Son::fun3()" << endl;}protected:int _b = 20;};typedef void(*FUNC) ();void PrintVTable(int* VTable){cout << " 虚表地址>" << VTable << endl;for (int i = 0; VTable[i] != 0; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, VTable[i]);FUNC f = (FUNC)VTable[i];f();}cout << endl;}void test(){Father a;Son b;int* VTable1 = (int*)(*(int*)&a);int* VTable2 = (int*)(*(int*)&b);PrintVTable(VTable1);PrintVTable(VTable2);}int main(){test();system("pause");return 0;}
可以看到在创建的两个对象中都存在着虚表。这也不可避免的出现了一些问题:
每个对象占据内存都变大了,增加内存来存放虚表的地址。
针对每个类,编译器都要创建一个虚函数表。
每次的函数调用,都要额外执行操作,要去虚表中查找虚函数的地址。
虽然非虚函数比虚函数的效率稍高一点,但是起不具备动态联编,不能构成多态。
虚函数应用:
构造函数
构造函数不能为虚构函数。虽然可以将operator=定义为虚函数,但是最好不要将operator=定义为虚函数,因为容易使用时容易引 起混淆。
析构函数
析构函数定义为虚函数,除非类不用做基类。
class A{public: A() { _ptra = new char[10]; } ~A() { delete[] _ptra; } private: char* _ptra;};class B: public A{public: B(){ _ptrb = new char[20];} ~B() { delete[] _ptrb;}private: char * _ptrb;};void test(){ A * a = new B; delete a;}
上面的程序存在内存泄漏,因为其是静态联编,在释放对象a时仅仅调用了A的·析构函数调用了,B的析构函数并未调用,这就造成了一个很危险的漏洞。
但如果将A类的析构函数定义为虚析构函数,那么执行的将是动态联编,则会将内存全部成功的释放掉。
纯虚函数
纯虚函数只进行声明,而不定义。如下:
class Test{public: virtual void fun()=0; // =0 标志一个虚函数为纯虚函数};
包含有纯虚函数的类是抽象类,而抽象类不能进行实例化。只能被其他派生类继承,而纯虚函数就是一个公共的接口,所有继承了抽象类的派生类内部都包含纯虚函数。
纯虚函数不定义,其定义交给派生类来完成,继承了抽象类的派生类必须对纯虚函数进行定义。
友元函数
友元函数不能定义为虚函数。因为友元不是类的成员,只有类的成员才能定义为虚函数。
总结:
1. 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
2. 基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
3. 只有类的成员函数才能定义为虚函数。
4. 静态成员函数不能定义为虚函数。
5. 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual。
6. 构造函数不能为虚函数,最好也不要将operator=定义为虚函数,因为容易使用时容易引起混淆。
7. 不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为。
8. 最好把基类的析构函数声明为虚函数。
参考资料:
《C++ primer plus》Stephen Prata,张海龙,袁国忠译
《深度探索C++对象模型》 Stanley B.Lippman,侯捷译
- C之system函数详解
- 虚函数详解(C++)
- c语言之memset函数详解
- c语言之 malloc函数详解
- c语言之 malloc函数详解
- C++之虚函数详解
- C++之 “虚函数” 详解
- C语言详解 之 函数参数的实现
- C编程之memcpy函数详解(附带几个例子)
- 【C/C++】C++之private虚函数
- c#日期函数详解
- C函数Strtok详解
- C语言函数详解
- c++string函数详解
- c函数指针详解
- c函数指针详解
- c函数指针详解
- c函数指针详解
- Redis —— 在ubuntu下的安装教程
- Mysql根据条件批量更新动态数据
- 1341三角形
- C3P0连接池报错:java.lang.NullPointerException 空指针异常
- Python---numpy的初步认识
- C++之 “虚函数” 详解
- 图像旋转公式
- Unity之Renderer组件
- 产品经理——初步认识
- (9)spring更多di知识
- 机器学习第三个算法SVM上(支持向量机)
- GC机制、收集器与GC调优
- 深度学习-梯度下降和梯度爆炸问题
- 递归 理解