MST

星途 面试题库

面试题:C++ 类中的多态实现细节

请描述 C++ 中静态多态和动态多态的实现方式,各举一个具体的代码示例。在动态多态中,虚函数表(vtable)和虚函数指针(vptr)的作用是什么?如果一个类中有多个虚函数,vtable 和 vptr 是如何工作的?请详细解释。
37.6万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

静态多态

静态多态是通过函数重载和模板实现的,在编译期就确定调用哪个函数。

函数重载示例

#include <iostream>

class Calculator {
public:
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }
};

int main() {
    Calculator calc;
    std::cout << "Int addition: " << calc.add(3, 5) << std::endl;
    std::cout << "Double addition: " << calc.add(3.5, 5.5) << std::endl;
    return 0;
}

模板示例

#include <iostream>

template <typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    std::cout << "Int addition: " << add(3, 5) << std::endl;
    std::cout << "Double addition: " << add(3.5, 5.5) << std::endl;
    return 0;
}

动态多态

动态多态是通过虚函数和指针或引用来实现的,在运行期才确定调用哪个函数。

示例

#include <iostream>

class Animal {
public:
    virtual void speak() {
        std::cout << "I am an animal" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "I am a dog, woof!" << std::endl;
    }
};

class Cat : public Animal {
public:
    void speak() override {
        std::cout << "I am a cat, meow!" << std::endl;
    }
};

int main() {
    Animal* animal1 = new Dog();
    Animal* animal2 = new Cat();

    animal1->speak();
    animal2->speak();

    delete animal1;
    delete animal2;
    return 0;
}

虚函数表(vtable)和虚函数指针(vptr)的作用

  • 虚函数表(vtable):是一个存储类虚函数地址的数组。每个包含虚函数的类都有一个对应的虚函数表。虚函数表为每个虚函数在表中分配一个条目,存储该虚函数的实际地址。如果派生类重写了基类的虚函数,虚函数表中该函数的条目将更新为派生类中重写函数的地址。
  • 虚函数指针(vptr):每个包含虚函数的类的对象都有一个虚函数指针。这个指针指向该对象所属类的虚函数表。当通过指针或引用调用虚函数时,程序会首先通过虚函数指针找到虚函数表,然后在虚函数表中找到对应的虚函数地址并调用。

多个虚函数时 vtable 和 vptr 的工作方式

当一个类中有多个虚函数时,虚函数表会为每个虚函数分配一个条目,按照它们在类中声明的顺序排列。每个虚函数的地址被存储在对应的条目中。对象的虚函数指针仍然指向该类的虚函数表。

例如,假设有如下类:

class Base {
public:
    virtual void func1() {}
    virtual void func2() {}
    virtual void func3() {}
};

Base类的虚函数表会有三个条目,分别存储func1、func2、func3的地址。如果有派生类Derived继承自Base并重写了func2:

class Derived : public Base {
public:
    void func2() override {}
};

Derived类的虚函数表中func2的条目将被更新为Derived::func2的地址,而func1和func3的条目仍然指向Base类中的对应函数(除非Derived也重写了它们)。当通过Base* 或Derived* 指针调用虚函数时,程序通过虚函数指针找到虚函数表,根据调用的虚函数在表中的索引找到对应的函数地址并调用。