C++学习笔记19-多态

19.1 多态的基本概念

19.1.1 静态多态和动态多态

多态是C++面向对象三大特性之一(封装、继承、多态)
多态分为两类:

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

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

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

下面通过案例进行讲解多态:

#include<iostream>
using namespace std; 
//多态

//动物类

//地址早绑定,在编译阶段就确定了函数地址
class Animal
{
    
    
public:
	void speak()  
	{
    
    
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
    
    
public:
	void speak()
	{
    
    
		cout << "小猫在说话" << endl;
		m_A = 100;
	}
	int m_A;
};

//执行说话的函数

void doSpeak(Animal& animal) //Animal& animal = cat; 传入的是子类,用父类接收,此时此对象转换为了父类
{
    
    
	cout << (typeid(animal).name()) << endl;
	animal.speak();
}

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

//动态多态
//1.有继承关系
//2.子类要重写父类的虚函数

//动态多态的使用
//使用动态的指针或者引用来执行子类对象
class Animal_02
{
    
    
public:
	 virtual void speak()  //虚函数
	{
    
    
		cout << "动物在说话" << endl;
	}
};

class Cat_02 :public Animal_02
{
    
    
public:
	//函数重写 函数返回值类型 函数名 参数列表 完全不同  子类中的virtual可写可不写
	void speak()
	{
    
    
		cout << "小猫在说话" << endl;
	}
};
class Dog_02 :public Animal_02
{
    
    
public:
	void speak()
	{
    
    
		cout << "小狗在说话" << endl;
	}
};

void doSpeak_02(Animal_02& animal)    //这里仍旧是子类,因为父类中有虚函数,所以在这里
{
    
    
	cout << (typeid(animal).name()) << endl;
	animal.speak();
	animal.Animal_02::speak();
}

void test1_01()
{
    
    
	Cat cat1;
	doSpeak(cat1);

	Cat_02 cat2;
	doSpeak_02(cat2);

	Dog_02 dog;
	doSpeak_02(dog);
//通过晚绑定,使得函数地址不是一开始就绑定完毕, 而是通过不同的对象指针来绑定不同的函数地址。
}

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

总结:
动态多态的满足条件:

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

动态多态的使用:
   
 使用父类指针或者引用来执行子类对象。

19.1.2 多态的原理刨析

以上面的代码为例,当写了虚函数后,在类中会发生一些变化。

在Animal_02类中,会储存一个虚函数指针——vfptr(virtual function pointer),此指针指向一个虚函数表——vftable(virtual function table),在虚函数表的内部记录虚函数的地址。

在虚函数表中储存函数的方式就是将此函数的作用域一并储存起来,储存的是它的地址,即:&Animal_02::speak
在这里插入图片描述

当子类(cat_02)继承父类(Animal_02)时,子类继承了父类的虚函数指针和虚函数表。
此时:

  1. 若子类没有重写父类的虚函数,那么子类的虚函数指针和虚函数表完全与父类相同;
    在这里插入图片描述

  2. 若子类重写了父类的虚函数,那么在子类的虚函数表中子类重写的函数会将父类的虚函数覆盖。
    在这里插入图片描述

注:以上是多态的原理刨析,在实际应用中,当父类出现虚函数,无论子类是否重写此虚函数,通过 typeid(对象).name() 可以发现即使使用父类指针/引用指向子类对象,该对象的类型也是子类的类型,而不像往常一样被更改为父类类型。
也就是说当父类出现虚函数时,即使出现父类指针/引用指向子类对象的代码,这时此对象仍旧是子类类型,可以像正常被继承下来的子类一样使用访问子类和父类的语法

多态有但是继承所没有的功能:
如果在父类定义了一个成员函数(假设名字叫做A)用来调用虚函数(假设名字叫做B),此时根据我们的原理, 我们用子类继承父类并重写虚函数B, 此后子类对象调用父类对象的成员函数A时,这个A所调用的虚函数B已经被子类重写过的函数覆盖,所以会直接调用被覆盖之后的函数B。也就是说我们不需要再在子类中写一个新的A来调用新的B,直接通过此子类对象调用父类中的函数A即可。
这样一来,很多重复调用的函数我们就不需要在子类中一遍一遍重写了


19.2 多态案例1-计算器

案例描述:
  分别利用普通写法和多态技术,设计实现两介操作数进行运算的计算器类。

多态的优点:

  • 代码组织结构清晰(抽象类和子类很清晰可读)。
  • 可读性强 (每个功能都在一个类里,方便读)。
  • 利于前期和后期的扩展以及维护(直接创建或者修改一个新的子类即可)。
#include<iostream>
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;
	int m_Num2;
};

void test2_01()
{
    
    
	cout << "普通实现" << endl;
	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;
}


//利用多态实现计算器
//实现计算器抽象类
class AbstractCalculator
{
    
    
public:
	virtual int getResult() = 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;
	}
};

void test2_02()
{
    
    
	cout << "多态实现" << endl;
	//多态使用条件
	//父类指针或者引用指向子类对象

	//加法
	AbstractCalculator* abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;

	abc = new SubCalculator;  //重新规定指针指向即可
	abc->m_Num1 = 10;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;

	abc = new MulCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;
}

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

19.3 纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容。

因此可以将虚函数改为纯虚函数

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

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

抽象类特点

  • 无法实例化对象。

  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。

示例:

#include<iostream>
using namespace std;
class Base
{
    
    
public:
	//只要有一个纯虚函数,这个类就称为抽象类
	virtual void func() = 0;
};
class Son :public Base
{
    
    
public:
	int m_A;
};
class Son_02 :public Base
{
    
    
public:
	void func()
	{
    
    
		cout << "func的调用" << endl;
	}
	int m_A;
};
int main()
{
    
    
	//Base b;			   //无法实例化对象
	//Base* b = new Base;  //堆区也是不可以的
	//Son s;			   //子类不重写父类纯虚函数时,也会被认为是抽象类
	Son_02 s;              //重写虚函数后,可以实例化
	Base* base = new Son_02;
	base->func();
	system("pause");
}

19.4 多态案例2-制作饮品

案例描述:

制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料。

利用多态技术实现本案例:提供抽象制作饮品基类,提供制作咖啡和茶叶的子类。
在这里插入图片描述

示例:

#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:
	//煮水
	void Boil()
	{
    
    
		cout << "煮农夫山泉" << endl;
	}
	//冲泡
	void Brew()
	{
    
    
		cout << "冲泡咖啡" << endl;
	}
	//倒入杯中
	void PourInCup()
	{
    
    
		cout << "倒入杯中" << endl;
	}
	//加入辅料
	void PutSomething()
	{
    
    
		cout << "加入糖和牛奶" << endl;
	}
	
};
//茶叶
class Tea :public AbstractDrinking
{
    
    
public:
	//煮水
	void Boil()
	{
    
    
		cout << "煮开水" << endl;
	}
	//冲泡
	void Brew()
	{
    
    
		cout << "冲泡茶叶" << endl;
	}
	//倒入杯中
	void PourInCup()
	{
    
    
		cout << "倒入杯中" << endl;
	}
	//加入辅料
	void PutSomething()
	{
    
    
		cout << "加入枸杞和柠檬" << endl;
	}
};
void dowork(AbstractDrinking* abs)
{
    
    
	abs->makeDrink();
	delete abs;
}
void test4_01()
{
    
    
	cout << "制作咖啡:" << endl;
	dowork(new Coffee);
	cout << "制作茶叶:" << endl;
	dowork(new Tea);
}
int main()
{
    
    
	test4_01();
	system("pause");
	return 0;
}

19.5 虚析构和纯虚析构

问题:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码。因为当析构函数不是虚函数时,这个析构函数没有虚函数指针,而多态的情况下都是通过虚函数指针调用函数的,所以将不会调用整个析构链。

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

虚函数和纯虚函数析构共性:

  • 可以解决父类指针释放子类对象,即可以调用子类的析构函数。
  • 需要有具体的函数实现,纯虚析构也要有函数实现,使用内外定义即可。

虚析构和纯虚析构区别:

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

虚析构语法:

virtual ~类名(){
    
    }

纯虚析构语法:

//类内声明
virtual ~类名() = 0;
//类外定义
类名::~类名(){
    
    }

示例:

#include<iostream>
using namespace std;
class Animal5
{
    
    
public:
	Animal5()
	{
    
    
		cout << "Animal5构造函数调用" << endl;
	}
	//虚析构
	//virtual ~Animal5()
	//{
    
    
	//	cout << "Animal5析构函数调用" << endl;
	//}
	//纯虚析构
	virtual ~Animal5() = 0;

	//纯虚函数
	virtual void speak() = 0;	
};
Animal5::~Animal5()
{
    
    
	cout << "Animal5析构函数调用" << endl;
}
class Cat5 :public Animal5
{
    
    
public:
	Cat5(string name)
	{
    
    
		cout << "Cat5构造函数" << endl;
		m_Name = new string(name);
	}
	virtual void speak()
	{
    
    
		cout << *m_Name << "小猫在说话" << endl;
	}
	~Cat5()
	{
    
    
		cout << "Cat5析构函数" << endl;
		if (m_Name != NULL)
		{
    
    
			delete m_Name;
			m_Name = NULL;
		}
	}
	string* m_Name;   //创建在堆区
};

void test5_01()
{
    
    
	Animal5* animal = new Cat5("汤姆");
	animal->speak();
	delete animal;
}
int main()
{
    
    
	test5_01();
	system("pause");
	return 0;
}

总结:

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

19.6 多态案例3-电脑组装

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

示例:

#include<iostream>
using namespace std;

class 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)
	{
    
    
		this->cpu = cpu;
		this->vc = vc;
		this->mem = mem;
	}
	void work()
	{
    
    
		cpu->calculate();
		vc->display();
		mem->storage();
	}
	~Computer()
	{
    
    
		if (cpu != NULL)
		{
    
    
			delete cpu;
			cpu = 0;
		}
		if (vc != NULL)
		{
    
    
			delete vc;
			vc = 0;
		}
		if (mem != NULL)
		{
    
    
			delete mem;
			mem = 0;
		}
	}
private:
	CPU* cpu;
	VideoCard* vc;
	Memory* mem;
};
//Inter
class InterCPU :public CPU
{
    
    
public:
	void calculate()
	{
    
    
		cout << "Intel的CPU开始计算了" << endl;
	}
};
class InterVideoCrad :public VideoCard
{
    
    
public:
	void display()
	{
    
    
		cout << "Inter的显卡开始显示了" << endl;
	}
};
class InterMemory :public Memory
{
    
    
public:
	void storage()
	{
    
    
		cout << "Inter的内存条开始存储了" << endl;
	}
};
//Lenovo
class LenovoCPU :public CPU
{
    
    
public:
	void calculate()
	{
    
    
		cout << "Lenovo的CPU开始计算了" << endl;
	}
};
class LenovoVideoCrad :public VideoCard
{
    
    
public:
	void display()
	{
    
    
		cout << "Lenovo的显卡开始显示了" << endl;
	}
};
class LenovoMemory :public Memory
{
    
    
public:
	void storage()
	{
    
    
		cout << "Lenovo的内存条开始存储了" << endl;
	}
};

void test6_01()
{
    
    
	//创建第一台电脑
	cout << "第一台电脑开始工作" << endl;
	CPU* intercpu = new InterCPU;
	VideoCard* intercard = new InterVideoCrad;
	Memory* intermemory = new InterMemory;
	Computer* computer1 = new Computer(intercpu, intercard, intermemory);
	computer1->work();
	delete computer1;

	//创建第二台电脑
	cout <<"-----------------\n" << "第二台电脑开始工作" << endl;
	CPU* lenovocpu = new LenovoCPU;
	VideoCard* lenovocard = new LenovoVideoCrad;
	Memory* lenovomemory = new LenovoMemory;
	Computer* computer2 = new Computer(lenovocpu, lenovocard, lenovomemory);
	computer2->work();
	delete computer2;

	//创建第三台电脑
	cout << "-----------------\n" << "第三台电脑开始工作" << endl;
	Computer* computer3 = new Computer(new LenovoCPU, new InterVideoCrad, new LenovoMemory);
	computer3->work();
	delete computer3;

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

猜你喜欢

转载自blog.csdn.net/qq_49030008/article/details/123354311
今日推荐