一、题目要求:写出下面程序的运行结果?
//谢谢董天喆提供的这道百度的面试题 #include <iostream> 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 <iostream> using namespace std; class Base1 { public: virtual void f() { std::cout << "F1"<<endl; }; virtual void g() { std::cout << "G1"<<endl; }; }; class Base2 { public: virtual void f() { std::cout << "F2"<<endl; }; virtual void g() { std::cout << "G2"<<endl; }; }; class Base3 { public: virtual void f() { std::cout << "F3"<<endl; }; virtual void g() { std::cout << "G3"<<endl; }; }; class Derive : public Base1,public Base2,public Base3 { public: virtual void f() { std::cout << "D"<<endl; }; }; int main(int argc, char* argv[]) { 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() return 0; }
多态:
#include <iostream> 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 <iostream> 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 <iostream> using namespace std; class B { public: virtual void fun(int i=1) { std::cout << "B"<<i; }; }; class D : public B { private: virtual void fun(int i=2) { std::cout << "D"<<i; }; }; int main(int argc, char* argv[]) { B* p = new D(); p->fun(); return 0; }
输出:D1
原因:virtual 函数系动态绑定, 而缺省参数却是静态绑定”,
也就是说在编译的时候已经按照p的静态类型处理其默认参数了,转换成了(*p->vptr[1])(p, 1)这样的方式
后注:
1、存放类对象的内存区的前四个字节其实就是用来存放虚表的地址的