1、静态绑定和动态绑定
-
静态绑定:
-
在编译时确定调用的函数。
-
适用于非虚函数(包括非虚析构函数)。
-
编译器根据指针或引用的静态类型(声明类型)来决定调用哪个函数。
-
-
动态绑定:
-
在运行时确定调用的函数。
-
适用于虚函数(包括虚析构函数)。
-
编译器根据对象的动态类型(实际类型)来决定调用哪个函数。
-
2、子类的“虚表”是如何产生的?
-
多态:
-
满足条件:①有继承关系;②子类重写父类中的虚函数
-
使用条件:父类指针或引用指向子类对象
-
当一个类中包含有虚函数(被virtual修饰的成员函数)时,编译器在编译阶段会为该类创建一张表叫做“虚函数表”,简称“虚表”,存储当前类中的虚函数指针,虚表本质上是一个函数指针数组。
每个对象内部都有一个隐藏的虚表指针(vptr),指向其所属类的虚表。vptr 在对象构造时被初始化,指向正确的虚表。
-
子类虚表的产生:
-
子类继承父类的虚表:子类会拷贝一份父类的虚表。
-
子类覆盖重写的虚函数:如果子类重写了父类的虚函数,子类的虚表中对应的函数指针会被替换为子类的函数地址。重写:函数的返回值类型、函数名、形参列表类型都相同。
-
子类添加额外的虚函数:子类新增的虚函数会依次添加在虚表的末尾。
-
非虚函数不会被放入虚表:只有虚函数会被放入虚表,非虚函数的调用是静态绑定的。
-
虚表指针(vptr)属于对象,存储在对象的内存布局中。虚表(vtable):属于类,存储在程序的常量区(只读数据段)。
以下是一个示例,展示 vptr 和虚表的关系:
3、关键字final和override
-
final
可以用于类或虚函数:-
用于类时,表示该类不能被继承,为最终类。
-
用于父类虚函数时,表示该函数不能在子类中被重写。
-
-
override
:-
用于显式标记子类中的函数是重写父类的虚函数。
-
如果标记了
override
,但实际没有重写父类的虚函数,编译器会报错。
-
4、协变—一种特殊的返回值类型规则
-
协变的基本规则
-
返回类型必须是指针或引用。
-
子类的返回类型必须是父类返回类型的子类。
-
5、虚析构与纯虚析构
虚析构函数的主要作用是确保通过父类指针删除子类对象时,能够正确调用子类的析构函数,从而避免资源泄漏。子类没有显式定义析构函数,编译器也会生成一个默认的析构函数,并覆盖父类的析构函数。若子类中有属性开辟到堆区,需要显式定义析构函数。
6、父类析构函数怎么被调用的?
-
子类的析构函数
Derived::~Derived
会先执行子类的析构逻辑。 -
在
Derived::~Derived
执行完毕后,编译器会自动插入代码调用父类的析构函数Base::~Base
。这个过程是隐式的,不需要程序员显式编写。
调整心态,脚步不停下,就是在进步。