-
什么是多态?就是多种形态,具体来讲就是当去完成某个行为时,不同的对象去完成时会产生不同的状态。(即,在不同的继承关系的类对象,去调用同一函数会产生不同的行为。)
-
多态构成的两个必要条件:
1、所调用函数的对象必须是指针或者引用;
2、被调用的函数必须是虚函数,且完成了虚函数的初始化。 -
虚函数:在类的成员函数的前面加上关键字virtual,在编译阶段生成,存在于静态区。
-
虚函数的重写(覆盖):派生类中有一个和基类完全相同(函数名、参数、返回值都相同)的虚函数。
class Person{
public:
virtual void Buyticket(){cout << "全票" << endl;}
};
class Student{
public:
virtual void Buyticket(){cout << "半票" << endl;}
};
-
虚函数重写的例外:协变,即,重写的虚函数的返回值可以不同,但是必须分别是基类指针和派生类指针或者是基类引用和派生类引用。
-
不规范的虚函数的重写行为:父类的被重写的虚函数前面要加上关键字virtual,子类重写的虚函数前可以不加。
-
基类的析构函数最好写成虚函数:基类的析构函数如果是虚函数,那么派生类的析构函数就重写了基类的析构函数,虽然他们的函数名不相同,但是编译器会对析构函数的名称做特殊处理,编译后析构函数的名称就统一处理为destructor。
对析构函数的调用:构成多态------>与对象有关;不构成多态------>与类型有关。 -
实现继承与接口继承:普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现;而虚函数的继承是一种接口继承,目的是为了重写,达成多态,继承的是接口。
-
抽象类(接口类):包含纯虚函数的类,不能实例化出对象。
纯虚函数的作用:实现抽象类;强制派生类重写虚函数。
class Person{
public:
virtual void id() = 0;
};
- override修饰派生类虚函数强制性完成重写,如果没有重写编译器会报错。
class Student{
public:
virtual void id() override {cout << "111" << endl;}
};
- final修饰基类的虚函数不能被派生类重写。
class Person{
public:
virtual void id() final{}
};
-
一个含有虚函数的类中都至少有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中(虚表)。虚函数表中含有一个指针_vfptr(4个字节),可以理解为**_vfptr是一个指针数组**。
-
虚函数表的本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr。
-
派生类的虚表的生成过程(多态的原理):先将基类的虚表内容拷贝一份到派生类虚表中,如果派生类重写了基类中的某个函数,则用派生类自己的虚函数覆盖虚表中基类的虚函数,派生类自己新增加的虚函数按照其在派生类中的声明次序增加到派生类虚表的最后。
-
虚函数表在VS下存在于代码段。
-
满足多态的函数调用,不是在编译时确定的,是运行起来以后到对象中取找的。不满足多态的函数调用是在编译时确认好的。
-
静态绑定(在程序编译期间确定了程序的行为):函数重载,普通调用,模板实现。
-
动态绑定(在程序运行期间):虚函数的实现。
-
inline函数不是虚函数,没有地址,无法把地址放到虚函数表中。
-
静态成员不能为虚函数,没有this指针。
-
构造函数不能为虚函数,虚函数表指针是在构造函数初始化阶段初始化的。
-
当对象是普通对象时,访问虚函数和访问普通函数一样快;当对象是指针对象或引用时,构成多态,调用函数时要去虚函数表里寻找,访问普通函数快。