什么是多态呢?
字面意思就是同一事物有多种形态。
在面向对象程序设计中,多态指的是接口的多种不同的实现方式。编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。
对不同类的对象发出相同的消息将会有不同的行为。
那么多态又分为静态多态和动态多态。
静态多态:静态类型,对象声明时的类型,在编译期间就确定下来了,实现方式有:函数重载,泛型编程。
动态多态:动态类型,目前所指对象类型,在程序运行期间所调用的类型。实现方式有:虚函数。
动态多态产生的条件:
1:在继承体系下,基类必须要有虚函数,派生类中必须要对基类的虚函数进行重写(函数原型必须一模一样)。
2:通过基类的指针或者引用来调用。
协变:重写基类虚函数时返回值为基类/派生类的指针或者引用
多态的作用:
1. 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承
2. 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。 //多态的真正作用,以前需要用switch实现
多态的使用:
多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定。
通过例子看多态的实现:
#include <iostream>
using namespace std;
//静态多态
int Add(int left, int right)
{
return left + right;
}
float Add(float left, float right)
{
return left + right;
}
int main()
{
cout<< Add(10, 20) << endl;
cout << Add(12.34f, 34.12f) << endl;
system("pause");
return 0;
}
//静态多态:编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
【动态多态】
动态绑定:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。
class A
{
public:
virtual void Funtest()
{
cout << "A::Funtest" << endl;
}
public:
char* _a;
};
class B:public A
{
virtual void Funtest()
{
cout << "B::Funtest" << endl;
}
public:
char* _b;
};
int main()
{
B b;
A& a1 = b;//发生动态绑定
a1.Funtest();
system("pause");
return 0;
}
【动态绑定条件】
1、必须是虚函数
2、通过基类类型的引用或者指针调用虚函数
【纯虚函数】
在成员函数的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
class Person
{
virtual void Display () = 0; // 纯虚函数
protected :
string _name ; // 姓名
};
class Student : public Person
{};
总结:
1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
3、只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。
4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。
5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆
6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为。
7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊理)
8、虚表是所有类对象实例共用的
【虚表剖析】
什么是虚表呢?
类的对象里面有虚函数那么在类对象模型中前四个字节存放的就是虚表指针!
//class CTest
//{
//public:
// CTest()
// {
// iTest = 10;
// }
//private:
// int iTest;
//};
//int main()
//{
// cout << sizeof(CTest) << endl;//结果为4
// system("pause");
// return 0;
//}
class CTest
{
public:
CTest()
{
iTest = 10;
cout << "this = " << this << endl;
}
virtual ~CTest() {};
private:
int iTest;
};
int main()
{
CTest test;
cout << sizeof(test) << endl;//大小为8
system("pause");
}
【没有覆盖的情况下】
1:虚函数按照其声明的顺序存在于虚表中
2:在派生类中,前面是基类的虚函数,后面是派生类中虚函数
【有覆盖的情况下】
通过基类的引用或者指针调用虚函数,调用基类还是派生类的虚函数,要根据运行时根据引用(指针)实际引用(指向)的类型确定调用非虚函数,则无论基类指向的是何种类型,都调用的是基类的函数。
派生类虚函数表的生成:
1:先拷贝基类的虚函数表
2:如果派生类重写了基类的某个虚函数,就覆盖同位置的基类虚函数
3:跟上派生类自己新定义的虚函数