C++ 多态

引例:

cpp 复制代码
#include<iostream>
using namespace std;
class Animal
{
public:
	void speak()
	{
		cout<<"动物在说话"<<endl;
	}
};
class Cat:public Animal
{
public:
	void speak()
	{
		cout<<"小猫在说话"<<endl;
	}
};
void DoSpeak(Animal &animal)
{
	animal.speak();
}
void test01()
{ 
	Cat cat;
	DoSpeak(cat);
}
int main()
{
	test01();
}

这段代码会显示动物在说话,但函数中的本意是想显示小猫在说话。

因为

void DoSpeak(Animal &animal)

的地址在编译阶段已经被绑定了,

如果想执行小猫在说话,那么就要使用动态多态的技术,使函数的地址在运行时绑定。

零、什么是多态?

多态是面向对象编程中的一个概念,指同一个方法或操作可以被不同的对象调用,产生不同的结果。也可以理解为同一个接口,不同的实现方式。

在多态的概念中,通过继承,子类可以重写父类的方法,从而实现多态。例如,一个父类有一个某方法,子类可以继承该父类,并重写该方法,从而实现不同的行为。

多态的好处在于,可以增强代码的灵活性和可扩展性,让代码更加面向对象。

一、满足动态多态的条件:

1.有继承关系

2.子类重写父类的虚函数(重写:函数除了函数体,函数头一模一样)

二 、动态多态的使用:

父类的指针或引用 执行子类对象

父类中的被重写函数前面加上virtual,变成虚函数。

例如:void DoSpeak(Animal &animal) 中的 Animal &animal

cpp 复制代码
#include<iostream>
using namespace std;
class Animal
{
public:
	virtual void speak()//虚函数
	{
		cout<<"动物在说话"<<endl;
	}
};
class Cat:public Animal
{
public:
	void speak()
	{
		cout<<"小猫在说话"<<endl;
	}
};
void DoSpeak(Animal &animal)
{
	animal.speak();
}
void test01()
{ 
	Cat cat;
	DoSpeak(cat);
}
int main()
{
	test01();
}

三、多态的原理

cpp 复制代码
#include<iostream>
using namespace std;
class Animal
{
public:
	void speak()
	{
		cout<<"动物在说话"<<endl;
	}
};
class Cat:public Animal
{
public:
	void speak()
	{
		cout<<"小猫在说话"<<endl;
	}
};
void DoSpeak(Animal &animal)
{
	animal.speak();
}
void test01()
{ 
	Cat cat;
	DoSpeak(cat);
}
void test02()
{
	cout<<"sizeof(Animal) = "<<sizeof(Animal)<<endl;
}
int main()
{
	//test01();
	test02();
}

父类中的speak没有变成虚函数前,父类的大小时1字节,也就是一个空类

cpp 复制代码
#include<iostream>
using namespace std;
class Animal
{
public:
	void speak()
	{
		cout<<"动物在说话"<<endl;
	}
};
class Cat:public Animal
{
public:
	void speak()
	{
		cout<<"小猫在说话"<<endl;
	}
};
void DoSpeak(Animal &animal)
{
	animal.speak();
}
void test01()
{ 
	Cat cat;
	DoSpeak(cat);
}
void test02()
{
	cout<<"sizeof(Animal) = "<<sizeof(Animal)<<endl;
}
int main()
{
	//test01();
	test02();
}

加了virtual变成虚函数后,父类大小是8字节,多了一个指针。

cpp 复制代码
#include<iostream>
using namespace std;
class Animal
{
public:
	virtual void speak()
	{
		cout<<"动物在说话"<<endl;
	}
};
class Cat:public Animal
{
public:
	void speak()
	{
		cout<<"小猫在说话"<<endl;
	}
};
void DoSpeak(Animal &animal)
{
	animal.speak();
}
void test01()
{ 
	Cat cat;
	DoSpeak(cat);
}
void test02()
{
	cout<<"sizeof(Animal) = "<<sizeof(Animal)<<endl;
}
int main()
{
	//test01();
	test02();
}

有虚函数的类会包含一个虚函数指针vfptr

vfptr会指向一个vftable(虚函数表),vftalble中会存放该虚函数的地址。子类中重写虚函数时会将子类的vftable中的地址覆盖为子类中的虚函数地址。

四、多态案例(计算器)

不用多态的版本:

cpp 复制代码
#include<iostream>
using namespace std;
class Calculator
{
public:
	int getResult(string oper)
	{
		if(oper=="+")
			return m_Nums1+m_Nums2;
		else if(oper=="-")
			return m_Nums1-m_Nums2;
		else if(oper=="*")
			return m_Nums1*m_Nums2;
	}
	int m_Nums1;
	int m_Nums2;
};

void test01()
{ 
	Calculator c;
	c.m_Nums1=10;
	c.m_Nums2=10;
	cout<<c.m_Nums1<<"+"<<c.m_Nums2<<"="<<c.getResult("+")<<endl;
	cout<<c.m_Nums1<<"-"<<c.m_Nums2<<"="<<c.getResult("-")<<endl;
	cout<<c.m_Nums1<<"*"<<c.m_Nums2<<"="<<c.getResult("*")<<endl;
}
int main()
{
	test01();
}

如果想要对计算器的操作方式有拓展,需要修改源码。

真正开发中提倡 "开闭原则"

对拓展进行开放,对修改进行关闭

用多态的版本:

cpp 复制代码
#include<iostream>
using namespace std;
class AbstractCalculator
{
public:
	virtual int getResult()
	{
		return 0;
	}
	int m_Nums1;
	int m_Nums2;
};
class AddCalculator:public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Nums1+m_Nums2;
	}
};
class SubCalculator:public AbstractCalculator
{
public:
	int getResult()
{
	return m_Nums1-m_Nums2;
}
};
class MulCalculator:public AbstractCalculator
{
public:
	int getResult()
{
	return m_Nums1*m_Nums2;
}
};
void test01()
{ 
	AbstractCalculator *p=new AddCalculator;
	p->m_Nums1=100;
	p->m_Nums2=100;
	cout<<p->m_Nums1<<"+"<<p->m_Nums2<<"="<<p->getResult()<<endl;
	delete p;
	p=new SubCalculator;
	p->m_Nums1=100;
	p->m_Nums2=100;
	cout<<p->m_Nums1<<"*"<<p->m_Nums2<<"="<<p->getResult()<<endl;
	delete p;
	p=new MulCalculator;
	p->m_Nums1=100;
	p->m_Nums2=100;
	cout<<p->m_Nums1<<"*"<<p->m_Nums2<<"="<<p->getResult()<<endl;
	delete p;
}
int main()
{
	test01();
}

五、纯虚函数与抽象类

六、多态案例(制作饮品)

cpp 复制代码
#include<iostream>
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);
	cout<<"------------------"<<endl;
	doWork(new tea);
}
int main()
{
	test01();
}

七、虚析构与纯虚析构

父类指针在析构时候,不会调用子类中的析构函数,导致子类如果右堆区属性,出现内存泄露

cpp 复制代码
#include<iostream>
using namespace std;
class Animal
{
public:
	Animal()
	{
		cout<<"Animal的构造函数调用"<<endl;
	}
	virtual void speak()=0;
	~Animal()
	{
		cout<<"Animal的析构函数调用"<<endl;
	}
};
class Cat:public Animal
{
public:
	Cat(string name)
	{
		cout<<"Cat的构造函数调用"<<endl;
		m_Name=new string(name);
	}
	void speak()
	{
		cout<<*m_Name<<"小猫在说话"<<endl;
	}
	~Cat()
	{
		if(m_Name!=nullptr)
		{
			cout<<"Cat的析构函数调用"<<endl;
			delete m_Name;
			m_Name=nullptr;
		}
	}
	string *m_Name;
};
void test01()
{
	Animal *animal=new Cat("tom");
	animal->speak();
	delete animal;
}
int main()
{
	test01();
}

在父类的析构函数前加上virtual,就可以解决问题。

纯虚析构:virtual ~Animal()=0;

类外

Animal::~Animal()

{

cout<<"Animal纯虚析构函数调用"<<endl;

}

纯虚析构必须要有具体的函数实现

cpp 复制代码
#include<iostream>
using namespace std;
class Animal
{
public:
	Animal()
	{
		cout<<"Animal的构造函数调用"<<endl;
	}
	virtual void speak()=0;
	/*virtual~Animal()
	  {
		  cout<<"Animal的虚析构函数调用"<<endl;
	  }
	*/
	virtual~Animal()=0;
};
Animal::~Animal()
{
	cout<<"Animal纯虚析构函数调用"<<endl;
}
class Cat:public Animal
{
public:
	Cat(string name)
	{
		cout<<"Cat的构造函数调用"<<endl;
		m_Name=new string(name);
	}
	void speak()
	{
		cout<<*m_Name<<"小猫在说话"<<endl;
	}
	~Cat()
	{
		if(m_Name!=nullptr)
		{
			cout<<"Cat的析构函数调用"<<endl;
			delete m_Name;
			m_Name=nullptr;
		}
	}
	string *m_Name;
};
void test01()
{
	Animal *animal=new Cat("tom");
	animal->speak();
	delete animal;
}
int main()
{
	test01();
}
相关推荐
螺旋天光极锐斩空闪壹式!7 分钟前
自制游戏:监狱逃亡
c++·游戏
Thomas_Cai8 分钟前
Python后端flask框架接收zip压缩包方法
开发语言·python·flask
霍先生的虚拟宇宙网络10 分钟前
webp 网页如何录屏?
开发语言·前端·javascript
温吞-ing12 分钟前
第十章JavaScript的应用
开发语言·javascript·ecmascript
魔道不误砍柴功18 分钟前
实际开发中的协变与逆变案例:数据处理流水线
java·开发语言
鲤籽鲲26 分钟前
C# MethodTimer.Fody 使用详解
开发语言·c#·mfc
亚图跨际30 分钟前
Python和R荧光分光光度法
开发语言·python·r语言·荧光分光光度法
Rverdoser38 分钟前
RabbitMQ的基本概念和入门
开发语言·后端·ruby
dj244294570741 分钟前
JAVA中的Lamda表达式
java·开发语言
工业3D_大熊1 小时前
3D可视化引擎HOOPS Luminate场景图详解:形状的创建、销毁与管理
java·c++·3d·docker·c#·制造·数据可视化