C++类和对象——多态

文章目录

前言

        多态是C++面向对象三大特性之一。 多态是C++中的一种重要机制,它允许在不同的对象之间使用相同的接口,但实现方式却不同。具体来说,多态允许一个对象可以在运行时表现出不同类型的行为,这样就可以实现代码的灵活性和可扩展性。

        C++的多态性可以通过继承和虚函数实现。当基类和派生类中的函数名和参数列表都相同时,如果在基类中将这个函数声明为虚函数,那么在使用派生类对象时,通过基类的指针或引用来调用这个函数,将会根据实际对象的类型来动态绑定对应的函数,从而实现多态性。

多态分为两类

1.静态多态:函数重载和运算符重载属于静态多态,复用函数名

2.动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别

静态多态的函数地址绑定——编译阶段确定函数地址

动态多态的函数地址绑定——运行阶段确定函数地址       


一、多态基本语法

        举个例子,假设有一个Animal类,有一个名为“speak”的虚方法。现在我们创建一个Dog类和一个Cat类,它们都从Animal类继承而来,重写了“speak”方法。当我们用一个Animal指针指向一个Dog对象并调用“speak”方法时,将会调用Dog的“speak”方法。同样的,当Animal指针指向的是一个Cat对象时,将会调用Cat的“speak”方法。这就是多态的体现。

#include<iostream>
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.speak();
}
void test01()
{
	cat c1;
	dospeak(c1);//想执行cat就需要地址晚绑定,需要利用动态多态。
	dog d1;
	dospeak(d1);
}
int main()
{
	test01();
	system("pause");
	return 0;
}

        上例中父类与子类各自包含有speak这一成员函数,dospeak函数中要求传入父类animal的引用,但在测试函数 test01()中想实现子类猫或狗的函数调用,由于父类地址早绑定,只会调用父类中的成员函数,所以需要利用虚函数(关键字:virtual),在父类的成员函数前加上virtual ,可以实现地址晚绑定,就可以调用出子类中的成员函数。

        地址早绑定,在编译阶段就确定函数的地址,所有无法实现让“猫说话”、“狗说话”。如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,就是地址晚绑定,需要利用动态多态。

动态多态满足条件:1.需要有继承关系。 2.子类重写父类的虚函数

重写:函数返回值类型、函数名、参数列表完全相同。

动态多态的使用:父类的指针或引用,指向子类对象

1.1 案例1

多态的优点:
1.代码组织结构清晰
2.可读性强
3.利于前期和后期的扩展以及维护

在真实的开发中 提倡开闭原则(开闭原则:对扩展进行开发,对修改进行关闭)

下面为一个计算器的案例,分别采用了传统写法和使用多态的写法,代码如下:

#include<iostream>
using namespace std;
#include<cstring>
//多态案例——计算机类
//分别利用普通的写法和多态技术实现计算器

//普通写法
class calculator//
{
public:
	int getresult(string oper)
	{
		if (oper == "+")
		{
			return m_num1 + m_num2;
		}
		if (oper == "-")
		{
			return m_num1 - m_num2;
		}
		if (oper == "*")
		{
			return m_num1 * m_num2;
		}
		if (oper == "/")
		{
			return m_num1 / m_num2;
		}
	}
	int m_num1;
	int m_num2;
};
void test01()
{
	calculator c1;
	c1.m_num1 = 20;
	c1.m_num2 = 10;
	cout << c1.m_num1 << "+" << c1.m_num2 << "=" << c1.getresult("+") << endl;
	cout << c1.m_num1 << "-" << c1.m_num2 << "=" << c1.getresult("-") << endl;
	cout << c1.m_num1 << "*" << c1.m_num2 << "=" << c1.getresult("*") << endl;
	cout << c1.m_num1 << "/" << c1.m_num2 << "=" << c1.getresult("/") << endl;
}
//在真实的开发中 提倡开闭原则
//开闭原则:对扩展进行开放,对修改进行关闭
//利用多态实现计算器的好处:1.组织结构清晰。 2.可读性强。 3.对于前期和后期扩展以及维护性高
class abstractCalculator//实现一个计算器的抽象类
{
public:
	virtual int getresult()//因为多态条件:有继承关系,关键字virtual
	{
		return 0;
	}
	int m_num1;
	int m_num2;
};
//加法计算器类
class addcalculator :public abstractCalculator
{
public:
	int getresult()
	{
		return m_num1 + m_num2;
	}
};
//减法计算器类
class subcalculator :public abstractCalculator
{
public:
	int getresult()
	{
		return m_num1 - m_num2;
	}
};
//乘法计算器类
class mulcalculator :public abstractCalculator
{
public:
	int getresult()
	{
		return m_num1 * m_num2;
	}
};
//除法计算器类
class divcalculator :public abstractCalculator
{
public:
	int getresult()
	{
		return m_num1 / m_num2;
	}
};
void test02()
{
	//多态使用的条件
	//父类指针或者引用指向子类对象
	//加法运算
	abstractCalculator * abs= new addcalculator;
	abs->m_num1 = 20;
	abs->m_num2 = 10;
	cout << abs->m_num1 << "+" << abs->m_num2 << "=" << abs->getresult() << endl;
	//new放在堆区的对象用完后需要析构销毁
	delete abs;

	//减法运算
	abs = new subcalculator;
	abs->m_num1 = 20;
	abs->m_num2 = 10;
	cout << abs->m_num1 << "-" << abs->m_num2 << "=" << abs->getresult() << endl;
	delete abs;

	//乘法运算
	abs = new mulcalculator;
	abs->m_num1 = 20;
	abs->m_num2 = 10;
	cout << abs->m_num1 << "*" << abs->m_num2 << "=" << abs->getresult() << endl;
	delete abs;

	//除法运算
	abs = new divcalculator;
	abs->m_num1 = 20;
	abs->m_num2 = 10;
	cout << abs->m_num1 << "/" << abs->m_num2 << "=" << abs->getresult() << endl;
	delete abs;
}

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

 多态使用的条件:父类使用指针或者引用指向子类对象

        本例中采用了父类的指针的方法,需要创建一个 abstractCalculator 父类的指针 abs ,指向用new创建的 addcalculator(或者其他四则运算函数),当使用完之后需要用 delete 销毁。由本案例中可以看出使用多态的好处:1.组织结构清晰。 2.可读性强。 3.对于前期和后期扩展以及维护性高 

二、纯虚函数和抽象类 

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

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

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

抽象类特点

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

下面为事例代码:

#include<iostream>
using namespace std;
//纯虚函数和抽象类
class base
{
public:
	virtual void fun() = 0;//纯虚函数语法:virtual 返回值类型 函数名(参数列表) = 0
	//只要有一个纯虚函数,这个类被称为抽象类
	//抽象类的特点:1.无法实例化对象。2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
};
class son :public base
{
public:
	void fun()
	{
		cout << "fun()调用" << endl;
	}
};
void test01()
{
	base* b = new son;
	b->fun();
}
int main()
{
	test01();
	system("pause");
	return 0;
}

上面案例中父类为纯虚函数,因此这个类为抽象类,抽象类中子类必须对父类重写。

2.1 案例2

 案例2:制作饮品。

案例描述:
制作饮品的大致流程为: 煮水 - 冲泡 - 倒入杯中 - 加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶

#include<iostream>
using namespace std;
//多态案例2 制作饮品
class abstractdrinking
{
public:
	//煮水
	virtual void boil() = 0;
	//冲泡
	virtual void brew() = 0;
	//倒入杯中
	virtual void pourcup() = 0;
	//加入辅料
	virtual void putsth() = 0;
	//制作饮品全流程
	void makedrink()
	{
		boil();
		brew();
		pourcup();
		putsth();
	}
};
//制作咖啡
class coffee :public abstractdrinking
{
	//煮水
	virtual void boil()
	{
		cout << "煮雪水" << endl;
	}
	//冲泡
	virtual void brew()
	{
		cout << "冲咖啡" << endl;
	}
	//倒入杯中
	virtual void pourcup()
	{
		cout<<"倒入咖啡杯" << endl;
	}
	//加入辅料
	virtual void putsth()
	{
		cout << "加入糖和牛奶" << endl;
	}
};
//制作茶
class tea :public abstractdrinking
{
	//煮水
	virtual void boil()
	{
		cout << "煮山泉水" << endl;
	}
	//冲泡
	virtual void brew()
	{
		cout << "冲茶叶" << endl;
	}
	//倒入杯中
	virtual void pourcup()
	{
		cout << "倒入茶杯" << endl;
	}
	//加入辅料
	virtual void putsth()
	{
		cout << "加入枸杞" << endl;
	}
};
//制作的函数
void dowork(abstractdrinking*abs)
{
	abs->makedrink();
	delete abs;
}
void test01()
{
	//制作咖啡
	dowork(new coffee);
	cout << "-------------------" << endl;
	//制作茶叶
	dowork(new tea);

}
int main()
{
	test01();
	system("pause");
	return 0;
}

本案例中使用了创建父类指针,建立多态的方法。 

三、虚析构和纯虚析构 

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。解决方式:将父类中的析构函数改为虚析构或者纯虚析构。

虚析构和纯虚析构共性:

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

虚析构和纯虚析构区别:如果是纯虚析构,该类属于油象类,无法实例化对象 

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

纯虚析构语法:
virtual ~类名()= 0;
在类外写:类名::~类名(){}

        下面为一个案例,在子类cat堆区中创建了属性,父类指针在析构的时候,不会调用子类中析构函数,出现内存泄露,可以利用虚析构可以解决父类指针释放子类对象时不干净的问题。下面代码中提供了两种方法,一种是虚析构,另一种是创建纯虚析构(两种方法不能同时使用,在案例中注释掉了方法一)。

#include<iostream>
using namespace std;
#include<string>
//虚析构和纯虚析构
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;
		m_name=new string(name);
	}
	void speak()
	{
		cout <<*m_name<< "小猫在说话" << endl;
	}
	string *m_name;
	~cat()//delete是清空指针区域的内容,并不清空指针,NULL是把指针替换为NULL地址
	{
		cout << "cat析构函数调用" << endl;
		if (m_name != NULL)
		{
			delete m_name;
			m_name = NULL;
		}
	}
};
void test01() 
{
	animal* a1 = new cat("Tom");//父类指针指向子类对象
	a1->speak();
	delete a1;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

3.1 案例3——电脑组装

        电脑主要组成部件为 CPU (用于计算) ,显卡 (用于显示) ,内存条(用于存储)将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口测试时组装三台不同的电脑进行工作。 

 设计流程图

#include<iostream>
using namespace std;
#include<string>
//电脑组装案例
//1.抽象出每个零件的类
class cpu//抽象CPU类
{
public:
	virtual void calculate() = 0;
};
class videocard//抽象显卡类
{
public:
	virtual void display() = 0;
};
class memory//抽象内存条类
{
public:
	virtual void storage() = 0;
};
//2.构造电脑类
class computer
{
public:
	computer(cpu* c, videocard* v, memory* m)
	{
		m_c = c;
		m_v = v;
		m_m = m;
	}
	//工作函数
	void dowork()
	{
		//让零件工作起来,调用接口
		m_c->calculate();
		m_v->display();
		m_m->storage();
	}
	//提供析构函数 释放3个电脑零件
	~computer()
	{
		if (m_c != NULL)//释放CPU
		{
			delete m_c;
			m_c = NULL;
		}
		if (m_v != NULL)//释放显卡
		{
			delete m_v;
			m_v = NULL;
		}
		if (m_m != NULL)//释放内存条
		{
			delete m_m;
			m_m = NULL;
		}
	}
private:
	cpu* m_c;
	videocard* m_v;
	memory* m_m;
};
//3.具体厂商
//英特尔厂商
class InterCPU :public cpu
{
public:
	virtual void calculate()
	{
		cout << "Inter的CPU开始计算了" << endl;
	}
};
class InterVideoCard :public videocard
{
public:
	virtual void display()
	{
		cout << "Inter的VideoCard开始显示了" << endl;
	}
};
class InterMemory :public memory
{
public:
	virtual void storage()
	{
		cout << "Inter的Memory开始存储了" << endl;
	}
};
//联想厂商
class LenoveCPU :public cpu
{
public:
	virtual void calculate()
	{
		cout << "Lenove的CPU开始计算了" << endl;
	}
};
class LenoveVideoCard :public videocard
{
public:
	virtual void display()
	{
		cout << "Lenove的VideoCard开始显示了" << endl;
	}
};
class LenoveMemory :public memory
{
public:
	virtual void storage()
	{
		cout << "Lenove的Memory开始存储了" << endl;
	}
};

void test01()
{
	//第一台电脑零件   ——用父类指针指向子类对象
	cpu * intelcpu = new InterCPU;
	videocard* intelcard = new InterVideoCard;
	memory* intelmem = new InterMemory;
	//创建第一台电脑
	cout << "第一台电脑" << endl;
	computer* com1 = new computer(intelcpu, intelcard, intelmem);
	com1->dowork();
	delete com1;
	cout << "--------------------------" << endl;
	//创建第二台电脑
	cout << "第二台电脑" << endl;
	computer* com2 = new computer(new LenoveCPU, new LenoveVideoCard,new LenoveMemory);
	com2->dowork();
	delete com2;
	cout << "--------------------------" << endl;
	//创建第三台电脑
	cout << "第三台电脑" << endl;
	computer* com3 = new computer(new InterCPU, new LenoveVideoCard, new LenoveMemory);
	com3->dowork();
	delete com3;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

         虚析构是用在多态下,只调用父类的析构函数,没有调用子类的析构,从而无法释放在子类中建立的堆区内存。但此案例中并没有在子类中建立堆区内存。1、此处Computer实例可以不用指针,直接实例化就行,因为Computer实例的建立和销毁都在同一个函数下,所以存放在栈区或是堆区都行。2、不需要虚析构,因为new Computer堆区指向的三个零件堆区已经在Computer析构函数中释放3、Computer属于单独一个类,它的析构不会影响其他类的析构,所以不需要虚析构


总结

         C++的多态性可以通过继承和虚函数实现。当基类和派生类中的函数名和参数列表都相同时,如果在基类中将这个函数声明为虚函数,那么在使用派生类对象时,通过基类的指针或引用来调用这个函数,将会根据实际对象的类型来动态绑定对应的函数,从而实现多态性。

        多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。此时可以将父类中的析构函数改为虚析构或者纯虚析构,这样就能释放子类的内存,防止内存泄露。

猜你喜欢

转载自blog.csdn.net/m0_74893211/article/details/130875766