C++类与对象__多态

多态:就是使用虚函数,对父类的成员函数在子类中进行重构,在调用的时候让程序自动选择最匹配的函数和节省程序的开销,
在程序中定义的时候使用父类的指针访问子类的成员函数;
实现多态最主要的就是虚函数的使用;

虚函数介绍:
1.对象内,首先存储的是“虚函数表指针”,又称“虚表指针”。然后再存储非静态数据成员。

2.对象的非虚函数,保存在类的代码中!对象的内存,只存储虚函数表和数据成员
(类的静态数据成员,保存在数据区中,和对象是分开存储的)

3.添加虚函数后,对象的内存空间不变!仅虚函数表中添加条目多个对象,共享同一个虚函数表!

4.子类的虚函数表的构建过程:
A:直接复制父类的虚函数表;
B:如果子类重写了父类的某个虚函数,那么就在这个虚函数表中进行相应的替换;
C:如果子类增加了新的虚函数,就把这个虚函数添加到虚函数表中(在尾部添加);

虚函数表的实现

#include <iostream>
using namespace std;
class Father {
public:
	//在类函数前加 "virtual"后,这个函数就变成了虚函数
	virtual void fun1() { cout << "FUN1,打篮球" << endl; }
	virtual void fun2() { cout << "FUN2,打游戏" << endl; }
	void fun3() { cout << "非虚函数FUN3" << endl; }
public:
	int x = 20;
	int y = 50;
	static int z;
};
int Father::z = 0;//静态成员类外初始化


#include "Father.h"

typedef void (*FUN8_t)(void);//函数指针

int main(void) {
	Father father1;

	//先查看下这个类的对象占多少空间
	cout << "father1对象占几个字节:" << sizeof(father1) << endl;
	//查看对象的地址
	cout << "father1对象的地址:" << &father1 << endl;
	//使用指针获取虚函数地址
	int* vptr = (int*)*(int*)&father1;
	cout << "虚函数表指针vptr的地址:" << vptr << endl;
	//调用第一个虚函数
	cout << "调用第一个虚函数:" << endl;
	((FUN8_t)* (vptr))();//类似于void fun1()这个函数
	//调用第二个虚函数
	cout << "调用第二个虚函数:" << endl;
	((FUN8_t)* (vptr+1))();

	//查看数据成员的地址
	cout << "第一个数据成员的地址:\n" << &father1.x << endl;
	cout << std::hex<<(int)&father1 + 4 << endl;//以16进制打印这个地址
	//打印第一个函数成员的数据
	cout << std::dec<< father1.x << endl;//以10进制打印数据
	cout << *(int*)((int)&father1 + 4) << endl;
	//打印第二个数据成员的数据
	cout << std::dec << father1.y << endl;//以10进制打印数据
	cout << *(int*)((int)&father1 + 8) << endl;

	Father father2;
	//只要是这个类的对象在使用虚函数时都是指向同一个地址的虚函数地址
	cout << "father1的虚函数表:" << *(int*)((int)&father1) << endl;
	cout << "father2的虚函数表:" << *(int*)((int)&father2) << endl;


	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述
final: 用来修饰类,使该类不能被继承; 用来修饰类的虚函数,使该虚函数在子类中,不能 被重写; (例: virtual void function() final;)

override: (只能用于修饰虚函数), 提示程序的阅读者这个函数是重写父类的功能; 防止程序员在重写父类的函数时,把函数名写错;例:(void function()override;),如果父类中没有这个函数就会报错!!!
注意: override只需在函数声明中使用,不需要在函数的实现中使用。

注意:
为了防止内存泄露,最好是在基类析构函数上添加virtual关键字,使基类析构函数为虚函数
目的在于,当使用delete释放基类指针时,会实现动态的析构:
如果基类指针指向的是基类对象,那么只调用基类的析构函数
如果基类指针指向的是子类对象,那么先调用子类的析构函数,再调用父类的析构函数;

纯虚函数:
某些类,在现实角度和项目实现角度,都不需要实例化(不需要创建它的对象),
这个类中定义的某些成员函数,只是为了提供一个形式上的接口,准备让子类来做具体的实现。
此时,这个方法,就可以定义为“纯虚函数”, 包含纯虚函数的类,就称为抽象类。
用法:纯虚函数,使用virtual和 =0;
例: virtual void test()=0;//不用做具体的实现

注意事项:
父类声明某纯虚函数后,那么它的子类,
1)要么实现这个纯虚函数 (最常见);
2)要么继续把这个纯虚函数声明为纯虚函数,这个子类也成为抽象类;
3) 要么不对这个纯虚函数做任何处理,等效于上一种情况(该方式不推荐);

多态模拟实现一个通信项目:

#pragma once
#pragma once
#include <string>

#define ODU_TYPE_331_FLAG	"331"
#define	ODU_TYPE_335_FLAG	"335"

enum class ODU_TYPE {
	ODU_TYPE_331,
	ODU_TYPE_335,
	ODU_TYPE_UNKNOWN
};

// ODU类,用于处理老型号ODU331设备
class ODU
{
public:
	ODU();
	virtual int getTxFre(); //获取发射频率
	virtual bool setTxFre(int); //设置发射频率

	virtual int getRxFre(); //获取接收频率
	virtual bool setRxFre(int); //获取接收频率

	virtual float getTxPower(); //获取发射功率
	virtual bool setTxPower(float);  //设置发射功率

	virtual float getRxL(); //获取接收电平

	virtual bool heartBeat(); //心跳包
	virtual std::string name(); //获取该设备的名称

	ODU_TYPE getODUType(); //获取当前ODU的类型
protected:
	int txFre; //发射频率
	int rxFre; //接收频率
	float txPower; //发射功率
	float rxL; //接收电平

	ODU_TYPE type;
};

#include "ODU.h"
#include <iostream>
#include <string>

using namespace std;

ODU::ODU()
{
	txFre = 34400;
	rxFre = 31100;
	txPower = 20;
	rxL = 0;
	type = ODU_TYPE::ODU_TYPE_331;

	cout << "调用ODU()" << endl;
}

int ODU::getTxFre()
{
	return txFre;
}

bool ODU::setTxFre(int frequence)
{
	txFre = frequence;
	cout << name() << "发射频率已经设置为" << txFre << "Hz" << endl;
	return true;
}

int ODU::getRxFre()
{
	return rxFre;
}

bool ODU::setRxFre(int frequence)
{
	rxFre = frequence;
	cout << name() << "接收频率已经设置为" << rxFre << "Hz" << endl;
	return true;
}

float ODU::getTxPower()
{
	return txPower;
}

bool ODU::setTxPower(float power)
{
	txPower = power;
	cout << name() << "发射功率已经设置为" << txPower << "dBm" << endl;
	return true;
}

float ODU::getRxL()
{
	return rxL;
}

bool ODU::heartBeat()
{
	cout << name() << "模拟串口协议读取数据:获取心跳包的反馈...【"
		<< ODU_TYPE_331_FLAG << "】";

	string response;
	cin >> response;
	bool ret = false;

	if (response == ODU_TYPE_331_FLAG) {
		type = ODU_TYPE::ODU_TYPE_331;
		ret = true;
	}

	return ret;
}

std::string ODU::name()
{
	string ret;
	switch (type) {
	case ODU_TYPE::ODU_TYPE_331:
		ret = "ODU331";
		break;
	case ODU_TYPE::ODU_TYPE_335:
		ret = "ODU335";
		break;
	case ODU_TYPE::ODU_TYPE_UNKNOWN:
	default:
		ret = "ODU_UNKNOWN";
		break;
	}
	return ret;
}

ODU_TYPE ODU::getODUType()
{
	return type;
}
#pragma once
#pragma once
#include "ODU.h"
class ODU35 :
	public ODU
{
public:
	ODU35();
	bool heartBeat();
};

#include "ODU35.h"
#include <iostream>

using namespace std;

ODU35::ODU35()
{
	cout << "调用ODU335()" << endl;
	type = ODU_TYPE::ODU_TYPE_335;
}

bool ODU35::heartBeat()
{
	cout << name() << "模拟串口协议读取数据:获取心跳包的反馈...【"
		<< ODU_TYPE_335_FLAG << "】";

	string response;
	cin >> response;
	bool ret = false;

	if (response == ODU_TYPE_335_FLAG) {
		type = ODU_TYPE::ODU_TYPE_335;
		ret = true;
	}

	return ret;
}
#include <iostream>
#include <Windows.h>
#include <thread>
#include "ODU.h"
#include "ODU35.h"

using namespace std;

ODU* odu = NULL;

void test(ODU* odu) {
	odu->setTxFre(35588);
	cout << odu->getTxFre() << endl;
	odu->heartBeat();
}

void oduMonitorHandler() {
	while (1) {
		if (odu->heartBeat() == false) {
			// 切换ODU
			ODU_TYPE type = odu->getODUType();
			switch (type) {
			case ODU_TYPE::ODU_TYPE_331:
				delete odu;
				odu = new ODU35;
				break;
			case ODU_TYPE::ODU_TYPE_335:
				delete odu;
				odu = new ODU;
				break;
			default:
				odu = NULL;
				return;
			}
		}

		Sleep(1000);
	}
}

int main(void) {
	odu = new ODU();

	// 创建一个线程,用于对ODU进行监测
	std::thread oduMonitor(oduMonitorHandler);

	// 主线程等待线程oduMonitor的结束
	oduMonitor.join();

	return 0;
}

注意:
1.虚函数的函数原型
子类在重新实现继承的虚函数时,要和主要函数的原型一致如果已经继承虚函数:
bool heartBeat();

那么重写虚函数时,函数原型必须保持完全一致:
bool heartBeat();

而且子类不能添加:
int heartBeat();

//因为仅函数的返回类型不同时,不能区别两个函数。
但是可以添加:
int heartBeat(int);

2.析构函数是否使用虚函数
有子类时,析构函数就应该使用虚函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值