C++虚函数
2015-09-13 19:35
513 查看
转自:/article/2551883.html
测试代码:
其中:*(long long*)(&b)是把对象b最前面8字节内容取出;((long long*)*(long long*)(&b)),把取出的内容看作地址,也就是虚函数表的开始地址,即Base::f()所在位置;*((long long*)*(long long*)(&b))把虚函数表第一个位置内容取出;(Fun)*((long long*)*(long long*)(&b)内容作为地址,赋给函数指针。
结果:
虚函数表地址:0000006A8A65FB78
虚函数表 — 第一个函数地址:000000004CEA0498
Base::f
Base::g
Base::h
其它成员指的是member,不包括member function。成员函数存放在代码区域,同过对象空间内的指针调用。
注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。
一般继承(无虚函数覆盖)
我们可以看到下面两点:
虚函数按照其声明顺序放于表中。
父类的虚函数在子类的虚函数前面。
一般继承(有虚函数覆盖)
我们从表中可以看到下面两点:
覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
没有被覆盖的函数依旧。
多重继承(没有虚函数覆盖)
假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。
我们可以看到:
每个父类都有自己的虚表。
子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。
多重继承(有虚函数覆盖)
我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:
此处注意数组指针和二级指针的区别。
注:如果基类的析构函数声明为虚函数,那么在虚函数表base1中,Deriver::g1前会有base1析构函数的地址;在vs的虚函数表中base1析构函数后的地址不会显示,但确实存在。
测试代码:
class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } }; Base b; Fun pFun = NULL; cout << "虚函数表地址:" << (int*)(&b) << endl; cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl; // Invoke the first virtual function pFun = (Fun)*((long long*)*(long long*)(&b)); pFun(); pFun = (Fun)*((long long*)*(long long*)(&b) + 1); pFun(); pFun = (Fun)*((long long*)*(long long*)(&b)+2); pFun();
其中:*(long long*)(&b)是把对象b最前面8字节内容取出;((long long*)*(long long*)(&b)),把取出的内容看作地址,也就是虚函数表的开始地址,即Base::f()所在位置;*((long long*)*(long long*)(&b))把虚函数表第一个位置内容取出;(Fun)*((long long*)*(long long*)(&b)内容作为地址,赋给函数指针。
结果:
虚函数表地址:0000006A8A65FB78
虚函数表 — 第一个函数地址:000000004CEA0498
Base::f
Base::g
Base::h
其它成员指的是member,不包括member function。成员函数存放在代码区域,同过对象空间内的指针调用。
注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。
一般继承(无虚函数覆盖)
我们可以看到下面两点:
虚函数按照其声明顺序放于表中。
父类的虚函数在子类的虚函数前面。
一般继承(有虚函数覆盖)
我们从表中可以看到下面两点:
覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
没有被覆盖的函数依旧。
多重继承(没有虚函数覆盖)
假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。
我们可以看到:
每个父类都有自己的虚表。
子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。
多重继承(有虚函数覆盖)
我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:
Derive d; Base1 *b1 = &d; Base2 *b2 = &d; Base3 *b3 = &d; b1->f(); //Derive::f() b2->f(); //Derive::f() b3->f(); //Derive::f() b1->g(); //Base1::g() b2->g(); //Base2::g() b3->g(); //Base3::g()
long long** p = (long long**)&b; pFun = (Fun)p[0][0]; pFun(); pFun = (Fun)p[0][1]; pFun(); pFun = (Fun)p[0][2]; pFun();
此处注意数组指针和二级指针的区别。
注:如果基类的析构函数声明为虚函数,那么在虚函数表base1中,Deriver::g1前会有base1析构函数的地址;在vs的虚函数表中base1析构函数后的地址不会显示,但确实存在。
相关文章推荐
- C语言strstr()函数常用示例详解[求一个字符串在另一个字符串中出现的次数]
- C语言 结构体
- 第2周项目1 C/C++语言中函数参数传递的三种方式
- C语言定义字符串和字符数组
- C++处理字符串
- 第二周项目-c++语言中函数参数传递的三种方式
- 从C/C++到iOS,然后方向OpenGL资料
- POJ C++程序设计 编程题#4:计算整数平方和
- 实现C++ String类
- C++中用函数返回值初始类对象时的一个问题
- C++基础---面向对象建模方法
- [C++]虚函数
- C++中 常量引用、指向常量的指针、常量指针的区别
- POJ C++程序设计 编程题#3:计算数列平方和
- Effective C++——条款35(第6章)
- c++stl之逆向迭代器 Reverse Iterators
- POJ C++程序设计 编程题#2 输出指定结果一
- 第二周上机实践项目2——C/C++语言中函数参数传递的三种方式
- C++ 虚函数表解析
- 设计模式C++实现(5)——原型模式、模板方法模式