乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

虚函数与多态

定义与作用

虚函数是实现运行时多态(动态绑定)的机制。当通过基类指针或引用调用虚函数时,实际执行的是指针所指向的对象的真实类型的版本:

class Animal {
public:
    virtual void speak() const { std::cout << "动物叫声" << std::endl; }
};

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

Animal* pet = new Dog();
pet->speak();  // 输出 "汪汪"——动态绑定,而非 "动物叫声"

C++11 引入了两个关键的虚函数控制关键字:override(明确覆盖意图,编译器验证)和 final(阻止进一步覆盖或继承)。

核心原理

静态绑定 vs 动态绑定

override 与 final 的作用

完整示例

示例一:图形绘制系统——虚函数多态

场景说明:设计一个图形绘制系统,展示虚函数多态的实际应用。

#include <iostream>
#include <vector>
#include <memory>
#include <cmath>

class Shape {
public:
    virtual ~Shape() = default;

    virtual double area() const = 0;       // 纯虚函数:每个形状必须实现
    virtual double perimeter() const = 0;
    virtual void draw() const {
        std::cout << "绘制形状(面积: " << area()
                  << ", 周长: " << perimeter() << ")" << std::endl;
    }
};

class Circle : public Shape {
public:
    explicit Circle(double r) : radius_(r) {}

    double area() const override {
        return 3.14159 * radius_ * radius_;
    }

    double perimeter() const override {
        return 2 * 3.14159 * radius_;
    }

    void draw() const override {
        std::cout << "○ 圆形(半径: " << radius_ << ")" << std::endl;
    }

private:
    double radius_;
};

class Rectangle : public Shape {
public:
    Rectangle(double w, double h) : width_(w), height_(h) {}

    double area() const override {
        return width_ * height_;
    }

    double perimeter() const override {
        return 2 * (width_ + height_);
    }

    void draw() const final {  // final:子类不能再覆盖 draw
        std::cout << "□ 矩形(" << width_ << "×" << height_ << ")" << std::endl;
    }

private:
    double width_, height_;
};

class Triangle : public Shape {
public:
    Triangle(double a, double b, double c) : a_(a), b_(b), c_(c) {}

    double area() const override {
        double s = (a_ + b_ + c_) / 2;
        return std::sqrt(s * (s - a_) * (s - b_) * (s - c_));
    }

    double perimeter() const override {
        return a_ + b_ + c_;
    }

    void draw() const override {
        std::cout << "△ 三角形(边长: " << a_ << ", " << b_ << ", " << c_ << ")" << std::endl;
    }

private:
    double a_, b_, c_;
};

int main() {
    std::cout << "===== 图形绘制系统 =====" << std::endl;

    // 基类指针容器——多态的关键
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Circle>(5.0));
    shapes.push_back(std::make_unique<Rectangle>(4.0, 6.0));
    shapes.push_back(std::make_unique<Triangle>(3.0, 4.0, 5.0));

    // 统一接口操作不同形状
    for (const auto& shape : shapes) {
        shape->draw();
    }

    std::cout << "\n===== 面积与周长统计 =====" << std::endl;
    double total_area = 0.0;
    for (const auto& shape : shapes) {
        std::cout << "面积: " << shape->area()
                  << " | 周长: " << shape->perimeter() << std::endl;
        total_area += shape->area();
    }
    std::cout << "总面积: " << total_area << std::endl;

    return 0;
}

预期输出:

===== 图形绘制系统 =====
○ 圆形(半径: 5)
□ 矩形(4×6)
△ 三角形(边长: 3, 4, 5)

===== 面积与周长统计 =====
面积: 78.5398 | 周长: 31.4159
面积: 24 | 周长: 20
面积: 6 | 周长: 12
总面积: 108.54

逐段分析:

  • Shape 是抽象基类,定义纯虚函数 area() 和 perimeter()——子类必须实现
  • draw() 是非纯虚函数,提供默认实现——子类可选择覆盖或沿用
  • Rectangle::draw() 标记为 final——任何继承 Rectangle 的类不能再覆盖 draw()
  • std::vector<unique_ptr<Shape>> 存储不同类型对象的基类指针——实现多态遍历
  • 每个 shape->draw() 调用根据实际对象类型分派到对应的虚函数——运行时多态

示例二:override 的编译期保护

场景说明:展示 override 如何捕获常见的虚函数签名错误。

#include <iostream>

class Base {
public:
    virtual void process(int value) const {
        std::cout << "Base::process(int) = " << value << std::endl;
    }

    virtual void calculate(double x) {
        std::cout << "Base::calculate(double) = " << x << std::endl;
    }
};

// 错误示例(假设没有 override 保护)
class WrongDerived : public Base {
public:
    // 错误 1:参数类型不匹配(int → unsigned int)→ 没有覆盖,而是新增重载
    void process(unsigned int value) const {  // 没有 override
        std::cout << "WrongDerived::process(unsigned) " << value << std::endl;
    }

    // 错误 2:忘记 const → 没有覆盖
    void calculate(double x) {  // 没有 override
        std::cout << "WrongDerived::calculate " << x << std::endl;
    }
};

// 正确示例(使用 override 保护)
class CorrectDerived : public Base {
public:
    void process(int value) const override {  // 编译器验证:签名匹配 Base
        std::cout << "CorrectDerived::process(int) = " << value << std::endl;
    }

    void calculate(double x) override {
        std::cout << "CorrectDerived::calculate(double) = " << x << std::endl;
    }
};

int main() {
    std::cout << "===== 无 override 保护(隐藏而非覆盖) =====" << std::endl;
    WrongDerived wrong;
    wrong.process(10);       // 调用 WrongDerived::process(unsigned)
    wrong.process(10u);      // 调用 WrongDerived::process(unsigned)

    Base* base_ptr = &wrong;
    base_ptr->process(10);   // 调用 Base::process(int)!——不是覆盖,是隐藏

    std::cout << "\n===== 有 override 保护(正确覆盖) =====" << std::endl;
    CorrectDerived correct;
    Base* correct_ptr = &correct;

    correct_ptr->process(10);       // 调用 CorrectDerived::process(int)
    correct_ptr->calculate(3.14);   // 调用 CorrectDerived::calculate(double)

    return 0;
}

预期输出:

===== 无 override 保护(隐藏而非覆盖) =====
WrongDerived::process(unsigned) 10
WrongDerived::process(unsigned) 10
Base::process(int) = 10

===== 有 override 保护(正确覆盖) =====
CorrectDerived::process(int) = 10
CorrectDerived::calculate(double) = 3.14

逐段分析:

  • WrongDerived::process(unsigned) 没有 override——参数类型不同 (int vs unsigned int),它是隐藏而非覆盖
  • 当通过 Base* 调用 process(10) 时,由于没有覆盖,调用的是 Base::process(int)——完全不是预期行为
  • CorrectDerived 使用 override——如果签名不匹配直接编译错误,强制开发者正确覆盖
  • 这就是为什么所有虚函数覆盖都必须用 override——这是 C++11 最重要的代码安全改进之一

易错场景与面试考点

易错场景

场景错误表现正确做法
虚函数未加 override签名不匹配时隐藏而非覆盖所有覆盖都加 override
构造函数中调用虚函数调用基类版本而非派生类版本构造函数中避免调用虚函数
析构函数中调用虚函数同样只调用当前类版本析构函数中避免调用虚函数
final 类被继承编译错误检查设计意图
协变返回类型使用不当返回类型不兼容指针/引用类型才支持协变

常见面试问题

  1. 虚函数和普通函数的本质区别?实现原理?——虚函数通过虚函数表(vtable)实现运行时多态,调用开销略高于普通函数(间接跳转)。普通函数在编译时静态绑定。

  2. override 和 final 的作用?——override 标记虚函数覆盖,编译器验证基类确有匹配的虚函数。final 阻止虚函数被进一步覆盖或类被继承。

  3. 构造函数和析构函数中调用虚函数的行为?——调用的是当前正在构造/析构的类的版本,而不是最终派生类的版本。因为派生类部分尚未构造或已被析构。

  4. 什么是协变返回类型?——派生类中覆盖虚函数的返回类型可以是基类虚函数返回类型的派生类指针或引用(Base* → Derived*)。

  5. 虚函数可以是 static 的吗?——不能。static 成员函数没有 this 指针,无法动态绑定。

小结

  • 虚函数通过 vtable 实现运行时多态——基类指针/引用调用时动态分派到实际类型
  • 所有虚函数覆盖必须加 override——这是 C++11 最重要的编码规范之一
  • final 阻止虚函数被覆盖或类被继承——在不需要扩展的地方加锁
  • 构造函数/析构函数中避免调用虚函数——此时动态绑定被禁用
  • 多态 = 统一接口 + 不同行为,是面向对象设计的核心价值
上一页
继承基础
下一页
虚函数表与动态绑定原理