多态(C++)

目录

一、什么是多态?

1.介绍

2.语法

1. 虚函数的声明

2. 虚函数的定义

3. 多态的调用

4. 纯虚函数

5. 动态类型识别

3.虚函数特点

4.举例

二、案例

1.计算器 

a.普通实现

b.多态实现

2.制作饮品

a.编写基础功能

b.制作咖啡

c.制作茶叶

d.完整代码 

3.组装电脑 

a.零件和电脑类

b.具体零件厂商

1.Inter厂商

2.Lenovo厂商

c.实现函数

1.运行函数

2.调用函数

3. 析构函数

4.完整代码 

三、虚析构

1.问题

2.解决 


一、什么是多态?

1.介绍

      多态,这个词听起来就像是某个超级英雄有多个形态一样,能在需要的时候变成任何东西!不过,在我们编程的世界里,多态并没有那么神奇,但它确实让我们的代码变得更加灵活多变。想象一下,你有一个动物园,里面有各种各样的动物,比如狮子、大象和企鹅。现在,你想让这些动物都做出“叫”这个动作。但是,每种动物的叫声都是不同的,对吧?狮子会吼叫,大象会鸣叫,而企鹅则会唧唧叫。

      如果没有多态,你可能需要为每种动物编写一个不同的“叫”函数,然后在需要的时候调用对应的函数。但是,这样做不仅代码量庞大,而且维护起来也非常麻烦。每次添加一种新动物,你都需要编写一个新的“叫”函数,并且确保在正确的地方调用它。

      现在,让我们来看看多态是如何拯救这个混乱的动物园的!

      通过多态,你可以定义一个通用的“动物”类,并在其中包含一个虚函数“叫”。然后,你可以为每种具体的动物创建一个继承自“动物”类的子类,并在子类中实现自己的“叫”函数。这样,当你调用一个动物的“叫”函数时,程序会自动根据动物的实际类型来调用正确的函数。

      这就像是在动物园里安装了一个“叫声转换器”。不管哪种动物走过来,只要按下“叫”按钮,转换器就会自动识别动物类型,并播放出对应的叫声。真是太神奇了!

      所以,多态就像是编程世界里的超级英雄,让我们的代码变得更加简洁、灵活和易于维护。现在,你可以放心地添加更多种类的动物到你的动物园里,而不用担心叫声的问题了! 

2.语法

      多态允许你以统一的方式处理不同类型的对象,即使这些对象具有不同的实现。多态的语法主要涉及虚函数的声明、定义和调用

1. 虚函数的声明

在基类中,使用virtual关键字声明一个函数为虚函数。这告诉编译器,该函数可能会在派生类中被重写(Override)。

class Base {  
public:  
    virtual void func() { /* 基类实现 */ }  
};

2. 虚函数的定义

在派生类中,你可以重写基类中的虚函数。重写意味着派生类提供一个与基类虚函数具有相同签名(函数名和参数列表)的函数。通常,你还会使用override关键字来指示这是重写基类中的虚函数,尽管这不是强制性的,但它可以帮助编译器检查你的意图是否正确。

class Derived : public Base {  
public:  
    void func() override { /* 派生类实现 */ }  
};

3. 多态的调用

多态的调用通常通过基类指针或引用来实现。当你通过基类指针或引用调用一个虚函数时,实际调用的是对象实际类型所对应的实现,而不是指针或引用类型的实现。这就是动态绑定或运行时多态。

Base* ptr = new Derived();  
ptr->func(); // 调用的是Derived类的func实现,而不是Base类的

4. 纯虚函数

纯虚函数是一种特殊的虚函数,它在基类中没有实现,并且要求任何派生类都必须提供实现。包含纯虚函数的类被称为抽象类,不能被实例化。纯虚函数在类定义中以= 0结尾。

class AbstractBase {  
public:  
    virtual void pureVirtualFunc() = 0; // 纯虚函数  
};

5. 动态类型识别

有时,你可能需要在运行时确定对象的实际类型。这可以通过typeid操作符或dynamic_cast操作符来实现。

  • typeid操作符返回一个type_info对象,该对象包含了关于对象类型的信息。
    Base* ptr = new Derived();  
    cout << typeid(*ptr).name() << endl; // 输出Derived的类型信息
  • dynamic_cast操作符用于在类层次结构中进行安全的向下和侧向类型转换
    Base* basePtr = new Derived();  
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);  
    if (derivedPtr != nullptr) {  
        // 安全的转换,可以使用derivedPtr  
    }

3.虚函数特点

a.声明方式:在基类的成员函数声明前加上virtual关键字。

b.重写:派生类可以重写(覆盖)基类中的虚函数。重写时,函数签名(函数名、参数列表)必须相同。

c.动态绑定:当使用基类指针或引用调用虚函数时,程序会在运行时根据对象的实际类型来决定调用哪个实现。这称为动态绑定或运行时多态。

d.默认实现:基类中的虚函数必须有定义(实现),除非它被声明为纯虚函数。

e.访问控制:虚函数的访问控制修饰符(如publicprotectedprivate)在基类和派生类中必须保持一致或更为宽松。

f.构造函数和析构函数:构造函数和析构函数不能是虚函数,但它们可以调用虚函数。在构造或析构过程中,对象的类型是已知的,因此虚函数机制在这种情况下不适用。

4.举例

       在有继承关系的类之间,通过虚函数和动态绑定,使得基类指针或引用可以调用派生类的函数,实现不同类型对象对同一消息的不同响应。

      Cat和Dog是Animal的子类,他们之间有继承关系。

      在下面例子中,Animal是一个抽象类,因为它包含了一个虚函数speak。Cat和Dog是Animal的子类,并提供了speak的实现。通过Animal类型的指针Cat,我们可以调用Cat对象的speak方法,实现多态。在运行时,根据Animal &animal实际指向的对象类型(Dog),动态地绑定了speak的调用。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;

class Animal
{
public:
	//虚函数
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

//猫类
class Cat :public Animal
{
public:
	//重写 函数返回值类型 函数名 参数列表 完全相同 
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

//狗类
class Dog :public Animal
{
public:
	void speak()
	{
		cout << "小狗在说话" << endl;
	}
};

//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,
//需要在运行阶段进行绑定,地址晚绑定

//动态多态满足条件
//1、得有继承关系
//2、子类重写父类的虚函数

//动态多态使用
//父类的指针或者引用 执行子类对象
void doSpeak(Animal& animal)//Animal &animal = cat;
{
	animal.speak();
}

void test01()
{
	Cat cat;
	doSpeak(cat);

	Dog dog;
	doSpeak(dog);
}

int main()
{
	test01();
	return 0;
}

二、案例

1.计算器 

a.普通实现

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;

//普通写法

class Calculator
{
public:

	int getResult(string oper)
	{
		if (oper == "+")
		{
			return m_Num1 + m_Num2;
		}
		else if (oper == "-")
		{
			return m_Num1 - m_Num2;
		}
		else if (oper == "*")
		{
			return m_Num1 * m_Num2;
		}
	}
	
	int m_Num1;//操作数1
	int m_Num2;//操作数2
};

void test01()
{
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 20;

	cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
	cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
	cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}

缺点:

     1).如果想扩展新的功能,需要修改源码
     2).在真实开发中 提倡 开闭原则
     3).开闭原则:对外扩展进行开放,对修改进行关闭 

b.多态实现

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;

class AbstractCalculator
{
public:
	virtual int getResult()
	{
		return 0;
	}

	int m_Num1;
	int m_Num2;
};

//加法计算器类
class AddCalculator :public AbstractCalculator
{
public:
	virtual int getResult()
	{
		return m_Num1 + m_Num2;
	}
};

//减法计算器类
class SubCalculator :public AbstractCalculator
{
public:
	virtual int getResult()
	{
		return m_Num1 - m_Num2;
	}
};

//加法计算器类
class MulCalculator :public AbstractCalculator
{
public:
	virtual int getResult()
	{
		return m_Num1 * m_Num2;
	}
};

void test02()
{
	//多态使用条件
	//父类指针或者引用指向子类对象

	//加法运算
	AbstractCalculator* abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;

	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	//用完后要释放
	delete abc;

	//减法运算
	abc = new SubCalculator;
	abc->m_Num1 = 100;
	abc->m_Num2 = 100;

	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;

	//乘法
	abc = new MulCalculator;
	abc->m_Num1 = 100;
	abc->m_Num2 = 100;

	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;
}
int main()
{
	//test01();

	test02();
	return 0;
}

      虽然代码量增多,但可拓展性大大增强,添加新功能更加方便。

优点: 

       1).组织结构清晰
       2).可读性强
       3).对于前期和后期扩展及维护

2.制作饮品

a.编写基础功能

//饮品基础步骤
class AbstractDrinking
{
public:

	//煮水
	virtual void Boil() = 0;

	//冲泡
	virtual void Brew() = 0;

	//倒入杯中
	virtual void PourInCup() = 0;

	//加入辅料
	virtual void PutSomething() = 0;

	//制作饮品
	void makeDrink()
	{
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};

//制作函数
void doWork(AbstractDrinking* abs)
{
	abs->makeDrink();
	delete abs;
}

void test01()
{
	//制作咖啡/茶叶
	doWork(new Coffee);
}

int main()
{
	test01();
	return 0;
}

int main()
{
	return 0;
}

b.制作咖啡

class Coffee :public AbstractDrinking
{
public:
	//煮水
	virtual void Boil()
	{
		cout << "煮农夫山泉" << endl;
	}
	//冲泡
	virtual void Brew()
	{
		cout << "冲泡咖啡" << endl;
	}

	//倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入杯中" << endl;
	}

	//加入辅料
	virtual void PutSomething()
	{
		cout << "加入糖和牛奶" << endl;
	}
};

c.制作茶叶

//制作茶叶
class Tea :public AbstractDrinking
{
public:
	//煮水
	virtual void Boil()
	{
		cout << "煮矿泉水" << endl;
	}
	//冲泡
	virtual void Brew()
	{
		cout << "冲泡茶叶" << endl;
	}

	//倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入杯中" << endl;
	}

	//加入辅料
	virtual void PutSomething()
	{
		cout << "加入枸杞" << endl;
	}
};

d.完整代码 

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;

class AbstractDrinking
{
public:

	//煮水
	virtual void Boil() = 0;

	//冲泡
	virtual void Brew() = 0;

	//倒入杯中
	virtual void PourInCup() = 0;

	//加入辅料
	virtual void PutSomething() = 0;

	//制作饮品
	void makeDrink()
	{
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};

//制作咖啡
class Coffee :public AbstractDrinking
{
public:
	//煮水
	virtual void Boil()
	{
		cout << "煮农夫山泉" << endl;
	}
	//冲泡
	virtual void Brew()
	{
		cout << "冲泡咖啡" << endl;
	}

	//倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入杯中" << endl;
	}

	//加入辅料
	virtual void PutSomething()
	{
		cout << "加入糖和牛奶" << endl;
	}
};

//制作茶叶
class Tea :public AbstractDrinking
{
public:
	//煮水
	virtual void Boil()
	{
		cout << "煮矿泉水" << endl;
	}
	//冲泡
	virtual void Brew()
	{
		cout << "冲泡茶叶" << endl;
	}

	//倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入杯中" << endl;
	}

	//加入辅料
	virtual void PutSomething()
	{
		cout << "加入枸杞" << endl;
	}
};
//制作函数
void doWork(AbstractDrinking* abs)
{
	abs->makeDrink();
	delete abs;
}

void test01()
{
	//制作咖啡
	doWork(new Coffee);
}

int main()
{
	test01();
	return 0;
}

3.组装电脑 

a.零件和电脑类

//零件类
class CPU //抽象CPU类
{
public:
	//抽象计算函数
	virtual void calculate() = 0;
};

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

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

//电脑类
class Computer
{
public:
	Computer(CPU* cpu, VideoCard* vc, Memory* mem)
	{
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}

	//提供工作的函数

private:
	CPU* m_cpu; //CPU的零件指针
	VideoCard* m_vc;//显卡零件指针
	Memory* m_mem;//内存条零件指针
};

int main()
{
	test01();
	return 0;
}

b.具体零件厂商

1.Inter厂商
class InterCpu : public CPU
{
	void calculate()
	{
		cout << "Inter的CPU开始计算了!" << endl;
	}
};

class InterVideoCard : public VideoCard
{
	void display()
	{
		cout << "Inter的VideoCard开始显示了!" << endl;
	}
};

class InterMemory : public Memory
{
	void storage()
	{
		cout << "Inter的Memory开始存储了!" << endl;
	}
};
2.Lenovo厂商
class LenovoCpu : public CPU
{
	void calculate()
	{
		cout << "Lenovo的CPU开始计算了!" << endl;
	}
};

class LenovoVideoCard : public VideoCard
{
	void display()
	{
		cout << "Lenovo的VideoCard开始显示了!" << endl;
	}
};

class LenovoMemory : public Memory
{
	void storage()
	{
		cout << "Lenovo的Memory开始存储了!" << endl;
	}
};

c.实现函数

1.运行函数
void test01()
{
	//第一台电脑零件
	CPU* interCpu = new InterCpu;
	VideoCard* interCard = new InterVideoCard;
	Memory* interMem = new InterMemory;

	//创建第一台电脑
	Computer* computer1 = new Computer(interCpu, interCard, interMem);
	computer1->work();
	delete computer1;

	cout << "-----------------------------" << endl;

	//第二台电脑
	Computer* computer2 = new Computer(new LenovoCpu, new LenovoVideoCard, new LenovoMemory);
	computer2->work();
	delete computer2;

	cout << "-----------------------------" << endl;
	//第三台电脑
	Computer* computer3 = new Computer(new LenovoCpu, new InterVideoCard, new InterMemory);
	computer3->work();
	delete computer3;
}
2.调用函数

      电脑类中添加调用函数

//提供工作的函数
void work()
{
	//让零件工作起来,调用
	m_cpu->calculate();

	m_vc->display();

	m_mem->storage();
}
3. 析构函数

      数据开辟在栈区,需要手动释放,以免造成内存泄漏。

//提供析构函数 释放3个电脑零件
~Computer()
{
	if (m_cpu != NULL)
	{
		delete m_cpu;
		m_cpu = NULL;
	}

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

	if (m_mem != NULL)
	{
		delete m_mem;
		m_mem = NULL;
	}
4.完整代码 
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;

//电脑案例

//零件类
class CPU //抽象CPU类
{
public:
	//抽象计算函数
	virtual void calculate() = 0;
};

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

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

//电脑类
class Computer
{
public:
	Computer(CPU* cpu, VideoCard* vc, Memory* mem)
	{
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}

	//提供工作的函数
	void work()
	{
		//让零件工作起来,调用
		m_cpu->calculate();

		m_vc->display();

		m_mem->storage();
	}

	//提供析构函数 释放3个电脑零件
	~Computer()
	{
		if (m_cpu != NULL)
		{
			delete m_cpu;
			m_cpu = NULL;
		}

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

		if (m_mem != NULL)
		{
			delete m_mem;
			m_mem = NULL;
		}
	}
private:
	CPU* m_cpu; //CPU的零件指针
	VideoCard* m_vc;//显卡零件指针
	Memory* m_mem;//内存条零件指针
};

//具体零件厂商

//Inter厂商
class InterCpu : public CPU
{
	void calculate()
	{
		cout << "Inter的CPU开始计算了!" << endl;
	}
};

class InterVideoCard : public VideoCard
{
	void display()
	{
		cout << "Inter的VideoCard开始显示了!" << endl;
	}
};

class InterMemory : public Memory
{
	void storage()
	{
		cout << "Inter的Memory开始存储了!" << endl;
	}
};

//Lenovo厂商
class LenovoCpu : public CPU
{
	void calculate()
	{
		cout << "Lenovo的CPU开始计算了!" << endl;
	}
};

class LenovoVideoCard : public VideoCard
{
	void display()
	{
		cout << "Lenovo的VideoCard开始显示了!" << endl;
	}
};

class LenovoMemory : public Memory
{
	void storage()
	{
		cout << "Lenovo的Memory开始存储了!" << endl;
	}
};

void test01()
{
	//第一台电脑零件
	CPU* interCpu = new InterCpu;
	VideoCard* interCard = new InterVideoCard;
	Memory* interMem = new InterMemory;

	//创建第一台电脑
	Computer* computer1 = new Computer(interCpu, interCard, interMem);
	computer1->work();
	delete computer1;

	cout << "-----------------------------" << endl;

	//第二台电脑
	Computer* computer2 = new Computer(new LenovoCpu, new LenovoVideoCard, new LenovoMemory);
	computer2->work();
	delete computer2;

	cout << "-----------------------------" << endl;
	//第三台电脑
	Computer* computer3 = new Computer(new LenovoCpu, new InterVideoCard, new InterMemory);
	computer3->work();
	delete computer3;
}
int main()
{
	test01();
	return 0;
}

三、虚析构

1.问题

      下面这个例子中,Cat类的析构函数没有被调用的原因是因为基类Animal的析构函数没有被声明为虚函数(virtual)。当使用基类指针删除派生类对象时,如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
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)
	{
		m_Name = new string(name);
	}

	virtual void speak()
	{

		cout << "Cat构造函数调用" << endl;
		cout << *m_Name << "小猫在说话" << endl;
	}

	~Cat()
	{
		if (m_Name != NULL)
		{
			cout << "Cat析构函数调用" << endl;
			m_Name = NULL;
		}
	}

	string* m_Name;
};

void test01()
{
	Animal* animal = new Cat("Tmo");
	animal->speak();
	delete animal;
}
int main()
{
	test01();

	return 0;
}

2.解决 

      将Animal类中的析构函数设为虚析构,即可解决子类析构不被调用的问题。

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)
	{
		m_Name =  new string(name);
	}

	virtual void speak()
	{

		cout << "Cat构造函数调用" << endl;
		cout <<*m_Name<<"小猫在说话" << endl;
	}

	~Cat()
	{
		if (m_Name != NULL)
		{
			cout << "Cat析构函数调用" << endl;
			m_Name = NULL;
		}
	}

	string* m_Name;
};

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

	re

       多态的有关知识暂时分享到这里,感谢观看。

 看到这里,不妨点个攒,关注一下吧!

最后,谢谢你的观看 

  • 22
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值