电子说
一、多态的概念
在说多态之前,我们来先看一看对象的类型
来看一个例子:
多态:意思既是同一个事物的多种形态,用我们C++的专业词语来说就是:一个借口、多种实现方式。
二、多态分类
静态多态:
静态多态:编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
动态多态:
动态多态就是我们常说的多态。动态多态是在程序运行期间才决定调用哪个函数,是根据虚函数表实现的。声明了虚函数的类,类中都有一张虚函数表,里面存放类的入口地址。通过赋值兼容规则,可以用父类的指针或引用找到子类的虚函数。虚函数是处理类的派生体系中不同层次上不同作用域的同名问题,因此动态多态必须在类的继承体系中才能实现。
三、多态的实现原理(动态)
说到这里,有几个非常重要的概念,需要我们加以区分:重载、重写、重定义
通过上面的多态的介绍,这里来说几个重要的概念:
(1)虚函数表指针:类中除了定义成员函数之外还有一个成员是虚函数表指针(占四个基本内存单位),这个指针指向一个虚函数表的起始位置,这个表会与类的定义同时出现,这个表会与类的定义同时出现,这个表存放着该类的虚函数指针,调用的时候可以找到该类虚函数表指针,通过虚函数表指针找到虚函数表,通过虚函数表的偏移找到函数的入口地址,从而找到要使用的虚函数。
(2)当实例化一个该类的子类对象的时候,(如果)该类的子类并没有定义虚函数,但是却从父类中继承了虚函数,所以在实例化该类子类对象的时候也会产生一个虚函数表,这个虚函数表是子类的虚函数表,但是记录的子类的虚函数地址却是与父类的是一样的。所以通过子类对象的虚函数表指针找到自己的虚函数表,在自己的虚函数表找到的要执行的函数指针也是父类的相应函数入口的地址。
(3)如果我们在子类中定义了从父类继承来的虚函数,对于父类来说情况是不变的,对于子类来说它的虚函数表与之前的虚函数表是一样的,但是此时子类定义了自己的(从父类那继承来的)相应函数,所以它的虚函数表当中管于这个函数的指针就会覆盖掉原有的指向父类函数的指针的值,换句话说就是指向了自己定义的相应函数,这样如果用父类的指针,指向子类的对象,就会通过子类对象当中的虚函数表指针找到子类的虚函数表,从而通过子类的虚函数表找到子类的相应虚函数地址,而此时的地址已经是该函数自己定义的虚函数入口地址,而不是父类的相应虚函数入口地址,所以执行的将会是子类当中的虚函数。这就是多态的原理。
(4)纯虚函数
在成员函数的参数列表后面写上“=0”则该成员函数为纯虚函数。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
纯虚函数在抽象类中重新定义以后,派生类才能实现实例化出对象。
总结:
(1)派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同(协变除外)
协变:基类和派生类中的虚函数名字和参数列表相同、返回值类型不同,基类中的虚函数返回Base*,派生类中的虚函数返回Derived*
(2)基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
(3)只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。
(4)如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。
(5)构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆
(6)不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为。
(7)最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
(8)虚表是对所有类对象实例共用的
全部0条评论
快来发表一下你的评论吧 !