一、什么是多态?
- 多态是每次面试和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;
}
有什么问题希望广大网友提出,一定虚心采纳