C++面试高频(五)之虚函数

1.虚函数可以是模板函数吗?⭐

虚函数不可以是模板函数,模板函数的实例化是在编译器编译整个程序期间发生的,而虚函数的调用是在运行时才确定的。

这确实是一个重要的区别,模板函数在编译期间会根据使用的类型生成相应的实例,而虚函数需要在运行时根据对象的实际类型进行调用。

对于含有虚函数的类,编译器需要为每个类生成一个虚函数表(vtable),以便在运行时进行动态绑定。虚函数表中存储着该类的虚函数的地址。而模板函数的实例化个数是在整个程序被编译完成之后才确定的,编译器无法为模板函数生成固定数量的虚函数表。

因此,C++ 编译器不允许将虚函数声明为模板函数。只有普通的成员函数可以模板化。

2.请你说说虚函数的工作机制⭐⭐⭐

  1. 在有虚函数的类中,当类实例化为对象时,会在对象的内存布局中添加一个指向虚函数表的指针。这个指针通常位于对象最开始的位置,也就是对象的 vptr(虚表指针)。
  2. 虚函数表是一个静态的表格,保存了类中所有虚函数的地址。这个虚函数表在内存中的位置通常是在代码段(.text)中,而不是在对象的实际内存中。
  3. 当子类继承了父类的时候,子类对象也会继承父类的虚函数表。当子类重写(override)父类中的虚函数时,会将虚函数表中对应的函数地址替换为子类的虚函数地址,从而实现了动态绑定和多态。
  4. 运行时,通过对象的 vptr 指针来访问虚函数表,并根据表中存储的函数地址调用相应的虚函数。这个调用过程是动态的,会根据实际对象的类型来选择正确的虚函数实现。
  5. 虚函数的实现确实会增加访问内存的开销,因为需要通过 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);
    }

    int getValue() const {
        return value;
    }
};

int main() {
    MyNumber num1(5);
    MyNumber num2(10);

    MyNumber sum = num1 + num2;

    std::cout << "The sum is: " << sum.getValue() << std::endl;
    
    return 0;
}

然而,并不是所有的操作符都可以被重载。以下操作符不能被重载:

  • 成员选择操作符(.):无法改变点操作符的行为。
  • 展开操作符(::):它用于指定作用域,不能被重载。
  • 条件运算符(?:):无法改变条件运算符的行为。
  • sizeof:它是一个关键字,无法重载。
  • typeid:它是一个运算符,无法重载。

5.什么是纯虚函数

纯虚函数(Pure Virtual Function)是在基类中声明但没有提供实现的虚函数。它的声明形式为在函数原型后面加上= 0。纯虚函数在基类中起到以下作用:

  1. 提供接口定义:纯虚函数在基类中定义了一种接口,规定了派生类必须实现的函数。基类通过纯虚函数定义了一组可供派生类实现的操作,从而实现了接口的定义。

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

c++/嵌入式面经专栏 文章被收录于专栏

BG双9,目前在某外企。打算把之前校招时做的笔记通过专栏发出来,本专栏适合于C/C++、嵌入式方向就业的同学,本篇面经总结数千篇面经的知识集合,实时更新全网最新的嵌入式/C++最新内容,囊括了C语言、C++、操作系统、计算机网络、嵌入式、算法与数据结构、数据库等一系列知识点,在我看来这些是求职者在面试中必须掌握的知识点。最后呢祝各位能找到自己合适的工作。

全部评论
您好,第一个 函数模版不能是虚函数吧?我用你的代码例子无法通过编译。 然后我查了一下:函数模版编译时生成,虚函数调用是运行时绑定的。 而且你写的例子好像是函数模版。
2 回复 分享
发布于 2023-10-29 16:37 广东
第四点 展开操作符(::):它用于指定作用域,不能被重载。 这个好像是作用域解析运算符吧 不确定...
1 回复 分享
发布于 2024-01-06 23:23 江苏
订阅专栏随时更新,最新最全的嵌入式面经
点赞 回复 分享
发布于 2023-10-11 18:17 北京
你好。虚表好像是存储在只读数据段吧
点赞 回复 分享
发布于 2024-06-25 11:17 黑龙江
在构造函数执行期间,对象的虚函数表应该已经填充了吧。
点赞 回复 分享
发布于 2024-09-14 01:30 安徽

相关推荐

评论
4
28
分享

创作者周榜

更多
牛客网
牛客企业服务