1、多态的概念
①多态的字面意思就是同种事物的不同形态
②多态的分类:静态绑定(编译期间确定的多态)和动态绑定(程序运行时确定的多态)
③动态多态的实现条件:前提必须是在继承体系中
>>基类中必须包含有虚函数,在派生类中必须对基类中的虚函数进行重写
>> 在调用虚函数必须通过基类的指针或引用
④表现形式
>>在程序运行时基类的指针或引用指向不同的类对象时,调用通过的虚函数
⑤重写
>>派生类重写基类的虚函数
>>派生类的虚函数原型必须与基类的虚函数原型(函数名+返回值)完全一致,两个例外:协变和析构函数的重写
>>基类虚函数前面必须加virtual关键字,派生类中虚函数前可加可不加,但是建议加上
>>派生类中虚函数访问权限可以与基类虚函数不同
#include <iostream>
using namespace std;
class person {
public:
virtual void Buytick() {
cout << "全票" << endl;
}
};
class student :public person {
public:
virtual void Buytick() {
cout << "半价票" << endl;
}
};
class solider :public person {
public:
virtual void Buytick() {
cout << "免费票" << endl;
}
};
// 此处的参数列表中类型必须为基类的指针或者引用
void TestFunc(person& p) {
p.Buytick();
}
int main() {
person jack;
TestFunc(jack);
student LiMing;
TestFunc(LiMing);
solider Wu;
TestFunc(Wu);
return 0;
}
2、虚函数
概念:被virtual关键字修饰的类成员函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
纯虚函数:想实现多态但是又不想在基类中对函数具体实现,形如:virtual void test() = 0;
虚函数的重写的例外
>>协变(基类和派生类中虚函数的返回类型不同
class person {
public:
virtual person* Func() {
return new person;
}
};
class student :public person {
public:
virtual student* Func() {
return new student;
}
};
>>析构函数的重写(基类和派生类中虚函数的函数名不同)
class person {
public:
virtual ~person() {
cout << "person::~person()" << endl;
}
};
class student :public person {
public:
virtual ~student() {
cout << "student::~student()" << endl;
}
};
3、抽象类:不可以实例化的类对象,即包含纯虚函数的类,也称为接口类
4、单继承与多继承中的虚函数表
>> 虚函数表:包含虚函数的类中至少存在一个虚函数指针,虚函数表就是用于存放这些虚函数的地址的;所以在计算一个类的大小时,如果该类中包含虚函数则需要加上一个虚函数指针的大小
基类虚函数表的生成:
>>在编译期间,按照类中虚函数的声明顺序依次将虚函数的入口地址加入到虚函数中
派生类中的虚函数表生成流程:
>>首先将基类中的虚函数表拷贝到派生类中
>>如果派生类中重写了基类中的某个虚函数,就用派生类中的虚函数替换虚函数表中相同位置的积累中虚函数
>>如果派生类增加了新的虚函数,则将新增加的虚函数加载虚函数表的末尾处
虚函数的调用过程:
>>从类对象的前四个字节中取出虚函数表的地址
>>传递this指针
>>取虚函数地址
>>调用虚函数
注:若一个函数时虚函数,编译器在编译期间调用,若是虚函数则编译器没有直接调用;当多态条件不满足时虚函数调用不通过虚函数表,直接在生成汇编代码时调用;虚函数的调用时间相对普通函数更长
5、多态的原理
子类若重写父类虚函数,在虚函数表中,该函数的地址会被替换,对于存在虚函数的类的对象,在VS中,对象的对象模型的头部存放指向虚函数表的指针,通过该机制实现多态。
6、C++11中关于多态的新特性
override关键字:只能用于派生类虚函数后,用于检查派生类虚函数是否对基类虚函数进行了重写
final关键字:用于基类虚函数前,被final关键字修饰的虚函数不能在派生类进行重写
7、重载、重写、同名隐藏(重定义)的区别
>>重载的两个函数在同一个作用域中,函数名和参数相同
>>重写的两个函数分别在基类和派生类中,并且两个函数必须是虚函数,两个函数的原型必须一致(协变和析构函数的重写除外)
>>重定义:两个同名基类和派生类的同名函数如果没有构成重写就是重定义
8、补充
>> inline函数可以是虚函数吗?答:不能,因为inline函数没有地址,无法把地址放到虚函数表中。
>> 静态成员可以是虚函数吗?答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式 无法访问虚函数表,所以静态成员函数无法放进虚函数表。
>> 构造函数可以是虚函数吗?答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始 化的。
>> 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?答:可以,并且最好把基类的析构函数定义 成虚函数。
>> 对象访问普通函数快还是虚函数更快?答:首先如果是普通对象,是一样快的。如果是指针对象或者是 引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
>> 虚函数表是在什么阶段生成的,存在哪的?答:虚函数是在编译阶段就生成的,一般情况下存在代码段 (常量区)的。
>> C++菱形继承的问题?虚继承的原理?答:参考继承课件。注意这里不要把虚函数表和虚基表搞混了。
>>什么是抽象类?抽象类的作用?抽象类强制重写了虚函数,另外抽象类体现出 了接口继承关系。