MST

星途 面试题库

面试题:C++ 指针在多态与虚函数中的深度应用

设计一个C++ 类继承体系,基类`Base`包含一个虚函数`print`。派生出两个子类`Derived1`和`Derived2`,分别重写`print`函数以输出不同的信息。编写一个函数,接收一个`Base`类指针数组,遍历该数组并调用每个指针指向对象的`print`函数。同时,请解释虚函数表、虚指针的工作原理,以及在这种场景下如何通过指针实现动态绑定和多态性,并且分析如果没有正确使用虚函数会导致什么问题。
40.7万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

C++ 类继承体系代码实现

#include <iostream>
#include <memory>

class Base {
public:
    virtual void print() {
        std::cout << "This is Base class" << std::endl;
    }
};

class Derived1 : public Base {
public:
    void print() override {
        std::cout << "This is Derived1 class" << std::endl;
    }
};

class Derived2 : public Base {
public:
    void print() override {
        std::cout << "This is Derived2 class" << std::endl;
    }
};

void callPrint(Base** arr, size_t size) {
    for (size_t i = 0; i < size; ++i) {
        arr[i]->print();
    }
}

你可以这样调用这个函数:

int main() {
    Base* arr[2];
    arr[0] = new Derived1();
    arr[1] = new Derived2();

    callPrint(arr, 2);

    // 释放内存
    for (size_t i = 0; i < 2; ++i) {
        delete arr[i];
    }

    return 0;
}

虚函数表、虚指针工作原理

  1. 虚指针(vptr):当一个类包含虚函数时,该类的每个对象都会有一个额外的指针成员,即虚指针。这个指针指向该对象所属类的虚函数表(vtable)。虚指针在对象构造时被初始化,通常位于对象内存布局的开头位置。
  2. 虚函数表(vtable):虚函数表是一个函数指针数组,其中每个元素是一个虚函数的地址。对于每个包含虚函数的类,编译器会为其生成一个虚函数表。基类的虚函数表包含基类虚函数的地址,派生类的虚函数表首先会复制基类虚函数表的内容,然后如果派生类重写了某个虚函数,会用派生类自己的虚函数地址替换虚函数表中对应的基类虚函数地址。

动态绑定和多态性的实现

  1. 动态绑定:当通过基类指针或引用调用虚函数时,程序在运行时根据指针或引用实际指向的对象类型来决定调用哪个版本的虚函数。这是因为基类指针或引用指向的对象内存布局中包含虚指针,通过虚指针找到对应的虚函数表,再根据虚函数表中函数指针来调用实际的函数。这就是动态绑定,它实现了运行时多态性。
  2. 多态性:在上述代码中,callPrint函数接收Base类指针数组,在遍历数组调用print函数时,根据指针实际指向的是Derived1还是Derived2对象,会调用对应的print函数版本,从而实现多态性。

没有正确使用虚函数的问题

  1. 静态绑定:如果基类函数没有声明为虚函数,通过基类指针或引用调用该函数时,编译器会在编译时根据指针或引用的类型(即基类类型)来决定调用哪个函数,而不会考虑指针实际指向的对象类型。这将导致无法实现运行时多态,派生类重写的函数不会被调用,程序行为不符合预期。
  2. 资源泄漏:在继承体系中,如果基类析构函数不是虚函数,当通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,这可能导致派生类中分配的资源无法释放,从而造成资源泄漏。