C++之 多态(Polymorphism)

目录

一、基本概念

多态的使用:

        案例一——计算机类

多态的优点:

二、纯虚函数与抽象类

特点:

①无法实例化对象

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

        案例二——制作饮品

三、虚析构与纯虚析构

因为父类指针在析构时,不会调用子类中的析构函数,从而导致堆区属性未释放 

解决:将父类虚构改为虚析构

案例三——电脑组装


一、基本概念

多态是C++三大特性之一

分为、作用与区别:

作用 区别
静态多态 函数重载 和 运算符重载,复用函数名 函数地址早绑定,编译阶段确定函数地址
动态多态 派生类 和 虚函数 实现运行时多态 函数地址晚绑定,运行阶段确定函数地址

动态多态需要满足:

①有继承关系

②子类重写父类的虚函数

重写:子类的 返回值 函数名 形参列表 要与父类完全相同,但virtual可写可不写


多态的使用:

父类指针或引用子类对象 


例:

创建Animal与Cat与Dog类,其中每个都有dodif函数,最后单独实现dodif函数,创建测试函数并调用dodif函数

class Animal
{
public:
	void dodif()
	{
		cout << "动物" << endl;
	}
};
class Cat : public Animal
{
public:
	void dodif()
	{
		cout << "小猫" << endl;
	}
};
class Dog : public Animal
{
public:
	void dodif()
	{
		cout << "小狗" << endl;
	}
};
void dodif(Animal &animal)// Animal &animal = 子类传来的对象; 多态的使用
{
	animal.dodif();
}
void test01()
{
	Cat cat;
	dodif(cat);
    Cat dog;
	dodif(dog);
}

C++之 多态(Polymorphism)_第1张图片

可以发现,无论我传参是什么,结果总是输出动物,也就是调用父类中的函数 

这是因为dodif函数是静态多态,地址早绑定,在编译阶段就确定了函数地址,因此后面我怎么传参都是访问已经确定的函数地址


因此需要改为动态多态:父类中,函数virtual,使其变成虚函数,地址晚绑定

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

C++之 多态(Polymorphism)_第2张图片


接下来深度分析一下上面代码的实现逻辑

首先,没有加virtual时,我们输出一下父类的大小

C++之 多态(Polymorphism)_第3张图片

 结果是1,说明是个空对象

加上virtual,再次输出

C++之 多态(Polymorphism)_第4张图片

变成8,其实是变成了一个指针,即 虚函数指针cfptr


接下来,画出Animal类的内部结构

C++之 多态(Polymorphism)_第5张图片

使用开发人员命令提示符工具查看

C++之 多态(Polymorphism)_第6张图片

然后是,继承的Cat类的内部结构,此时并无重写 

C++之 多态(Polymorphism)_第7张图片

 虚函数表内仍然是&Animal::dodif

C++之 多态(Polymorphism)_第8张图片

而当我们加上重写

C++之 多态(Polymorphism)_第9张图片

 虚函数表内就改成了&Cat::dodif

C++之 多态(Polymorphism)_第10张图片

 因为重写后,子类中的虚函数表会被替换成子类的虚函数地址


        案例一——计算机类

使用普通方法多态2种方法实现计算器类

普通方法

class caculator
{
public:
	int getResult()
	{
		cin >> num1 >> oper >> num2;
		if (oper == "+")
		{
			return num1 + num2;
		}
		else if (oper == "-")
		{
			return num1 - num2;
		}
		else if (oper == "*")
		{
			return num1 * num2;
		}
	}
	string oper;
	int num1;
	int num2;
};
void test01()
{
	caculator c;
	int ret = c.getResult();
	cout << ret << endl;
}

现在实现了加法、减法和乘法三种功能,但是如果想要增加功能,则需要修改源码

实际开发中提倡开闭原则:对扩展进行开放,对修改进行关闭

②多态的方法

class caculator_abstract // 计算器抽象类
{
public:
	virtual int getResult()
	{
		return 0;
	}
	int num1;
	int num2;
};

class Add:public caculator_abstract  // 加法 Addition
{
	int getResult()
	{
		return num1 + num2;
	}
}; 
class Sub :public caculator_abstract // 减法 subtraction
{
	int getResult()
	{
		return num1 - num2;
	}
};
class Mul :public caculator_abstract // 乘法 multiplication
{
	int getResult()
	{
		return num1 * num2;
	}
};
class Div :public caculator_abstract // 除法 division
{
	int getResult()
	{
		return num1 / num2;
	}
};

调用

void test02()
{
	// 多态的使用方法:
	//父类指针或者引用子类对象
	caculator_abstract* c = new Add; // 加法调用
	c->num1 = 10;
	c->num2 = 5;
	cout << c->getResult() << endl;
	delete c;// 堆区的数据,记得销毁

	c = new Sub;
	c->num1 = 10;
	c->num2 = 5;
	cout << c->getResult() << endl; // 减法调用
	delete c;

	c = new Mul;
	c->num1 = 10;
	c->num2 = 5;
	cout << c->getResult() << endl; // 乘法调用
	delete c;

	c = new Div;
	c->num1 = 10;
	c->num2 = 5;
	cout << c->getResult() << endl; // 除法调用
	delete c;
}

C++之 多态(Polymorphism)_第11张图片

多态的优点:

①组织结构清晰

②可读性强

③对于扩展以及维护能力高


二、纯虚函数与抽象类

在多态中,通常父类中虚函数的实现没有什么意义,主要是调用子类重写的内容

因此可将父类中的虚函数改为纯虚函数


纯虚函数:写一个虚函数,使其 = 0,即为纯虚函数

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

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


特点:

①无法实例化对象

class Base
{
public:
	virtual void func() = 0;
};

void test01()
{
	Base b;
	new Base;
}

C++之 多态(Polymorphism)_第12张图片

 无论是栈上还是堆区都无法实例化对象


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

首先,不重写:

class Son :public Base
{
public:
	
};

void test01()
{
	Son s;
	new Son;
}

C++之 多态(Polymorphism)_第13张图片

 一样的无法实例化对象

加上重写

class Son :public Base
{
public:
	virtual void func() {}; // 重写
};

即使重写是空实现,仍然可以实例化对象

C++之 多态(Polymorphism)_第14张图片

简单修改一下

class Son :public Base
{
public:
	virtual void func() 
	{
		cout << "Son_func的调用" << endl;
	}; // 重写
};

void test01()
{
	Son s;
	s.func();
	 // 多态的方法:
	Base* base = new Son;
	base->func();
}

 C++之 多态(Polymorphism)_第15张图片


        案例二——制作饮品

简单制作一份饮品,咖啡or沏茶

咖啡:①煮水②冲泡咖啡③倒入杯中④加糖与牛奶

沏茶:①煮水②冲泡茶叶③倒入杯中④加柠檬

流程大致相同:①煮水②冲泡③倒入杯中④加佐料

class AbstactDrinking // 抽象类的饮品
{
public:
	virtual void Boil() = 0;
	virtual void Brew() = 0;
	virtual void Pour() = 0;
	virtual void Put() = 0;
	void makeDriking()
	{
		Boil();
		Brew();
		Pour();
		Put();
	}
};
class tea :public AbstactDrinking // 茶的具体实现
{
public:
	virtual void Boil()
	{
		cout << "煮水" << endl;
	}
	virtual void Brew()
	{
		cout << "冲泡茶叶" << endl;
	}
	virtual void Pour()
	{
		cout << "倒入杯中" << endl;
	}
	virtual void Put() 
	{
		cout << "加柠檬" << endl;
	}
};
class coffee :public AbstactDrinking // 咖啡的具体实现
{
public:
	virtual void Boil()
	{
		cout << "煮水" << endl;
	}
	virtual void Brew()
	{
		cout << "冲泡咖啡" << endl;
	}
	virtual void Pour()
	{
		cout << "倒入杯中" << endl;
	}
	virtual void Put()
	{
		cout << "加糖与牛奶" << endl;
	}
};
void doDrinking(AbstactDrinking* abd)
{
	abd->makeDriking();
	delete abd; // new出的堆区数据,记得销毁
}
void test01()
{
	doDrinking(new tea);
	cout << "-----------------" << endl;
	doDrinking(new coffee);
}

C++之 多态(Polymorphism)_第16张图片

 没啥说的


三、虚析构与纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,导致子类堆区属性未被释放,引起内存泄漏

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


共同点 不同点
虚析构 可以解决父类指针释放子类对象 \
纯虚析构 都需要有具体的函数实现 属于抽象类,无法实例化对象

语法:

虚析构 virtual~类名() {};
纯虚析构 virtual~类名() = 0 ; 声明
类名 : : 类名() {} ;实现

接下来创建父类Animal类与子类Cat类

class Animal
{
public:
	virtual void speak() = 0;
};
class Cat:public Animal
{
public:
	virtual void speak()
	{
		cout << "小猫" << endl;
	}
};
void test01()
{
	Animal* animal = new Cat;
	animal->speak();
	delete animal;
}

C++之 多态(Polymorphism)_第17张图片

接下来在Cat类中增加string属性c_name; 

class Cat:public Animal
{
public:
	Cat(string name)
	{
		c_name = new string(name); // 堆区创建,应有释放
	}
	virtual void speak()
	{
		cout << *c_name << "小猫" << endl;
	}
	string *c_name;
};
void test01()
{
	Animal* animal = new Cat("yomi");
	animal->speak();
	delete animal;
}

C++之 多态(Polymorphism)_第18张图片

 而c_name是堆区开辟的内存,应有释放,补全Cat的析构:

	~Cat()
	{
		if (c_name != NULL)
		{
			cout << "Cat的析构" << endl;
			delete c_name;
			c_name = NULL;
		}
	}

同时,我们把父类Animal的析构和构造以及子类Cat的构造也补全:

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;
		}
	}
};

C++之 多态(Polymorphism)_第19张图片

运行可以发现,没有子类Cat的析构,说明堆区new出的c_name属性并未释放

因为父类指针在析构时,不会调用子类中的析构函数,从而导致堆区属性未释放 


解决:将父类虚构改为虚析构

	virtual ~Animal()
	{
		cout << "Animal的析构" << endl;
	}

C++之 多态(Polymorphism)_第20张图片

就走了子类的析构 


接下来实现纯虚析构

class Animal
{
public:
	Animal()
	{
		cout << "Animal的构造" << endl;
	}

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

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

如果只在父类中写 virtual ~Animal() = 0;  运行将报错

C++之 多态(Polymorphism)_第21张图片

 

因为这个相当于声明,没有实现

接下来在类外实现纯虚析构实现

Animal::~Animal() // 纯虚析构的实现
{
	cout << "Animal的纯虚析构" << endl;
}

C++之 多态(Polymorphism)_第22张图片

因为如果父类有开辟堆区的属性,也需要释放,因此需要纯虚析构的实现

同时,当类中有了纯虚析构,这个类也属于抽象类,无法实例化对象


 总结:

①虚析构或纯虚析构是用来解决通过父类指针释放子类对象

②如果子类没有堆区数据,可以不写虚析构或纯虚析构

③纯虚析构的类也属于抽象类,无法实例化对象


案例三——电脑组装

电脑主要组成部件是处理器(计算)+显卡(显示)+内存条(存储)


将每个零件封装成抽象基类,并提供不同的厂商生产不同的零件

class CPU
{
public:
	// 抽象计算函数
	virtual void calculate() = 0;
};
class GraphicsCard
{
public:
	// 抽象显示函数
	virtual void display() = 0;
};
class Memory
{
public:
	// 抽象存储函数
	virtual void storage() = 0;
};

具体厂商零件: 

// 具体零件厂商
// 1、Inter
class InterCPU :public CPU
{
	virtual void calculate()
	{
		cout << "Inter_CPU" << endl;
	}
};
class InterGPU :public GraphicsCard
{
	virtual void display()
	{
		cout << "Inter_GPU" << endl;
	}
};
class InterRAM :public Memory
{
	virtual void storage()
	{
		cout << "Inter_RAM" << endl;
	}
};
// 2、lenovo
class LenovoCPU :public CPU
{
	void calculate()
	{
		cout << "Lenovo_CPU" << endl;
	}
};
class LenovoGPU :public GraphicsCard
{
	void display()
	{
		cout << "Lenovo_GPU" << endl;
	}
};
class LenovoRAM :public Memory
{
	void storage()
	{
		cout << "Lenovo_RAM" << endl;
	}
};

 

创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

// 电脑类的实现
class Computer
{
public:
	Computer(CPU* cpu, GraphicsCard* gpu, Memory* ram)
	{
		c_cpu = cpu;
		c_gpu = gpu;
		c_ram = ram;
	}
	void dowork() // 工作接口
	{
		c_cpu->calculate();
		c_gpu->display();
		c_ram->storage();
	}
private:
	CPU* c_cpu;
	GraphicsCard* c_gpu;
	Memory* c_ram;
};

测试时组装3种不同的电脑进行工作

void test01()
{
	// 创建第一台电脑的零件
	CPU* interCpu = new InterCPU;
	GraphicsCard* interGpu = new InterGPU;
	Memory* interRam = new InterRAM;
	// 创建第一台电脑
	Computer* computer1 = new Computer(interCpu, interGpu, interRam);
	computer1->dowork(); // 调用工作函数
	delete computer1;
}

C++之 多态(Polymorphism)_第23张图片

在computer1工作完后,我们将其delete,但仍有一个问题:

上面new出的3个零件尚未delete,下面有2个方法

①在最后delete computer后,接着delete3个零件

②在电脑类中,提供析构函数,释放3个零件,下面实现方法②

	~Computer()
	{
		if (c_cpu != NULL) // 释放cpu
		{
			delete c_cpu;
			c_cpu = NULL;
		}
		if (c_gpu != NULL) // 释放gpu
		{
			delete c_gpu;
			c_gpu = NULL;
		}
		if (c_ram!= NULL) // 释放ram
		{
			delete c_ram;
			c_ram = NULL;
		}
	}

这样就解决了堆区的问题


创建另外2台电脑

2:

void test01()
{
		// 创建第二台电脑的零件
	CPU* lenovoCpu = new LenovoCPU;
	GraphicsCard* lenovoGpu = new LenovoGPU;
	Memory* lenovoRam = new LenovoRAM;
	// 创建第二台电脑
	Computer* computer2 = new Computer(lenovoCpu, lenovoGpu, lenovoRam);
	computer2->dowork(); // 调用工作函数
	delete computer2;
}

C++之 多态(Polymorphism)_第24张图片

 3:

	// 创建第三台电脑的零件
	CPU* InterCpu = new InterCPU;
	GraphicsCard* InterGpu = new InterGPU;
	Memory* lenovoRam = new LenovoRAM;
	// 创建第三台电脑
	Computer* computer3 = new Computer(InterCpu, InterGpu, lenovoRam);
	computer3->dowork(); // 调用工作函数
	delete computer3;

C++之 多态(Polymorphism)_第25张图片

你可能感兴趣的:(c++)