六一献礼——动态绑定解密

六一献礼——动态绑定解密

作者:chence  时间:2012/6/1

献给我的老婆...愿她像小朋友一样健康、快乐...

程序1:

#include <iostream>
using namespace std;
#include <windows.h>

class A{
protected:
	string m_s;
public:
	A(string s):m_s(s){}
public:
	void virtual show()
	{
		MessageBoxA(NULL, m_s.c_str(), "A", MB_OK);
	}
};

class B:public A{
public:
	B(string s):A(s){}
public:
	void virtual show()
	{
		MessageBoxA(NULL, m_s.c_str(), "B", MB_OK);
	}
};

int main()
{
	A a("This is class of A");
	B b("This is class of B");

	a.show();
	b.show();

	A *p, *pa = new A("This is a object of A via new!");
	p = pa;
	pa->show();
	pa = new B("This is a object of B via new!");
	pa->show();

	delete p, pa;
	return 0;
}

在main函数第5行下个断点,执行完这个函数后,看看pa指针

pa指向的内存包含一个A类的虚表指针。pa->show()调用的是A类的show()函数。

执行完第8行后:

可以看出pa的地址没有变化,变化的是pa的值,也就是pa指向的内存。pa其实指向的是一个B类对象,包含的虚表指针也是指向B类虚表的。故通过pa->show()调用的是B类的show()函数。

综上,通过pa调用show函数是动态的,这得看pa指向的哪个类。这就是多态的核心所在吧...

 

程序2:

#include <iostream>
using namespace std;

class A
{
public:
	A(int a):val_A(a){}
	A():val_A(10){}

	void fa()
	{	
		cout << "this is func_fa of class A." << endl;
	}
	void virtual f()
	{
		cout << "Virtual func_f of A" << endl;
	}
private:
	int val_A;
};

class B:public A
{
public:
	B(int b):val_B(b){}
	B():val_B(11){}

	void fb()
	{	
		cout << "this is func_fb of class B." << endl;
	}

	void virtual f()
	{
		cout << "Virtual func_f of B" << endl;
	}

	void virtual h()
	{
		cout << "Virtual func_h of B" << endl;
	}
private:
	int val_B;
};

int main()
{
	A* pa = new A();
	pa->f();
	
	B* pb = new B();
	pb->f();

	delete pa,pb;
	return 0;
}

程序输出:

Virtual func_f of A

Virtual func_f of B

main函数的第3行下个断点,F5进入调试。执行第3..

看看Local窗口各变量的内存:

先看papa指向A类的一个对象,该对象包含一个虚表指针_vfptr和私有成员val_A.虚表指针类型为const A::’vftable’,虚表包含的虚函数类型为A::f(void).

pbpb指向B的一个对象,该对象包含A类的一个对象和私有成员val_B. A类的对象包含一个虚表指针_vfptrval_A,注意虚表指针类型为const B::’vftable’,虚表包含的虚函数类型为B::f(void).

由此可见派生类对象的内存组成为:父对象+ 子类自身的新的成员变量。父对象组成为:子类的虚表指针(如果有虚函数的话) +父类的成员变量。虚表里的函数指针规则为:如果子类重写了某个函数,则虚表里的对应的函数指针就指向子类重写的函数,否则就指向父类的那个函数。

 

对主函数作些许修改:

int main()
{
	A* pa = new A();
	pa->f();
	
	B* pb = (B*)pa;//新建一个B类指针,并将pa赋给它
	pb->f();

	delete pa,pb;
	pa = new B();
	pa->f();

	pb = (B*)pa;
	pb->f();

	delete pa,pb;
	return 0;
}


程序输出:

Virtual func_f of A

Virtual func_f of A

Virtual func_f of B

Virtual func_f of B

执行完第三句的时候,看看papb指针:

B* pb =(B*)pa;这个语句就是将pa所指的地址强制转换成B类指针,但是地址所指内存的内容却没有发生任何变化。本来new的一个对象是A,故属于对象的内存只包括一个虚表指针和A类的一个成员变量val_A=0xa。但是现在强制转换成了B类指针,因此会将紧跟后面的4个字节和前面A类对象组合成一个完整的B类对象。因为后面的4个字节没有初始化,故为一个非法值(0xfdfdfdfd)。所以用pb指针调用f()的时候,调用的实际上是A类的f().

我们还注意到pa和pb其实指向同一个对象,后面delete了两次却没有出现问题,说明delete运算符会检查是否已经释放。这和free()函数有很大区别。

接下来new了一个B对象,并将其指针赋给pa。后面又将pa转换成B类指针赋给pb。

看下这两个指针:

可以看到从new了开始,内存空间就已经分配和初始化好了。由于new的是一个B类对象,故虚表指针就指向一个B类的虚表。不管后面将该地址强制转化成一个什么对象指针,内存的数据不会变。虽然pa是一个A类指针,当把B类指针赋给它的时候,它调用的虚函数却是B类的虚函数。所以调用的函数不跟指针的类型相关,而是看它指向什么类型对象。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值