【C++基础】类和对象——多态

本文详细介绍了C++中的多态性,包括静态多态(函数重载和运算符重载)和动态多态(通过虚函数实现)。动态多态需要满足继承和子类重写父类虚函数的条件。文章通过实例展示了如何使用虚函数实现动态多态,以及如何通过抽象类和纯虚函数来设计接口。此外,还讨论了虚析构函数的重要性,特别是在处理子类中包含堆内存的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【C++基础】类和对象——多态

一、多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类

  • 静态多态:函数重载 和 运算符重载 属于静态多态,复用函数名
  • 动态多态:派生类 和 虚函数 实现运行时多态

静态多态 和 动态多态 的区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

动态多态满足条件

  1. 有继承关系
  2. 子类重写父类的虚函数

重写:函数返回值类型函数名参数列表 完全一致称为重写

动态多态使用

  • 父类的指针或引用 , 指向子类对象
示例:
静态多态引发的问题:
#include<iostream>
using namespace std;

//基类
class Animal
{
public:
	//添加virtual关键字,使成为虚函数,虚函数 为动态多态 晚绑定
	void show()
	{
		cout << "这是 动物。" << endl;
	}
};

//派生类
class Cat :public Animal
{
public:
	void show()
	{
		cout << "这是 猫。" << endl;
	}
};

//基类引用可以接收派生类对象
//地址早绑定,在编译阶段确定函数地址
void doShow(Animal& animal)	
{
	animal.show();
}

int main()
{
	Cat cat;
	//父类的指针或引用  , 指向子类对象
	doShow(cat);	//Animal & animal = cat;	
}

输出:

在这里插入图片描述
想要的结果是调用猫的show()函数。
输出出错的原因如下:
在这里插入图片描述
因为地址早绑定,所以调用的是基类的show()函数。

解决方案:
在这里插入图片描述
完整代码如下:

#include<iostream>
using namespace std;

//基类
class Animal
{
public:
	//添加virtual关键字,使成为虚函数,虚函数 为动态多态 晚绑定
	virtual void show()
	{
		cout << "这是 动物。" << endl;
	}
};

//动态多态满足条件
//1. 有继承关系
//2. 子类重写父类的虚函数
//派生类
class Cat :public Animal
{
public:
	void show()
	{
		cout << "这是 猫。" << endl;
	}
};

//基类引用可以接收派生类对象
//地址早绑定,在编译阶段确定函数地址
void doShow(Animal& animal)
{
	animal.show();
}

int main()
{
	Cat cat;
	doShow(cat);
}

输出:
在这里插入图片描述

二、案例一:计算器实现

在C++中提倡使用多态

示例:
#include<iostream>
using namespace std;

//利用多态实现计算器的好处:
//1、组织结构清晰
//2、可读性强
//3、对于前期和后期扩展以及维护性高

//实现计算机抽象类
class AbstractCalculator
{
public:
	
	virtual int getResult()
	{
		return 0;
	}

	int num1;
	int num2;
};

//加法计算器
class AddCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return num1 + num2;
	}
};



int main()
{
	//new 在堆区开辟空间,返回地址
	AbstractCalculator* ac = new AddCalculator;
	ac->num1 = 1;
	ac->num2 = 2;
	
	cout << "ac->getResult = " << ac->getResult() << endl;
	//别忘了释放空间
	delete ac;
}

三、纯虚函数和抽象类

在多态中,通常父类中的虚函数的实现是无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 ( 参数列表 ) = 0 ;

当类中有了纯虚函数,这个类就称为 抽象类

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

无法实例化对象:
在这里插入图片描述

子类必须重写抽象类中的纯虚函数,否则也属于抽象类:
在这里插入图片描述

示例:
#include<iostream>
using namespace std;

//利用多态实现计算器的好处:
//1、组织结构清晰
//2、可读性强
//3、对于前期和后期扩展以及维护性高

class Base
{
public:
	//纯虚函数
	//只要有一个纯虚函数,这个类就称为抽象类
	//抽象类特点:
	//	1、无法实例化对象
	//	2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类
	virtual void func() = 0;

};

class Son :public Base
{
	//c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。
	//因此,在子类从新声明该虚函数时,可以加,也可以不加,但习惯上每一层声明函数时都加virtual,使程序更加清晰。
	virtual void func()
	{
		cout << "func函数" << endl;
	}
};


int main()
{
	Base* b = new Son;
	b->func();

	delete b;
}

四、多态案例二:制作饮品

#include<iostream>
using namespace std;

class AbstractDrink
{
public:
	virtual void Boil() = 0;
	virtual void Brew() = 0;
	virtual void PourInCup() = 0;
	virtual void PutSomething() = 0;

	void makeDrink()
	{
		this->Boil();
		this->Brew();
		this->PourInCup();
		this->PutSomething();
	}
};

class Coffee :public AbstractDrink
{
public:
	virtual void Boil()
	{
		cout << "加入自来水" << endl;
	}
	virtual void Brew()
	{
		cout << "地上捡的咖啡" << endl;
	}
	virtual void PourInCup()
	{
		cout << "WC里捡的杯子" << endl;
	}
	virtual void PutSomething()
	{
		cout << "加芥末,不要糖" << endl;
	}
};

void doWork(AbstractDrink* ad) 
{
	ad->makeDrink();
	delete ad;
}

int main()
{
	doWork(new Coffee);
}

五、虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:virtual ~类名(){}

纯虚析构语法(既要声明,也要实现):
声明virtual ~类名() = 0; ,实现类名::~类名(){}

问题:父类指针在释放时无法调用到子类的析构代码

示例:
#include<iostream>
using namespace std;

class Animal
{
public:
	Animal()
	{
		cout << "Animal()调用" << endl;
	}

	~Animal()
	{
		cout << "~Animal()调用" << endl;
	}

	//纯虚函数
	virtual void speak() = 0;
};

class Cat :public Animal
{
public:
	Cat(string name)
	{
		cout << "Cat()调用" << endl;
		c_Name = new string(name);
	}

	virtual void speak()
	{
		cout << *c_Name << "的 猫··在说话" << endl;
	}

	string* c_Name;

	~Cat()
	{
		if (c_Name != NULL)
		{
			cout << "~Cat()调用" << endl;
			delete c_Name;
			c_Name = NULL;
		}
	}
};

void test1()
{
	Animal* a = new Cat("张三");
	a->speak();
	//父类指针在析构时,不会调用子类中的析构函数
	//这导致子类如果有堆区属性,会出现内存泄露
	delete a;
}

int main()
{
	test1();
}

输出:
Cat类的析构函数没有调用,说明c_Name对象没有被释放
在这里插入图片描述

将父类中的析构函数改为虚析构或者纯虚析构

注:

  1. 纯虚析构,需要声明,也需要实现
  2. 有了纯虚析构之后,这个类也属于抽象类,无法实例化
  3. 一般在子类含有堆区数据时,才需要用到虚析构
示例:
#include<iostream>
using namespace std;

class Animal
{
public:
	Animal()
	{
		cout << "Animal()调用" << endl;
	}

	//将父类中的析构函数改为虚析构或者纯虚析构
	//虚析构
	/*virtual ~Animal()	
	{
		cout << "~Animal()调用" << endl;
	}*/

	//纯虚析构,需要声明,也需要实现
	//有了纯虚析构之后,这个类也属于抽象类,无法实例化
	virtual ~Animal() = 0;	

	//纯虚函数
	virtual void speak() = 0;
};

Animal::~Animal()
{
	cout << "~Animal()调用" << endl;
}


class Cat :public Animal
{
public:
	Cat(string name)
	{
		cout << "Cat()调用" << endl;
		c_Name = new string(name);
	}

	virtual void speak()
	{
		cout << *c_Name << "的 猫··在说话" << endl;
	}

	string* c_Name;

	~Cat()
	{
		if (c_Name != NULL)
		{
			cout << "~Cat()调用" << endl;
			delete c_Name;
			c_Name = NULL;
		}
	}
};

void test1()
{
	Animal* a = new Cat("张三");
	a->speak();
	//父类指针在析构时,不会调用子类中的析构函数
	//这导致子类如果有堆区属性,会出现内存泄露
	delete a;
}

int main()
{
	test1();
}

输出:
在这里插入图片描述

总结:

  1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  3. 拥有纯虚析构函数的类也属于抽象类

六、多态案例三:电脑组装

#include<iostream>
using namespace std;

//抽象出CPU,内存,显卡
//抽象CPU类
class CPU
{
public:
	//抽象的计算函数
	virtual void calculate() = 0;
};

//抽象VideoCard类
class VideoCard
{
public:
	//抽象的显示函数
	virtual void display() = 0;
};

//抽象内存条类
class Memory
{
public:
	//抽象的存储函数
	virtual void storage() = 0;
};

//电脑类
class Computer
{
public:
	Computer(CPU* cpu, VideoCard* videoCard, Memory* memory)
	{
		c_Cpu = cpu;
		c_VideoCard = videoCard;
		c_Memory = memory;
	}

	//提供工作函数
	void work()
	{
		c_Cpu->calculate();
		c_VideoCard->display();
		c_Memory->storage();
	}

	//提供析构函数,释放CPU,内存,显卡
	~Computer()
	{
		if (c_Cpu != NULL)
		{	
			delete c_Cpu;
			c_Cpu = NULL;
		}

		if (c_VideoCard != NULL)
		{
			delete c_VideoCard;
			c_VideoCard = NULL;
		}

		if (c_Memory != NULL)
		{
			delete c_Memory;
			c_Memory = NULL;
		}
	}

private:
	CPU* c_Cpu;
	VideoCard* c_VideoCard;
	Memory* c_Memory;
};

//具体厂商
//Intel厂商
class IntelMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Intel的 Memory" << endl;
	}
};

class IntelVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Intel的 VideoCard" << endl;
	}
};

class IntelCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Intel的 CPU" << endl;
	}
};

//Lenovo厂商
class LenovoMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Lenovo的 Memory" << endl;
	}
};

class LenovoVideoCard :public VideoCard
{
public:
	virtual void display()
	{
		cout << "Lenovo的 VideoCard" << endl;
	}
};

class LenovoCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Lenovo的 CPU" << endl;
	}
};

void myComputer()
{
	CPU* ic = new IntelCPU;
	VideoCard* iv = new IntelVideoCard;
	Memory* lm = new LenovoMemory;

	Computer* computer = new Computer(ic,iv,lm);
	computer->work();
	delete computer;
}

int main()
{
	myComputer();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值