八、多态、虚析构

一、基于虚函数(virtual)的多态

#include <iostream>
using namespace std;

class CWater
{
public:
	void Show() 
	{
		cout << "CWater :: Show" << endl;
	}
};

class CBeer : public CWater
{
public:
	void Show()
	{
		cout << "CBeer :: Show" << endl;
	}

};

class CMilk : public CWater
{
public:
	void Show()
	{
		cout << "CMilk :: Show" << endl;
	}

};

class CCoco : public CWater
{
public:
	void Show()
	{
		cout << "CCoco :: Show" << endl;
	}

};

//==============================================================

void Bottle(CWater *p)
{
	p->Show();
}

int main()
{
	CBeer *beer = new CBeer;
	CMilk *milk = new CMilk;
	CCoco *coco = new CCoco;

	Bottle(beer);
	Bottle(milk);
	Bottle(coco);


	system("pause");
	return 0;
}

输出结果为:

 出现了上节所提到的问题:父类无法调用子类的成员函数。

解决方式为:多态—虚函数的使用

输出结果为:

 虚函数:通过父类的指针调用实际的子类的成员函数,前提是子类中需要重写这个函数,重写是名一样,参数列表也一样

如上图所示,只能输出第一个函数Show(),因为它与父类函数名一样,参数列表也一样。

多态提高了复用性和扩展性。

https://blog.csdn.net/qmroom/article/details/3085356

二、多态的实现原理 

★调用函数的两种方式:

#include <iostream>
using namespace std;

void Show(int a)
{
    cout << "AA" << a << endl;
}

int main()
{
    //函数名();                        //调用函数的方法—>功能是固定的
    Show(1);

    //(*函数指针名)();        //调用函数指针的方法—>功能是可变的,可扩展的
    void (*p)(int a); //定义一个函数指针
    p = &Show;//给函数指针赋值
    (*p)(2);


    system("pause");
    return 0;
}

//======================================================================== 

 定义一个父类,函数参数为空,输出长度为1。

#include <iostream>
using namespace std;

class CFather
{
public:
	void AA()
	{
		cout << "CFather :: AA" << endl;
	}
};

int main()
{
	cout << sizeof(CFather) << endl; //输出长度为1

	system("pause");
	return 0;
}

加上虚函数,再次运行,输出长度为4,是一个函数指针。 

 //=======================================================================

在父类中定义三个函数,两个虚函数AA和BB,普通函数CC,主函数中定义函数指针,并开辟父类空间 

#include <iostream>
using namespace std;

class CFather
{
public:
	virtual void AA()
	{
		cout << "CFather :: AA" << endl;
	}
	virtual void BB()
	{
		cout << "CFather :: BB" << endl;
	}
	void CC()
	{
		cout << "CFather :: CC" << endl;
	}
};
 
int main()
{
	cout << sizeof(CFather) << endl; 
	CFather*p = new CFather;

	system("pause");
	return 0;
}

在system("pause");处下断点,可以发现,父类指针p是一个函数指针,其中包括虚函数AA和BB  (都是父类的)

 //========================================================================

接上文,再设计一个子类,包括三个函数,两个虚函数(AA和DD),一个普通函数(CC);并改为开辟子类空间。 

#include <iostream>
using namespace std;

class CFather
{
public:
	virtual void AA()
	{
		cout << "CFather :: AA" << endl;
	}
	virtual void BB()
	{
		cout << "CFather :: BB" << endl;
	}
	void CC()
	{
		cout << "CFather :: CC" << endl;
	}
};

class CSon : public CFather
{
	virtual void AA()
	{
		cout << "CFather :: AA" << endl;
	}
	void CC()
	{
		cout << "CFather :: CC" << endl;
	}
	virtual void DD()
	{
		cout << "CFather :: DD" << endl;
	}

};
 
int main()
{
	cout << sizeof(CFather) << endl; 
	CFather*p = new CSon;

	system("pause");
	return 0;
}

 在system("pause");处下断点,可以发现,父类指针p是一个函数指针,其中包括虚函数AA和BB  (AA是子类的;BB是父类的,因为子类中未定义虚函数BB;之所以没有虚函数DD,是因为定义的是父类指针,只能看到父类成员的变化)

总结:

        每个类中定义的多个函数构成了一个虚函数列表,开辟父类空间则vfptr调用父类虚函数列表,开辟子类空间则vfptr调用子类虚函数列表。若子类重写了父类的函数,则重写的覆盖了父类函数,开辟子类空间,输出为重写的。虚函数输出长度为4,多出来的就是vfptr,它标记了对象调用哪个虚函数列表。

//======================================================================== 

★怎样取出虚函数列表里的内容?

CFather*p = new CSon;//p指向对象空间的首地址,但只想要前四个字节(vfptr)的首地址

对象前4个字节vfptr(是一个二级指针)装了数组的首地址,数组的每个元素装了函数的地址,那么,怎样拿到函数的地址? 

注意:函数指针不能进行地址偏移,因为函数几个字节不知道。每个指针占4个字节,只要从首地址向后偏移4个字节就能拿到下一个元素。

*(int*)p代表数组的首地址 ,因为地址的类型就是函数的类型(里面每个元素都是函数指针,不能做地址偏移),因此将它当作整型地址,可以向后偏移4个字节,((int*)*(int*)p+1)得到每个元素的地址。*((int*)*(int*)p+1)就取得这个地址对应的内存空间的内容,这个内容就是函数的地址。

#include <iostream>
using namespace std;

class CFather
{
public:
	virtual void AA()
	{
		cout << "CFather :: AA" << endl;
	}
	virtual void BB()
	{
		cout << "CFather :: BB" << endl;
	}
	void CC()
	{
		cout << "CFather :: CC" << endl;
	}
};

class CSon : public CFather
{
	virtual void AA()
	{
		cout << "CFather :: AA" << endl;
	}
	void CC()
	{
		cout << "CFather :: CC" << endl;
	}
	virtual void DD()
	{
		cout << "CFather :: DD" << endl;
	}

};
 
int main()
{
	cout << sizeof(CFather) << endl; 
	CFather*p = new CSon;//p指向对象空间的首地址,但只想要前四个字节(vfptr)的首地址
	typedef void (*PFUN)();

	PFUN  aa = (PFUN)*((int*)*(int*)p+0);//取得这个地址前4个字节装的内容,这个内容是虚函数列表的首地址。那怎样拿出每个元素呢? 
	PFUN  bb = (PFUN)*((int*)*(int*)p+1);
	PFUN  cc = (PFUN)*((int*)*(int*)p+2);
	PFUN  dd = (PFUN)*((int*)*(int*)p+3);

	(*cc)();

	system("pause");
	return 0;
}

 本节详见视频12-1

三、虚析构

虚析构:通过父类的指针完整删除一个子类的对象,防止内存泄漏。

#include <iostream>
using namespace std;

class CFather
{
public:
	CFather()
	{
		cout << "CFather" << endl;
	}
	virtual ~CFather()
	{
		cout << "~CFather" << endl;
	}
};

class CSon : public CFather
{
public:
	CSon()
	{
		cout << "CSon" << endl;
	}
	~CSon()
	{
		cout << "~CSon" << endl;
	}
};
 
int main()
{
	{
		CFather *son = new CSon;
		delete son;
	}

	system("pause");
	return 0;
}

输出结果为:

总结: 

1、什么是多态?

父类的指针指向一个子类的对象,然后通过父类的指针调用这个实际的子类的成员函数,这种技术使父类的指针有多种形态。

2、多态是基于什么来实现的?

多态是基于虚函数实现的,虚函数要基于重写才可以。

3、虚析构

虚函数构造前提是:子类中需要重写这个函数,名一样,参数列表也一样。但是虚函数为什么不需要重写?因为子类重写函数是为了确定要调用哪一个,但析构函数只有一个,只要获得了析构函数的地址,那么无需重写也能实现虚析构。

4、虚函数实现多态的原理?

1)最基本的是函数指针

2)需要维护一个由函数指针组成的虚函数列表(vtable),还需要一个记录使用哪个列表的指针(vfptr)。

3)当调用一个虚函数,通过vfptr找到对应的vtable,调用表里的函数指针,如果在子类中重写了,表中装的就是子类的函数(覆盖),调用的也就是子类。

4)vtable每个类包含一个,编译器存在,vfptr是在创建对象的时候存在的,每个对象有一个,在构造函数里初始化,指向对应的虚函数列表

5、多态的优缺点?

优点:提高复用性和扩展性

缺点:1、空间 : 虚函数会占用虚函数列表的空间,虚函数类越多,列表就越多,占用空间就越               多。

           2、效率:普通函数通过函数名和函数地址就能调用这个函数;

                 虚函数要通过指针vfptr找到虚函数列表vtable,再找到里面对应的函数指针才能调用函             数。间接过程,效率比普通函数低一些。

           3、安全性:只要知道对象首地址,就可以取出指针vfptr,拿到虚函数列表,调用里面的函             数。如果有一些函数是私有的,不允许使用的,但它又是个虚函数,那么也能调用里面的               函数,因此安全性低。

6、设计原则之一:开闭原则——对扩展是开放的、对修改是关闭的,即在不修改一个软件实体的基础上去扩展其功能。

(6条消息) 设计原则:面向对象设计原则详解_hguisu的博客-CSDN博客_面向对象设计

 本节详见12-2视频

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值