第七层:多态

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

文章目录

🎉welcome🎉
✒️博主介绍一名大一的智能制造专业学生在学习C/C++的路上会越走越远后面不定期更新有关C/C++语法数据结构算法Linuxue5使用制作游戏的心得和大家一起共同成长。
✈️C++专栏C++爬塔日记
😘博客制作不易👍点赞+⭐收藏+➕关注

前情回顾

在第六层中我遇到了继承它是面向对象三大特性之一它也是我遇到的第二个面向对象的特性因为继承C++中的类被分成子类和父类还有虚继承等强大的力量但是我还是掌握走向了第七层…

  • 🚄上章地址第六层继承

多态

“你来了啊这层有着面向对象的最后一种特性——多态它是每一个C++程序员都必须掌握的核心技术希望你也可以掌握…”“面向对象的最后一中特性了吗看起来是一场恶战。”

多态的基本概念

多态是C++面向对象的三大特性之一多态分为两类

  • 静态多态两种重载函数重载和运算符重载属于静态多态复用函数名
  • 动态多态子类和虚函数实现运行时发生的多态

那这两种多态有什么区别呢

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

那什么是晚绑定什么是早绑定下面就是早绑定的案例

  • 现在有一个函数它的参数是父类引用里面调用父类和子类当中都有的函数现在传过去一个子类可以调用吗可以的话是调用子类还是父类
#include<iostream>
using namespace std;

class A
{
public:
	void a1()
	{
		cout << "A在调用" << endl;
	}
};

class A1 :public A
{
public:
	void a1()
	{
		cout << "A1在调用" << endl;
	}
};

void polym(A& a)
{
	a.a1();
}
void test1()
{
	A1 a;
	polym(a);
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
是可以调用的因为在C++中允许父子之间的类型转换不需要做强制类型转换父类的引用可以直接指向子类但是反省调用的是父类中的函数那为什么传过去一个子类却调用的是父类呢因为在底层是调用父类这个时候就是因为地址的早绑定在编译阶段就确定了函数地址所以不管传子还是父都会指向父类中的函数地址调用父类中的函数地址如果要执行子类那便不能让编译器就编译阶段就确定函数地址不能进行早绑定需要在运行时在进行绑定这个时候就叫做地址晚绑定需要在父类成员函数前加

  • virtual

这个时候成员函数就变成了虚函数也就实现晚绑定了

#include<iostream>
using namespace std;

class A
{
public:
	virtual void a1()
	{
		cout << "A在调用" << endl;
	}
};

class A1 :public A
{
public:
	void a1()
	{
		cout << "A1在调用" << endl;
	}
};

void polym(A& a)
{
	a.a1();
}
void test1()
{
	A1 a;
	polym(a);
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述

动态多态的满足条件

在上面所提到的地址晚绑定就是动态多态那要实现动态多态有一些需要满足的条件

  • 有继承关系
  • 子类中要重写父类中虚函数返回类型、函数名、参数列表都要一致对于子类中的函数重写virtual可加可不加

动态多态的使用

  • 使用父类指针或者引用去执行子类对象

那具体为什么会这样呢就是因为虚函数的作用。

虚函数

第三层C++中的对象和this指针中提到了在类内没有非静态成员时的大小为一那类内只有有虚函数的时候大小是多少

#include<iostream>
using namespace std;

class A
{
public:
	virtual void a1()
	{
		cout << "A在调用" << endl;
	}
};

class A1 :public A
{
public:
	void a1()
	{
		cout << "A1在调用" << endl;
	}
};

void test1()
{
	cout << sizeof(A) << endl;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
是8那它的本质其实是指针因为我编译器的环境是x64所以是8那虚函数的内部指向的其实和虚继承一样内部也只一个指针但是是vfptr叫做虚函数指针会指向vftable叫做虚函数表这个虚函数表内部是记录的是虚函数地址当父类的指针指向的是指针或者引用的时候发生多态当子类通过父类引用调用函数的时候就会去子类的虚函数表内调用子类当中的函数因为重写子类的函数就会覆盖掉自己虚函数表中的父类函数这个时候就会调用子类函数。
在这里插入图片描述

多态的优点

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护

可以用代码实现一个不用多态的和一个使用多态的来对比验证

  • 现在处于一个麦饮料的地方需要你选择饮料

普通方法

#include<string>
#include<iostream>
using namespace std;

class drink
{
public:
	void dele(string s)
	{
		if (s == "mike")
		{
			cout << "牛奶来咯" << endl;
		}
		else if (s == "orange")
		{
			cout << "橙汁来咯" << endl;
		}
		else if (s == "coke")
		{
			cout << "可乐来咯" << endl;
		}
	}	
	string _d;
};
void test1()
{
	drink d;
	cin >> d._d;
	d.dele(d._d);
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
多态实现

#include<string>
#include<iostream>
using namespace std;

class drink
{
public:
	virtual void dele()
	{
		
	}	
};
class mike :public drink
{
public:
	void dele()
	{
		cout << "牛奶来咯" << endl;
	}
};
class orange :public drink
{
public:
	void dele()
	{
		cout << "橙汁来咯" << endl;
	}
};
class coke :public drink
{
public:
	void dele()
	{
		cout << "可乐来咯" << endl;
	}
};
void test1()
{
	drink *d = new coke;
	d->dele();
	delete d;
	d=NULL;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
可以发现多态的代码量要远远超于普通的写法那为什么还要使用多态就是因为多态的三个优点并且对于第三个优点来说对于普通写法要加入什么饮品需要修改原码而在真实的开发中是不建议去修改的提倡开闭原则1

纯虚函数和抽象类

上面例子中可以看到对于父类中的虚函数是基本不会调用的用的多的是子类中的同名函数这个时候可以将父类中的虚函数变成纯虚函数语法

  • virtual 返回类型 函数名 参数 =0大括号不用写

这个时候当类内有了纯虚函数这个类也就被称为抽象类。

抽象类特点

  • 抽象类无法实例化对象
  • 子类必须重写抽象类函数中的纯虚函数否则子类也会成为抽象类

验证抽象类无法实例化对象

#include<string>
#include<iostream>
using namespace std;

class drink
{
public:
	virtual void dele() = 0;
};
class mike :public drink
{
public:
	void dele()
	{
		cout << "牛奶来咯" << endl;
	}
};
class orange :public drink
{
public:
	void dele()
	{
		cout << "橙汁来咯" << endl;
	}
};
class coke :public drink
{
public:
	void dele()
	{
		cout << "可乐来咯" << endl;
	}
};
void test1()
{
	drink d;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
验证子类不重写函数也将变成抽象类

#include<string>
#include<iostream>
using namespace std;

class drink
{
public:
	virtual void dele() = 0;
};
class mike :public drink
{
public:
	void dele(int a)
	{
		cout << "牛奶来咯" << endl;
	}
};
class orange :public drink
{
public:
	void dele()
	{
		cout << "橙汁来咯" << endl;
	}
};
class coke :public drink
{
public:
	void dele()
	{
		cout << "可乐来咯" << endl;
	}
};
void test1()
{
	mike d;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述

虚析构和纯虚析构

  • 在使用多态的时候子类中有成员属性开辟空间到堆区则父类指针在释放的时候无法调用子类中的析构函数

验证

#include<string>
#include<iostream>
using namespace std;

//人
class people
{
public:
	people()//查看是否调用
	{
		cout << "父类内构造函数调用" << endl;
	}
	~people()//查看是否调用
	{
		cout << "父类内析构函数调用" << endl;
	}
	virtual void come() = 0;//纯虚函数
};
//男人
class man :public people
{
public:
	man(string _name)//查看是否调用
	{
		cout << "子类内构造函数调用"	<< endl;
		this->_name = new string(_name);//对名字进行初始化
	}
	~man()//查看是否调用
	{
		if (_name != NULL)
		{
			cout << "子类内析构函数调用" << endl;
			delete _name;
			_name = NULL;
		}
	}
	void come()
	{
		cout <<*_name<<"来咯" << endl;
	}
	string* _name;//用指针来管理名字
};

void test1()
{
	people* p = new man("张三");
	p->come();
	delete p;
	p = NULL;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
那这个时候就会造成内存泄漏那怎么去解决呢

  • 将父类中的析构函数变成虚析构或者纯虚析构
#include<string>
#include<iostream>
using namespace std;

//人
class people
{
public:
	people()//查看是否调用
	{
		cout << "父类内构造函数调用" << endl;
	}
	virtual ~people()//查看是否调用
	{
		cout << "父类内析构函数调用" << endl;
	}
	virtual void come() = 0;//纯虚函数
};
//男人
class man :public people
{
public:
	man(string _name)//查看是否调用
	{
		cout << "子类内构造函数调用"	<< endl;
		this->_name = new string(_name);//对名字进行初始化
	}
	~man()//查看是否调用
	{
		if (_name != NULL)
		{
			cout << "子类内析构函数调用" << endl;
			delete _name;
			_name = NULL;
		}
	}
	void come()
	{
		cout <<*_name<<"来咯" << endl;
	}
	string* _name;//用指针来管理名字
};

void test1()
{
	people* p = new man("张三");
	p->come();
	delete p;
	p = NULL;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
选择就会走子类的调用如果是纯虚析构呢

#include<string>
#include<iostream>
using namespace std;

//人
class people
{
public:
	people()//查看是否调用
	{
		cout << "父类内构造函数调用" << endl;
	}
	virtual ~people() = 0;//查看是否调用
	virtual void come() = 0;//纯虚函数
};
//男人
class man :public people
{
public:
	man(string _name)//查看是否调用
	{
		cout << "子类内构造函数调用"	<< endl;
		this->_name = new string(_name);//对名字进行初始化
	}
	~man()//查看是否调用
	{
		if (_name != NULL)
		{
			cout << "子类内析构函数调用" << endl;
			delete _name;
			_name = NULL;
		}
	}
	void come()
	{
		cout <<*_name<<"来咯" << endl;
	}
	string* _name;//用指针来管理名字
};

void test1()
{
	people* p = new man("张三");
	p->come();
	delete p;
	p = NULL;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述
那这里是为什么呢可以看到报错内容说纯虚析构是无法解析的外部符号对比上面的虚析构发现纯虚析构没有定义纯虚函数可以不用定义那为什么纯虚析构就需要定义了呢这是因为析构是所有类在销毁前会走的一个函数这个时候内部没有实现就走不过去就会产生报错那解决方法就是在类外定义析构函数需要在析构函数名前加上作用域

#include<string>
#include<iostream>
using namespace std;

//人
class people
{
public:
	people()//查看是否调用
	{
		cout << "父类内构造函数调用" << endl;
	}
	virtual ~people() = 0;//查看是否调用
	virtual void come() = 0;//纯虚函数
};
people::~people()
{
		cout << "父类内析构函数调用" << endl;
}
//男人
class man :public people
{
public:
	man(string _name)//查看是否调用
	{
		cout << "子类内构造函数调用"	<< endl;
		this->_name = new string(_name);//对名字进行初始化
	}
	~man()//查看是否调用
	{
		if (_name != NULL)
		{
			cout << "子类内析构函数调用" << endl;
			delete _name;
			_name = NULL;
		}
	}
	void come()
	{
		cout <<*_name<<"来咯" << endl;
	}
	string* _name;//用指针来管理名字
};

void test1()
{
	people* p = new man("张三");
	p->come();
	delete p;
	p = NULL;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述

虚析构和纯虚析构的共性

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

虚析构和纯虚析构的区别

  • 有纯虚析构的类也是抽象类无法实例化对象

验证

#include<string>
#include<iostream>
using namespace std;

//人
class people
{
public:
	people()//查看是否调用
	{
		cout << "父类内构造函数调用" << endl;
	}
	virtual ~people() = 0;//查看是否调用
	virtual void come() = 0;//纯虚函数
};
people::~people()
{
		cout << "父类内析构函数调用" << endl;
}
//男人
class man :public people
{
public:
	man(string _name)//查看是否调用
	{
		cout << "子类内构造函数调用"	<< endl;
		this->_name = new string(_name);//对名字进行初始化
	}
	~man()//查看是否调用
	{
		if (_name != NULL)
		{
			cout << "子类内析构函数调用" << endl;
			delete _name;
			_name = NULL;
		}
	}
	void come()
	{
		cout <<*_name<<"来咯" << endl;
	}
	string* _name;//用指针来管理名字
};

void test1()
{
	people p;
}
int main()
{
	test1();
	return 0;
}

在这里插入图片描述

面向对象结束接下来是什么?

随着石碑倒下看着眼前的楼梯我心情沉重“面向对象结束了那接下来会是什么”

本章知识点图片形式

在这里插入图片描述

😘预知后事如何关注新专栏和我一起征服C++这座巨塔
🚀专栏C++爬塔日记
🙉都看到这里了留下你们的👍点赞+⭐收藏+📋评论吧🙉


  1. 开闭原则对扩展进行开发对修改进行关闭。 ↩︎

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“第七层:多态” 的相关文章