【C++】 ——必须要了解的多态基础篇

一、什么是多态?

  • 多态是每次面试和C++中必问和必会的模块,咱这么说吧,多态就是多种形态,当去完成某个行为时候,不同对象去完成会有不同的状态。
  • 晦涩难懂的话,举个栗子哈,吃饭这一种行为就有很多形态,比如人的吃饭和狗狗,猫咪的吃饭就是不同的动作,这就反映了多态这一概念,同一行为不同的对象去完成有不同的效果。

    那到这里,你会不会跟我之前有同样的疑问?–封装可以使得代码模块化,继承可以扩展已经存在的代码,他们的目的都是为了代码重用。那多态的作用是什么呢???其实多态的目的就是为了接口重用,函数都能通过同一个接口调用到适应各自对象的实现方法。

二、多态的形成条件与实现

2.1 形成条件(三个必要条件缺一不可!!!)

  • 要有继承
  • 要有重写(函数名相同,参数相同,返回值类型相同)
  • 父类的引用指向子类的对象

  • 一句话概括:在基类的函数前面上virtual关键字,在派生中重写该函数
  • 运行时会根据对象的实际类型来调用响应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数

2.2 多态的实现

以下是关于多态的简单实现,结果截图如下图:

#include <iostream>
#include <stdlib.h>
using namespace std;

class animal 
{
public:
	virtual void sleep()
	{
		cout << "animal sleep......" << endl;
	}
};
class fish :public animal
{
	void sleep()
	{
		cout << "fish sleep......" << endl;
	}
};
int main()
{
	fish fh;
	animal *ani = &fh;
	ani->sleep();
	system("pause");
	return 0;
}

在这里插入图片描述

三、多态的实现原理

我们对以上结果做一个剖析(虽然很长,但是很管用):编译器在编译的时候,发现animal类中有虚函数,此时编译器会为每个包含虚函数的类创建一个虚表,该表是一个一维数组,在这个虚表中存放着每个虚函数的地址。那基类也有虚函数,子类也有虚函数(当基类的成员函数时虚函数时,默认子类也有虚函数),那如何定位虚表呢???(满脸问号),往下看,其实编译器还为每个类的对象提供一个虚表指针vptr,这个指针指向对象所属类的所有虚表,当程序运行的时候,编译器会根据对象的类型去初始化vptr,而让vptr正确的指向所属类的虚表。

在以上程序中对象ani实际指向的对象类型是fish,因此vptr指向的是fish类的虚表。当调用sleep函数的时候,根据虚表中的函数地址找到的就是fish类的sleep函数,那现在对执行结果没有什么疑问了吧。

那我现在有疑问了,哈哈哈,既然说程序运行的时候是根据虚表指针来找到对象所调用的虚函数,那虚函数的初始化一定很重要,虚函数是如何进行初始化的???或者说在啥地方进行初始化呢???别急,往下看,其实答案是在构造函数中进行虚表的创建和初始化,当我们实例化一个派生类的对象时,他首先会调用基类的构造函数,那就会先初始化虚表指针vptr,让其指向基类的虚表,当调用子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表,在以上程序中,fish的对象fh构造完成后,虚表指针已经指向fish类的虚表了,在类型转换后,调用ani->sleep(),由于ani最终指向的是fish类的对象,而虚表指针又是指向fish的虚表,因此最终调用的才是fish类的sleep函数

  • 总而言之,言而总之,对于虚函数的调用来说,每个对象内部都有一个虚表指针,该虚表指针会被初始化为指向本类的虚表,所以程序在运行时,无论你的对象类型怎么变化,他里面的虚表指针是不会发生变化的,它永远都会指向自己的虚表,而不会指向别的类的虚表,所以才会实现动态的函数的重用。

四、多态其他的概念

  • 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数
  • 存在虚函数的类都有一个一维的虚函数表叫做虚表。类的对象有一个指向虚表开始的虚指针。虚表是和类对应的。
  • 多态性是一个接口的多种实现,是面向对象的核心,分为类的多态性和函数的多态性
  • 多态用虚函数来实现,结合动态绑定
    什么是虚函数?
    虚函数就是在基类中使用关键字virtual声明的函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数
    什么是纯虚函数?
    你可能想要在基类中定义虚函数,以便在派生类中重新定义该函数以便更好的适用于对象,但是你又想在基类中不能对虚函数给出有意义的实现,这个时候纯虚函数就要发挥自己的作用了
class animal   //这是基类
{
public:
	virtual void sleep()
	{
		cout << "animal sleep......" << endl;
	}
	virtual void eat() = 0;   //声明为纯虚函数
};

看到了吗,已经将基类的eat函数声明为纯虚函数了,就是在告诉编译器,函数没有主体。那派生类就可以重新定义eat函数,赋予它有意义的操作

五、抽象类

抽象类:抽象类,描述世界上一些比较泛化不具体的类型,这些类型找不出具体的对象,如人,动物,是一些比较宽泛的类。

  • 包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
  • 派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
  • 纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
  • 我们可以说,其实虚函数是一种强制派生类重写虚函数的机制,因为派生类若不重写虚函数,则无法实例化出对象。
    这里有个例子,有兴趣的可以看看:
#include <iostream>
#include <stdlib.h>
using namespace std;
class Car   //基类
{
public:
	virtual void Drive() = 0;//纯虚函数
};
class Benz :public Car   //子类Benz
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};
class BMW :public Car   //子类BMW
{
public:
	virtual void Drive()
	{
		cout << "BMW-操控" << endl;
	}
};
int main()
{
	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();
	system("pause");
	return 0;
}

在这里插入图片描述
有什么问题希望广大网友提出,一定虚心采纳

发布了33 篇原创文章 · 获赞 13 · 访问量 1042

猜你喜欢

转载自blog.csdn.net/Vicky_Cr/article/details/105033872
今日推荐