现在的位置: 首页 > 综合 > 正文

第八章:虚函数笔记(虚函数碉堡了)

2014年08月29日 ⁄ 综合 ⁄ 共 2771字 ⁄ 字号 评论关闭

详见:程序员编程艺术:第八章、从头至尾漫谈虚函数

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

//谢谢董天喆提供的这道百度的面试题   
#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、存放类对象的内存区的前四个字节其实就是用来存放虚表的地址的

抱歉!评论已关闭.