继承与多态中常见的问题(二)

继承与多态中常见的问题分析
1、画出派生类Derive的内存布局
#include<iostream>
using namespace std;
class Base
{
public:
	Base( int data ):ma(data) { };
	virtual void Show()
	{
		cout<<"Base::Show()"<<endl;
	}
private:
	int ma;
};
class Derive : public Base
{
public:
	Derive( int data) :mb (data) { };
	virtual void Show()
	{
		cout<<"Derive ::Show()"<<endl;
	}
private:
	int mb;
};
int main()
{
	Base *p = new Derive( 10 );
	p->Show ();
	delete p ;
	return 0;
}
 
  
 
  
 
  
 
 


(1)如果Base中的Show函数是虚函数,那么p->show();则动态绑定,打印出Derive::Show();
(2)如果Base中的Show函数不是虚函数,Derive中的show函数不是虚函数,那么p->Show(),则静态绑定,打印出Base::Show();
(3)如果Base中的Show函数不是虚函数,Derive中的show函数是虚函数,那么p->Show(),则在编译器发现Base::Show()不是虚函数,则不会发生动态绑定,故打印Base::Show();此时的内存布局是:

但是在执行上述main()代码中,执行到delete p时会崩溃。原因是:基类指针指向派生类对象,指针永远指向基类部分的起始地址,但是此时派生类的起始地址和基类的起始地址不在一起,故从基类起始地址处释放则会崩溃。(开辟和释放的内存不在一起)
注:delete调用的析构函数是指针类型 。new和delete两者配对使用,两者不配对使用的情况有:(1)自定义类型 (2)提供了自定义类型的析构函数
2、为什么要将基类的析构函数写为虚函数?



由以上可知:当基类对象指针指向堆上的派生类对象时,需要将基类 的析构函数写为虚析构函数。
3、
#include<iostream>
using namespace std;
class Base
{
public:
	Base() { };
	Base( int data ) { clear() };//执行clear()前的汇编指令为:
/*
push ebp
mov ebp,esp
sub esp,4ch
rep ……
vftable->vfptr
*/
	void clear()
	{
		memset(this,0,sizeof(this));
	}
	virtual void Show()
	{
		cout<<"Base::Show()"<<endl;
	}
protected:
	int ma;
};
class Derive : public Base
{
public:
	Derive( int data) :Base(data),mb (data) { };
	virtual void Show()
	{
		cout<<"Derive ::Show()"<<endl;
	}
private:
	int mb;
};
(1)
int main()
{
	Base *p = new Base( 10 );
	p->Show ();//动态绑定
	delete p ;//程序崩溃
	return 0;
}
此时程序崩溃的原因是:在调用构造函数Base时,调用了clear()将内存中的元素清空为0,(也将前4个字节(存放vfptr)置为0)
(2)
int main()
{
	Base *p = new Derive( 10 );//先构造基类,后将Base中构造的内容清为0.派生类构造时重新将vftable写入vfptr中,不过此时的vftable是派生类的。
	p->Show ();//动态绑定  调用派生类的show()
	delete p ;
	return 0;
}
(3)去掉Base构造函数中的clear(),给Base和Derive构造函数中加上Show(),并不会发生动态绑定,因为在构造函数中对象还没有生成,不会发生多态。
(4)在Base的析构函数中调用Show();,此时不会发生动态绑定,因为析构完后对象已经不存在了,所以不会发生。
(5)将派生类中的成员函数设为私有的,在编译时期,是否会发生编译错误?(访问限定符只可以在编译阶段起作用)



由此可见:将派生类的虚函数设为私有的,编译不会出错,此时这个函数能否被调用主要取决于基类中该虚函数的访问限定符。
4、



注:成员能不能被访问,访问权限是否正确?函数的默认值用哪个?这些均是在编译时期都已经确定好了,最终调用哪个派生类对象的方法在运行时确定。(在虚表中取谁的地址,运行时就调用谁)。

猜你喜欢

转载自blog.csdn.net/minld/article/details/77345668