博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
第八章:虚函数笔记(虚函数碉堡了)
阅读量:4134 次
发布时间:2019-05-25

本文共 2770 字,大约阅读时间需要 9 分钟。

详见:

一、题目要求:写出下面程序的运行结果?

//谢谢董天喆提供的这道百度的面试题   #include 
using namespace std; class A{ public:virtual void p() { cout << "A" << endl; } }; class B : public A { public:virtual void p() { cout << "B" << endl; } }; int main() { A * a = new A; A * b = new B; a->p(); b->p(); delete a; delete b; return 0; }
答案是:A,B

一、有无虚函数区别:

无虚函数时程序此时将输出两个A,A。为什么?

在构造一个类的对象时,如果它有基类,那么首先将构造基类的对象,然后才构造派生类自己的对象。

如上,A* a=new A,调用默认构造函数构造基类A对象,然后调用函数p(),a->p();输出A。

然后,A * b = new B;,构造了派生类对象B,B由于是基类A的派生类对象,所以会先构造基类A对象,然后再构造派生类对象,

但由于当程序中函数是非虚函数调用时,B类对象对函数p()的调用时在编译时就已静态确定了,所以,不论基类指针b最终指向的是基类对象还是派生类对象,只要后面的对象

调用的函数不是虚函数,那么就直接无视,而调用基类A的p()函数。

二、虚函数的原理与本质

虚(virtual)函数的一般实现模型是:每一个类(class)有一个虚表(virtual table),内含该class之中有作用的虚(virtual)函数的地址,然后每个对象有一个vptr,指向虚表

(virtual table)的所在

多重继承事例:

#include 
using namespace std; class Base1 { public: virtual void f() { std::cout << "F1"<
f(); //Derive::f() b2->f(); //Derive::f() b3->f(); //Derive::f() b1->g(); //Base1::g() b2->g(); //Base2::g() b3->g(); //Base3::g() return 0; }
多态:

#include 
using namespace std; class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } }; class Derive : public Base{ public: virtual void f() { cout << "Derive::f" << endl; } virtual void g() { cout << "Derive::g" << endl; } }; typedef void(*Fun)(void); void main() { Base *d = new Derive; Fun pFun = (Fun)*((int*)*(int*)(d)+0); printf("&(Base::f): 0x%x \n", &(Base::f)); printf("&(Base::g): 0x%x \n", &(Base::g)); printf("&(Derive::f): 0x%x \n", &(Derive::f)); printf("&(Derive::g): 0x%x \n", &(Derive::g)); printf("pFun: 0x%x \n", pFun); pFun(); }

打印的时候表现出来了多态的性质:

pFun与&(Base::f) 不相等

安全性问题:

访问non-public的虚函数

#include 
using namespace std; class Base {private: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } };class Derive : public Base{ };typedef void(*Fun)(void); int main() { Derive d; Fun pFun = (Fun)*((int*)*(int*)(&d)+0); pFun(); pFun = (Fun)*((int*)*(int*)(&d)+1); pFun(); return 0; }

注:

1. (int*)(&d)取vptr地址,该地址存储的是指向vtbl的指针

2. (int*)*(int*)(&d)取vtbl地址,该地址存储的是虚函数表数组
3. (Fun)*((int*)*(int*)(&d) +0),取vtbl数组的第一个元素,即Base中第一个虚函数f的地址
4. (Fun)*((int*)*(int*)(&d) +1),取vtbl数组的第二个元素

子类重载的虚拟函数为private,通过父类的指针访问

#include 
using namespace std; class B { public: virtual void fun(int i=1) { std::cout << "B"<
fun(); return 0; }
输出:D1

原因:virtual 函数系动态绑定, 而缺省参数却是静态绑定”,
也就是说在编译的时候已经按照p的静态类型处理其默认参数了,转换成了(*p->vptr[1])(p, 1)这样的方式

后注:

1、存放类对象的内存区的前四个字节其实就是用来存放虚表的地址的

转载地址:http://ibvvi.baihongyu.com/

你可能感兴趣的文章