1 虚函数
函数声明前加上virtual。
如果定义的类将被用做是基类,则应该将那些要在派生类中重新定义的类方法声明为虚的。
如果该类不作为基类或者派生类不会重新定义基类的任何方法,那么就没有必要声明为虚函数。因为虚函数会消耗内存增加处理时间,如无必要,无需添加。
虚函数的存在是为了实现多态,方法的行为应取决于调用该方法的对象。一个接口,灵活调用。
当用一个父类指针或引用指向子类的对象时,在实际使用过程中,能够根据对象类型正确调用子类的函数。
这也就是动态联编,即编译器生成能够在程序运行时选择正确的虚方法的代码。
2 虚函数表和虚函数表指针
虚函数表是类成员,虚函数指针是类对象成员。
虚函数表的元素值是虚函数的入口地址。
在实例化派生类对象时,先实例化基类,将基类的虚函数表入口地址复制给基类的虚表指针,当基类构造函数执行完毕,再将派生类的虚函数表入口地址复制给基类的虚表指针,再执行派生类的构造函数。
虚表的构建:基类的虚表构建,先填上虚析构函数的入口地址,之后所有虚函数的入口地址按在类中声明顺序填入虚表;派生类的虚表构建,先将基类的虚表内容复制到派生类虚表中,如果派生类覆盖了基类的虚函数,则虚表中对应的虚函数入口地址也会被覆盖,为了后面寻址的一致性。
虚函数表中有序存放了父类和子类中所有的虚函数。
相同的虚函数在类继承链中的每一个虚函数表中的偏移量都是一致的,所以确定的虚函数在虚函数表中就对应一个固定的位置n,n是一个在编译期间就确定的常量。
所以vptr+n就可以得到对应函数入口地址。
C++就采用这种绝对地址+偏移量的方法调用虚函数,查找时间复杂度为o(1)。
3 构造函数
不能是虚函数。
因为创建派生类对象,将调用派生类的构造函数,而不是积累的构造函数,然后派生类的对象将使用基类的一个构造函数。
派生类不继承基类的构造函数,因此将构造函数定义为虚函数没有意义。
4 析构函数
如果该类作为基类,那么析构函数应该是虚函数。
因为如果析构函数不是虚函数,那么属于静态联编,在delete指向派生类对象的基类指针时,只会调用基类的析构函数,那么只能释放基类指向的不分内存,派生类中新增成员的内存则不会释放,造成内存泄漏。
但如果析构函数是虚函数,那么就会先调用派生类的析构函数,然后再调用基类的析构函数。
所以通常给基类添加一个虚析构函数
5 友元函数
不是虚函数。
因为友元函数不示类成员,只有类成员才能是虚函数。
6 重载&重写&重定义
6.1 重载
-有相同的作用范围(同一个类中)
-函数名字相同
-参数不同
-virtual关键字可有可无
6.2重写/覆盖
指派生类函数覆盖基类函数。
-作用范围不同,一个在基类,一个在派生类
-函数名字相同
-参数相同
-基类函数必须有virtual
6.3 重定义/隐藏
指派生类的函数屏蔽了与其同名的基类函数。
-参数不同,无论基类有没有virtual,基类函数被隐藏。
-参数相同,但是基类没有virtual,基类函数被隐藏。
7 纯虚函数
纯虚函数声明开头是virtual,结尾是=0.
通过纯虚函数提供未实现的函数。
当类声明中包含纯虚函数,则不能创建该类的对象,只能当做抽象基类(ABC)。