C++虚函数:从基础到高阶

C++ 虚函数 超全知识点总结

虚函数是 C++ 多态(动态多态) 的核心,专门解决父类指针 / 引用指向子类对象时,调用正确函数的问题。我会把所有考点、用法、原理、坑点一次性讲全。


一、基础概念

1. 定义

类的成员函数 前加 virtual 关键字,就是虚函数 。作用:运行时决定调用哪个类的函数(动态绑定 / 晚绑定)。

2. 核心目的

父类指针 / 引用 指向子类对象 时,能调用子类重写的函数,而不是父类版本。

3. 语法格式

cpp

运行

复制代码
class 父类 {
public:
    virtual 返回值 函数名(参数) { // 虚函数
        // 实现
    }
};

class 子类 : public 父类 {
public:
    返回值 函数名(参数) override { // 重写(建议加 override)
        // 子类实现
    }
};

二、必须掌握的 4 个基础规则

1. 只有成员函数能加 virtual

全局函数、静态函数、构造函数 不能是虚函数

2. 子类重写时,virtual 可写可不写

只要父类声明了 virtual,子类自动继承虚属性。建议:子类重写加 override 关键字(编译器检查是否正确重写)。

3. 动态绑定条件(缺一不可)

  1. 函数必须是 虚函数
  2. 通过 父类指针 / 父类引用 调用
  3. 指向 / 引用 子类对象

满足这 3 条,才会发生多态。

4. 构造函数不能是虚函数,析构函数建议是虚函数

  • 构造:对象还没创建,无法调用虚函数
  • 析构:父类指针删子类对象时,防止内存泄漏

三、虚析构函数(必考)

1. 为什么需要

父类指针指向 new 出来的子类对象,delete 时:

  • 析构不是虚函数 → 只调用父类析构,子类资源泄漏
  • 析构是虚函数 → 先调用子类析构,再调用父类析构

2. 写法

cpp

运行

复制代码
class Base {
public:
    virtual ~Base() { cout << "父类析构\n"; }
};

3. 结论

只要类里有虚函数,析构函数一律写成虚析构!


四、纯虚函数 & 抽象类

1. 纯虚函数定义

没有函数体,用 = 0 结尾:

cpp

运行

复制代码
virtual void func() = 0;

2. 抽象类

包含至少一个纯虚函数的类。

  • 不能实例化对象
  • 必须由子类重写所有纯虚函数才能实例化
  • 作用:定义接口,强制子类实现

3. 示例

cpp

运行

复制代码
class Shape { // 抽象类
public:
    virtual void draw() = 0; // 纯虚函数
};

class Circle : public Shape {
public:
    void draw() override { // 必须实现
        cout << "画圆\n";
    }
};

五、override /final 关键字(C++11)

1. override(检查重写)

告诉编译器:我要重写父类虚函数,写错就报错。

cpp

运行

复制代码
void func() override; // 正确
void fun() override;  // 编译报错(函数名写错)

2. final(禁止重写 / 禁止继承)

  • 修饰函数:子类不能再重写
  • 修饰类:该类不能被继承

cpp

运行

复制代码
virtual void func() final; // 禁止重写
class A final {};          // 禁止继承

六、虚函数底层原理(面试必考)

1. 虚函数表 vtable

  • 每个有虚函数的类 ,编译器会生成一张 虚函数表(静态,属于类)
  • 表中存放:虚函数地址
  • 子类重写 → 表中地址替换为子类函数地址

2. 虚指针 vptr

  • 每个对象 会包含一个 虚指针(4/8 字节)
  • 指向所属类的虚函数表

3. 调用流程

plaintext

复制代码
父类指针 → 对象 vptr → 虚函数表 → 找到正确函数地址 → 调用

4. 开销

  • 空间:每个对象多一个指针(4/8 字节)
  • 时间:多一次查表(几乎可忽略)

七、多重继承下的虚函数

  1. 子类会继承所有父类的虚函数
  2. 子类只有一张虚表(合并所有父类虚表)
  3. 子类重写任意父类虚函数,表中地址都会更新

八、10 个高频易错点

1. 静态函数不能是虚函数

静态函数属于类,不属于对象,没有 this 指针 → 无法动态绑定。

2. inline 与 virtual 冲突

虚函数是运行时确定,inline 是编译时展开 → 虚函数不能真正内联

3. 构造函数中调用虚函数

不会发生多态!只会调用当前类的函数。(构造时子类还没初始化完成)

4. 友元函数不能是虚函数

友元不是类成员,无法加 virtual。

5. 函数签名必须完全一致才能重写

  • 函数名、参数、const 都必须一样
  • 返回值可协变(父返回父指针,子返回子指针)

6. 缺省参数用父类

虚函数调用动态绑定,但缺省参数编译期确定,永远用父类默认值。

7. 虚函数可以是 private

依然能多态!权限检查在编译期,调用在运行期。

8. 不能定义抽象类对象

包含纯虚函数 → 编译报错。

9. 子类没实现纯虚函数 → 子类仍是抽象类

依旧不能创建对象。

10. 虚函数可以有实现

纯虚函数也能写实现体,作用:给子类提供默认逻辑。


九、完整示例代码(可直接运行)

cpp

运行

复制代码
#include <iostream>
using namespace std;

// 父类
class Animal {
public:
    virtual void sound() { // 虚函数
        cout << "动物叫\n";
    }
    virtual ~Animal() { // 虚析构
        cout << "Animal 析构\n";
    }
};

// 子类
class Dog : public Animal {
public:
    void sound() override { // 重写
        cout << "汪汪汪\n";
    }
    ~Dog() {
        cout << "Dog 析构\n";
    }
};

int main() {
    Animal* p = new Dog(); // 父指针指向子对象
    p->sound(); // 多态:调用 Dog::sound()
    delete p;   // 先调用 Dog 析构,再 Animal 析构
    return 0;
}

输出:

plaintext

复制代码
汪汪汪
Dog 析构
Animal 析构
相关推荐
2401_824697661 小时前
Go语言如何用systemd_Go语言systemd服务管理教程【总结】
jvm·数据库·python
hhb_6181 小时前
C语言核心技术难点梳理与实战案例解析
c语言·开发语言
海参崴-1 小时前
C++ STL篇 红黑树的模拟实现
开发语言·c++
2301_775639891 小时前
mysql修改字段长度是否影响数据_隐式转换与字符集限制分析
jvm·数据库·python
Dshuishui1 小时前
我用 Claude Code 做了一个学术论文搜索工具
开发语言·人工智能·python·pip·uv
IT 行者1 小时前
Spring AI 2.0.0-M5 发布:全面转向 OpenAI Java SDK
java·人工智能·spring
ㄟ留恋さ寂寞1 小时前
怎样修改提示“表已空”的空状态界面_Empty State插画替换
jvm·数据库·python
Resky08181 小时前
ReentrantReadWriteLock 深度解析
java·开发语言·juc
铭keny1 小时前
子系统 SSO 单点登录接入配置指南
java