什么是多态
●多态就是同一事物在不同环境下的不同状态。
多态的分类
●静态多态:编译器在编译期间来确定程序具体调用哪个函数
①函数重载:https://blog.csdn.net/h___q/article/details/80663135
②泛型编程
●动态多态:在程序运行时来确定程序具体调用哪个函数
动态多态的实现条件
●基类中必须包含虚函数,在派生类中必须对基类的虚函数进行重写
●必须通过基类的指针或引用调用虚函数
什么是虚函数
●虚函数的定义即就是在函数的定义前加上virtual关键字。
●查阅C++primer得知,在C++语言中,当我们使用基类的引用或指针调用一个虚成员函数时会进行动态绑定。因为我们直到运行时才能知道到底使用了哪个版本的虚函数,所以所有的虚函数都必须有定义。
●当某个虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定究竟要调用哪一个版本的虚函数。
注意:
●只有类的非静态成员函数才能定义为虚函数,静态成员不能定义为虚函数。
●如果在类外定义虚函数,只在声明时加上virtual关键字,定义时不用加。
●构造函数不能定义为虚函数,虽然可以将operator=(操作符的重载)定义为虚函数,但是最好不要这样做,使用时容易混淆。
●不要在构造函数和析构函数中调用虚函数,在构造函数或析构函数中,对象是不完整的,可能出现未定义的行为。
●最好将基类的析构函数声明为虚函数。
什么是对虚函数的重写
●首先将要被重写的来自基类的函数必须为虚函数。
●对虚函数的重写就是在派生类中,写一个与基类中虚函数原型完全一致(返回值相同,函数名相同,参数列表相同)的函数来覆盖虚函数中的函数内容。
●当我们对某个来自基类的虚函数进行重写时,可以再一次使用virtual关键字指出该函数的性质。但是这并不是必须的,因为一旦一个函数被声明为虚函数,则在所有派生类中它都是虚函数。
例外:
①协变(返回值类型不同):当一个虚函数在基类中的返回值为基类的指针(this指针)或引用时,派生类中对该函数的重写也应该返回派生类的指针(this指针)或引用。
②析构函数的重写(最好将基类的析构函数声明为虚函数):析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是仍然构成重写,编译器在这里做了特殊处理。
抽象类
●在成员函数(必须是虚函数)的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类称为抽象类(也叫接口类),抽象类不能实例化出对象,纯虚函数在派生类中重新定义之后,派生类才能实例化出对象。
//创建一个抽象类
class Base
{
public:
virtual void BaseTest() = 0;//纯虚函数
};
int main()
{
Base b;//尝试用抽象类实例化对象
return 0;
}
进行编译时编译器报错:
实现动态多态
当你走在街上,看到一个人,要是你想要与这位人进行交谈,那么称呼肯定是必须的,如果这位人是男士,你就应该称他为帅哥,如果是女士,就应该称她为美女,如果是小孩子,那就应该称他为小朋友,这就是一个实际生活中多态的例子:
#include<iostream>
using namespace std;
class YouSay//定义称呼的类模型
{
public:
void IsAMan()//如果是男
{
cout << "hi 帅哥" << endl;//打印hi 帅哥
}
void IsAWoman()//如果是女
{
cout << "hi 美女" << endl;//打印hi 美女
}
void IsAChild()//如果是小朋友
{
cout << "hi 小朋友" << endl;//打印hi 小朋友
}
};
class Person//定义用来指向人的抽象类
{
public:
virtual void YouWillSay(YouSay& y) = 0;//定义称呼的函数
};
class Man :public Person//定义男士类让它公有继承自person类
{
public:
virtual void YouWillSay(YouSay& y)//重写继承自person类的纯虚函数
{
y.IsAMan();//调用遇到男士的称呼函数
}
};
class Woman :public Person//定义女士类让它公有继承自person类
{
public:
virtual void YouWillSay(YouSay& y)//重写继承自person类的纯虚函数
{
y.IsAWoman();//调用遇到女士的称呼函数
}
};
class Child :public Person//定义小孩子类让它公有继承自person类
{
public:
virtual void YouWillSay(YouSay& y)//重写继承自person类的纯虚函数
{
y.IsAChild();//调用遇到小孩子的称呼函数
}
};
int main()
{
YouSay y;//创建称呼的实例化对象
Person* p;//创建一个可以指向人的类类型的指针
p = new Man;//让p先指向一个男士
p->YouWillSay(y);//通过男士的对象调用要称呼的函数
p = new Woman;//让p指向一个女士
p->YouWillSay(y);//通过女士的对象调用要称呼的函数
p = new Child;//让p指向一个小孩子
p->YouWillSay(y);//通过小孩子的对象调用要称呼的函数
return 0;
}
运行结果:
可以看到,我们调用的始终是相同的函数,但是它的结果是不同的。
动态多态调用原理
●创建对象时,在对象的前4个字节内会存放一个指针,这个指针指向一个虚函数列表
●如果对象是基类对象,那么就会将基类中虚函数的地址按照其声明次序依次添加到虚表中,在vs2013环境下会以00 00 00 00作为虚函数列表结束的标志。
●如果对象是派生类对象,那么就会先将基类的虚函数表拷贝一份,如果派生类重写了基类的某个虚函数,就用派生类中的虚函数地址替换相同偏移量位置的基类虚函数地址,将派生类自己新添加的虚函数地址按照其声明次序添加到虚函数表的最后,在vs2013环境下会以00 00 00 00作为虚函数表的结束标志。
调用过程:
①从对象的前四个字节取出虚函数表的地址。
②传递参数(虚函数形参+this指针)。
③从虚函数表中取出虚函数的地址。
④调用该虚函数。