C++高频(五)之虚函数
C++面试高频(五)之虚函数
1.虚函数可以是模板函数吗?⭐
虚函数不可以是模板函数,模板函数的实例化是在编译器编译整个程序期间发生的,而虚函数的调用是在运行时才确定的。
这确实是一个重要的区别,模板函数在编译期间会根据使用的类型生成相应的实例,而虚函数需要在运行时根据对象的实际类型进行调用。
对于含有虚函数的类,编译器需要为每个类生成一个虚函数表(vtable),以便在运行时进行动态绑定。虚函数表中存储着该类的虚函数的地址。而模板函数的实例化个数是在整个程序被编译完成之后才确定的,编译器无法为模板函数生成固定数量的虚函数表。
因此,C++ 编译器不允许将虚函数声明为模板函数。只有普通的成员函数可以模板化。
2.请你说说虚函数的工作机制⭐⭐⭐
- 在有虚函数的类中,当类实例化为对象时,会在对象的内存布局中添加一个指向虚函数表的指针。这个指针通常位于对象最开始的位置,也就是对象的 vptr(虚表指针)。
- 虚函数表是一个静态的表格,保存了类中所有虚函数的地址。这个虚函数表在内存中的位置通常是在代码段(.text)中,而不是在对象的实际内存中。
- 当子类继承了父类的时候,子类对象也会继承父类的虚函数表。当子类重写(override)父类中的虚函数时,会将虚函数表中对应的函数地址替换为子类的虚函数地址,从而实现了动态绑定和多态。
- 运行时,通过对象的 vptr 指针来访问虚函数表,并根据表中存储的函数地址调用相应的虚函数。这个调用过程是动态的,会根据实际对象的类型来选择正确的虚函数实现。
- 虚函数的实现确实会增加访问内存的开销,因为需要通过 vptr 指针来访问虚函数表,并进行间接的函数调用。这可能会带来一些性能上的损失。对于不需要多态性的函数,可以选择将其声明为非虚函数,以提高性能。
总结一下,虚函数的实现方式通常包括在对象中添加一个指向虚函数表的指针(vptr),虚函数表存储了虚函数的地址,子类继承并重写父类的虚函数时会替换相应的地址,通过 vptr 指针和虚函数表来实现动态绑定和多态。虚函数的实现会带来额外的内存访问开销。
3.虚函数表在什么时候创建?每个对象都有一份虚函数表吗?
- 虚函数表在编译阶段由编译器创建,并且对于每个类都只会创建一份虚函数表。每个类只有一个虚函数表。
- 虚函数表是类级别的静态成员,存储了类中所有虚函数的地址。
- 每个对象中包含一个虚函数表指针(vptr),它指向了所属类的虚函数表。每个对象通过自己的虚表指针来访问类的虚函数表。
- 对象之间共享类的虚函数表,它们的虚表指针指向同一个虚函数表。
- 每个类的派生类继承了基类的虚函数表,并可以在派生类中扩展和重写虚函数。派生类的虚函数表会包含基类的虚函数,并添加派生类自己的虚函数。派生类的虚函数表会替代基类的虚函数表。.
- 虚函数表只有一份,而有多少个对象,就对应多少个虚函数表指针。
4.请说说操作符重载?哪些操作符不能重载?⭐⭐
操作符重载是一种特殊的函数重载,可以使得某些运算符在对特定对象进行操作时具有自定义的行为。通过重载操作符,可以为自定义的类类型创建与内置类型相似的语法和行为。
当谈到操作符重载时,以下是一个简单的示例,展示了如何重载加法操作符(+)来实现两个自定义对象的相加:
#include <iostream>
class MyNumber {
private:
int value;
public:
MyNumber(int val) : value(val) {}
MyNumber operator+(const MyNumber& other) {
return MyNumber(value + other.value);
}