一、题目要求:写出下面程序的运行结果?
//谢谢董天喆提供的这道百度的面试题
#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、存放类对象的内存区的前四个字节其实就是用来存放虚表的地址的