C++ 析构函数与虚函数机制详细教程

本文将从 析构函数的作用静态函数和虚函数的区别函数重载与函数覆盖虚函数表的实现机制 四个方面为初学者系统讲解 C++ 中的重要概念,并通过示例帮助理解。


析构函数的作用

在 C++ 中,析构函数(Destructor) 是一种特殊的成员函数,它与构造函数相对应,主要用于在对象生命周期结束时,自动执行清理工作。

  • 构造函数:用于初始化对象(开文件、申请内存、建立数据库连接等)。
  • 析构函数:用于释放资源(关闭文件、释放内存、断开连接等)。

语法规则:

~类名() {
    // 清理操作
}

示例:文件操作类

#include <iostream>
#include <fstream>
using namespace std;

class FileHandler {
private:
    fstream file;  // 文件对象
public:
    // 构造函数:打开文件
    FileHandler(const string& filename) {
        file.open(filename, ios::out);
        if (file.is_open()) {
            cout << "文件已打开: " << filename << endl;
        }
    }

    // 析构函数:关闭文件
    ~FileHandler() {
        if (file.is_open()) {
            file.close();
            cout << "文件已关闭" << endl;
        }
    }
};

int main() {
    FileHandler handler("test.txt");  // 构造函数打开文件
    // main 结束时,handler 被销毁 -> 自动调用析构函数关闭文件
    return 0;
}

运行结果:

文件已打开: test.txt
文件已关闭

👉 这里即使程序员忘记调用关闭文件的函数,析构函数也能自动完成清理,避免资源泄漏。


静态函数与虚函数的区别

静态函数(static function)

  • 属于 类本身,而不是对象。
  • 在编译期就确定了调用关系(静态绑定)。
  • 不能被 virtual 修饰。
  • 调用方式:类名::函数名()对象.函数名()
class Test {
public:
    static void show() {
        cout << "静态函数调用" << endl;
    }
};

int main() {
    Test::show();  // 不需要创建对象
    return 0;
}

虚函数(virtual function)

  • 属于对象的动态行为。
  • 在运行期根据对象的实际类型决定调用哪个函数(动态绑定)。
  • 支持 多态:父类指针指向子类对象时,可以调用子类重写的函数。
class Animal {
public:
    virtual void speak() {
        cout << "动物叫" << endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        cout << "汪汪汪" << endl;
    }
};

int main() {
    Animal* p = new Dog();  // 父类指针指向子类对象
    p->speak();  // 动态绑定,输出:汪汪汪
    delete p;
    return 0;
}

👉 区别总结:

  • 静态函数:编译期确定(快),但不具备多态性。
  • 虚函数:运行期决定(多态),但有一点性能开销。

函数重载与函数覆盖

函数重载(Overload)

  • 同一个类中,函数名相同,参数列表不同(参数个数或类型不同)。
  • 编译器根据实参选择调用哪个函数。
  • 水平关系
class Math {
public:
    int add(int a, int b) {
        return a + b;
    }
    double add(double a, double b) {
        return a + b;
    }
};

int main() {
    Math m;
    cout << m.add(2, 3) << endl;       // 调用 int 版本
    cout << m.add(2.5, 3.1) << endl;  // 调用 double 版本
}

函数覆盖(Override)

  • 父类和子类之间的关系。
  • 子类重写父类中的虚函数。
  • 垂直关系
class Base {
public:
    virtual void print() {
        cout << "Base" << endl;
    }
};

class Derived : public Base {
public:
    void print() override {
        cout << "Derived" << endl;
    }
};

int main() {
    Base* b = new Derived();
    b->print();  // 调用子类的 print
    delete b;
}

👉 总结:

  • 重载:编译期决定,靠参数表区分。
  • 覆盖:运行期决定,靠对象的实际类型区分。

虚函数表(vtable)与多态实现

C++ 的多态是通过 虚函数表(Virtual Table, vtable) 实现的。

  • 每个包含虚函数的类,编译器都会为其生成一个虚函数表。
  • 虚函数表中存放着该类的所有虚函数的地址。
  • 每个对象内部会有一个 隐藏的指针(vptr),指向所属类的虚函数表。
  • 当调用虚函数时,程序通过 vptr 查找虚函数表,从而找到正确的函数地址,实现运行时多态。

示例说明

class A {
public:
    virtual void f1() { cout << "A::f1" << endl; }
    virtual void f2() { cout << "A::f2" << endl; }
};

class B : public A {
public:
    void f1() override { cout << "B::f1" << endl; }
    void f2() override { cout << "B::f2" << endl; }
};

int main() {
    A* p = new B();
    p->f1();  // 实际调用 B::f1
    p->f2();  // 实际调用 B::f2
    delete p;
}

运行机制:

  1. p 是一个指向 B 对象的 A* 指针。
  2. p 内部有一个 vptr,指向 B 的虚函数表。
  3. p->f1() 时,程序从 B 的虚函数表中找到 f1 的地址,最终执行 B::f1()

👉 正是虚函数表的存在,才使得 “父类指针调用子类方法” 成为可能。


总结

  1. 析构函数:在对象销毁时自动清理资源,避免内存泄漏。
  2. 静态函数 vs 虚函数:前者属于类本身,编译期绑定;后者属于对象,运行期绑定。
  3. 函数重载 vs 函数覆盖:重载发生在同类中,依赖参数表;覆盖发生在继承体系中,依赖虚函数机制。
  4. 虚函数表:多态的底层实现,每个对象都有 vptr 指向虚函数表,从而在运行时决定调用哪个函数。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐