在上篇博客中,简单提了一下C++的第一个特点继承,今天就来聊聊第二个特点:多态
一、多态的概念
1、什么是多态呢?简单来说,多态就是面向对象的重要特性,也可以说是同一种事物的不同体现,或者是同一事物表现出得多种形态。
2、多态的分类
静态多态:编译器在编译期间完成,编译器根据函数实参的类型(可能会进行隐式转换)。
动态多态:在程序运行期间(非编译期间)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
3、动态多态实现的两个条件:
(1)调用虚函数--->一定在派生类中对基类的虚函数进行重写
重写(覆盖):在继承体系中,如果基类中有虚函数,在派生类中有和基类虚函数原型相同的虚函数。
重写的特例:1>协变:基类返回基类指针,派生类返回派生类指针
2>析构函数:基类调用基类析构函数 ~Base() 派生类调用派生类构造函数 ~Derived()
(2)通过基类的指针或引用来调用虚函数
二、动态多态的实现及其原理
多态通过虚函数结合动态绑定来实现。
虚函数:通过关键字virtual所修饰的成员函数。
在讲动态多态的实现之前,必须先来讲一下虚表以及上篇提过的对象模型
先看一下下面的代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class A
{
public:
void Test1()
{
cout << "A::Test1()" << endl;
}
};
class B
{
public:
virtual void Test1()
{
cout << "B::Test1()" << endl;
}
};
int main()
{
cout <<"A的大小" << sizeof(A) << endl;
cout <<"B的大小" << sizeof(B) << endl;
system("pause");
return 0;
}
输出结果及分析1、普通函数的调用:call函数地址
虚函数的调用: 通过查询虚表
2、先来提两个问题
1>同一个类的对象是否公用一张虚表? 答案:是,公用同一张虚表
2>派生类是否和基类公用一张虚表? 答案:否,不共用
下面就重点讲一下虚表的构造
基类中的虚表:虚函数在类中的申明次序
派生类中的虚表:
1、单继承
1>先拷贝基类中的虚表
2>检测派生类中是否对基类中的虚函数进行重写----是->用派生类中重写的虚函数来替代相同偏移量位置的基类虚函数
3>在虚表之后添加派生类自己的虚函数
在此处说一下虚函数的调用:1>取对象地址2>取对象前四个字节中的内容—>虚表指针3>传this指针4>调对应指针
下面就用几行代码来通俗的演示及解释一番
class A { public: virtual void Test1() { cout << "A::Test1()" << endl; } private: int _a; }; class B:public A { public: //重写基类虚函数 virtual void Test1() { cout << "B::Test1()" << endl; } //派生类新定义的虚函数 virtual void Test2() { cout << "B::Test2()" << endl; } private: int _b; }; typedef void(*Pfun)(); //遍历虚表内容 void Display(const A&a) { Pfun*pfun = (Pfun*)*(int*)&a; while (*pfun) { (*pfun)(); ++pfun; } } void test() { A a; B b; Display(a); Display(b); } int main() { test(); system("pause"); return 0; }
通过对结果的分析可以看出在派生类的对象模型中,不仅继承了基类中的,而且也对在派生类中重写的虚函数做了修改,然后再下边补上了派生类自己的虚函数。
2、多继承
在第一张虚表之后加上派生类自己的虚函数,其他与单继承类似。
将派生类的虚函数加在第一张虚表之后的好处:效率快,仅访问前四个字节就能访问到派生类虚函数。
class A { public: virtual void Test1() { cout << "A::Test1()" << endl; } virtual void Test2() { cout << "A::Test2()" << endl; } private: int _a; }; class B { public: virtual void Test3() { cout << "B::Test3()" << endl; } private: int _b; }; class C:public A,public B { public: //重写基类虚函数 virtual void Test1() { cout << "C::Test1()" << endl; } virtual void Test3() { cout << "C::Test3()" << endl; } //派生类新定义的虚函数 virtual void Test4() { cout << "C::Test4()" << endl; } private: int _c; }; typedef void(*Pfun)(); //遍历虚表内容 void Display(const A&a) { //访问A的虚表 Pfun*pfun = (Pfun*)*(int*)&a; while (*pfun) { (*pfun)(); ++pfun; } //跳过A的内容访问B的虚表 pfun = (Pfun*)*(int*)((char*)&a + sizeof(A)); while (*pfun) { (*pfun)(); ++pfun; } } void test() { C c; Display(c); } int main() { test(); system("pause"); return 0; }
对代码的解读如下:
多继承:先继承的基类的虚表在前,再把派生类自己的虚函数加在第一张虚表之后,再把第二个继承的基类的虚表接在后面。
3、菱形继承
1)带虚函数的虚拟继承
在此继承的体系下,则对象的对象模型需要再多四个字节,用来存放偏移量表格。
<1>先看派生类仅重写了基类的虚函数,而无自己的虚函数的情况
class A { public: virtual void Test1() { cout << "A::Test1()" << endl; } int _a; }; class B:virtual public A { public: virtual void Test1() { cout << "B::Test1()" << endl; } int _b; }; int main() { cout << sizeof(B) << endl; A a; B b; b._a = 0; b._b = 1; system("pause"); return 0; }
<2>先看派生类不仅重写了基类的虚函数,而且拥有自己的虚函数的情况
class A { public: virtual void Test1() { cout << "A::Test1()" << endl; } int _a; }; class B:virtual public A { public: virtual void Test1() { cout << "B::Test1()" << endl; } virtual void Test2() { cout << "B::Test2()" << endl; } int _b; }; int main() { cout << sizeof(B) << endl; A a; B b; b._a = 0; b._b = 1; system("pause"); return 0; }
2)菱形继承
class A { public: virtual void Test1() { cout << "A::Test1()" << endl; } virtual void Test2() { cout << "A::Test2()" << endl; } int _a; }; class B1:virtual public A { public: virtual void Test1() { cout << "B1::Test1()" << endl; } virtual void Test3() { cout << "B1::Test3()" << endl; } int _b1; }; class B2 :virtual public A { public: virtual void Test1() { cout << "B2::Test1()" << endl; } virtual void Test4() { cout << "B2::Test4()" << endl; } int _b2; }; class D :public B1,public B2 { public: virtual void Test1() { cout << "D::Test1()" << endl; } virtual void Test3() { cout << "D::Test3()" << endl; } virtual void Test4() { cout << "D::Test4()" << endl; } virtual void Test5() { cout << "D::Test5()" << endl; } int _d; }; typedef void(*FunPtr)(); void Print(A& a) { FunPtr* pfun = (FunPtr*)(*(int*)&a); while (*pfun) { (*pfun)(); pfun++; } cout << endl; } void Print(B1& b1) { FunPtr* pfun = (FunPtr*)(*(int*)&b1); while (*pfun) { (*pfun)(); pfun++; } cout << endl; } void Print(B2& b2) { FunPtr* pfun = (FunPtr*)(*(int*)&b2); while (*pfun) { (*pfun)(); pfun++; } cout << endl; } int main() { cout << sizeof(D) << endl; D d; B1& b1 = d; Print(b1); B2& b2 = d; Print(b2); A& a = d; Print(a); return 0; }
对于上述带虚函数的菱形继承的代码根据内存块的分析
以上就是我对多态的一点小小总结,希望各位大佬们多指点。