C++ 基础

C++ 主页 C++ 概述 C++ 环境设置 C++ 基本语法 C++ 注释 C++ Hello World C++ 省略命名空间 C++ 标记 C++ 常量/字面量 C++ 关键字 C++ 标识符 C++ 数据类型 C++ 数字数据类型 C++ 字符数据类型 C++ 布尔数据类型 C++ 变量类型 C++ 变量作用域 C++ 多变量 C++ 基本输入/输出 C++ 修饰符类型 C++ 存储类 C++ 数字 C++ 枚举 C++ 枚举类 C++ 引用 C++ 日期和时间

C++ 运算符

C++ 运算符 C++ 算术运算符 C++ 关系运算符 C++ 逻辑运算符 C++ 位运算符 C++ 赋值运算符 C++ sizeof 运算符 C++ 条件运算符 C++ 逗号运算符 C++ 成员运算符 C++ 强制类型转换运算符 C++ 指针运算符 C++ 运算符优先级 C++ 一元运算符

C++ 控制语句

C++ 决策语句 C++ if 语句 C++ if else 语句 C++ 嵌套 if 语句 C++ switch 语句 C++ 嵌套 switch语句 C++ 循环类型 C++ while 循环 C++ for 循环 C++ do while 循环 C++ Foreach 循环 C++ 嵌套循环 C++ break 语句 C++ continue 语句 C++ goto 语句

C++ 字符串

C++ 字符串 C++ 循环遍历字符串 C++ 字符串长度 C++ 字符串连接 C++ 字符串比较

C++ 函数

C++ 函数 C++ 多函数参数 C++ 递归函数 C++ 返回值 C++ 函数重载 C++ 函数重写 C++ 默认参数

C++ 数组

C++ 数组 C++ 多维数组 C++ 指向数组的指针 C++ 将数组传递给函数 C++ 从函数返回数组

C++ 结构 &联合

C++ 结构 C++ 联合

C++ 指针

C++ 指针 C++ 解引用 C++ 修改指针

C++ 类和对象

C++ 面向对象 C++ 类 &对象 C++ 类成员函数 C++ 类访问修饰符 C++ 静态类成员 C++ 静态数据成员 C++ 静态成员函数 C++ 内联函数 C++ this 指针 C++ 友元函数 C++ 指向类的指针

C++ 构造函数

C++ 构造函数 &析构函数 C++ 默认构造函数 C++ 参数化构造函数 C++ 复制构造函数 C++ 构造函数重载 C++ 带默认参数的构造函数 C++ 委托构造函数 C++ 构造函数初始化列表 C++ 使用构造函数动态初始化

C++ 继承

C++ 继承 C++ 多重继承 C++ 多级继承

C++ 面向对象

C++ 重载 C++ 多态性 C++ 抽象 C++ 封装 C++ 接口 C++ 虚函数 C++ 纯虚函数与抽象类

C++ 文件处理

C++ 文件和流 C++ 文件读取

C++ 进阶

C++ 异常处理 C++ 动态内存 C++ 命名空间 C++ 模板 C++ 预处理器 C++ 信号处理 C++ 多线程 C++ Web 编程 C++ 套接字编程 C++ 并发 C++ 高级概念 C++ Lambda 表达式 C++ unordered_multiset

C++ 实用资源

C++ 问答 C++ 快速指南 C++ 速查表 C++ STL 教程 C++ 标准库 C++ 实用资源 C++ 讨论


C++ 中的虚函数

C++ 中的虚函数是基类中的成员函数,该函数在派生类中被重写。这有助于实现运行时多态性,这意味着要执行的函数是在运行时确定的,而不是在编译时,因为调用的函数取决于实际的对象类型,而不是指针或引用类型。

虚函数声明

在基类中使用 virtual 关键字声明虚函数。

语法

以下是虚函数声明的语法:

class BaseClassName {
public:
    virtual void func_name() {
        // 执行
    }
};

其中,

  • BaseClassName 是用户指定的基类名称。
  • func_name 是指定的函数名称。

虚函数的工作原理

在 C++ 中,虚函数允许运行时多态性,这意味着被调用的函数取决于实际的对象类型,而不是指针或引用类型,这是通过使用称为 vtable(虚表)和 vptr(虚表指针)的机制来实现的。

虚表 (VTable) 和虚拟指针 (VPTR)

虚拟表是为每个包含虚函数的类创建的函数指针表,它存储该类的虚函数地址
然而,该类中每个具有虚函数的对象都包含一个隐藏的 vptr,该 vptr 指向其类的虚函数表 (vtable)。

分步操作

  1. 当我们在基类中将函数声明为虚函数时,它将启用动态绑定而不是静态绑定。
  2. 对于具有虚函数的类,编译器会创建一个 vtable,其中存储指向这些函数的指针
  3. 这里,每个具有虚函数的类的对象都有一个 vptr,它指向该类的 vtable。
  4. 然后,基类指针使用 vptr 在运行时从 vtable 中获取正确的函数,确保调用派生类的重写函数。

示例

#include<iostream>
using namespace std;

class shape {
  public: virtual void draw() { // 虚函数
    cout << "Creating a shape!" << endl;
  }
};

class circle: public shape {
  public: void draw() { // 重写虚函数
    cout << "Creating a Circle!" << endl;
  }
};

class square: public shape {
  public: void draw() { // 重写虚函数
    cout << "Creating a Square!" << endl;
  }
};

int main() {
    shape * shapePtr; // 基类指针
    circle c;
    square s;
    
    shapePtr = & c; // 指向 circle 对象
    shapePtr -> draw(); // 调用 circle 的 draw() 方法
    
    shapePtr = & s; // 指向 square 对象
    shapePtr -> draw(); // 调用 square 的 draw() 方法
    
    return 0;
}

输出

Creating a Circle!
Creating a Square!

说明

  1. 首先,创建一个名为 shape 的基类,并为其设置虚函数 draw(),从而为该类建立虚表机制。
  2. 现在,circle 类继承自 shape 并重写了 draw() 方法,因此当使用 circle 对象时,会调用其 draw() 函数。
  3. 与 circle 类似,square 类也会重写 draw() 函数。
  4. 现在,在 int main() 函数中,shape* shapePtr; 是一个 shape 类型的指针,它将指向 circle 或 square 对象。
  5. shapePtr = &c; 这个基类指针指向 circle 对象 c,并且 shapePtr->draw(); 调用 circle 的 draw() 函数。
  6. 类似地,shapePtr = &s;这个基类指针指向正方形对象 s,shapePtr->draw(); 调用正方形的 draw() 函数。

虚函数规则

  1. 虚函数必须在基类中使用"virtual"关键字声明。如果省略 virtual 关键字,则无法动态覆盖。
  2. 虚函数必须通过基类指针引用访问。如果直接使用对象访问,则会进行编译时绑定。
  3. 对于虚函数,基类还必须具有虚析构函数,以防止内存泄漏。如果析构函数不是虚函数,则只会调用基类的析构函数,从而导致内存泄漏。
  4. 具有纯虚函数的类将成为抽象类,并且无法实例化。如果派生类未重写纯虚函数,它也会成为抽象类。
    class Base {
    public:
        virtual void show() = 0; // 纯虚函数
    };
    
  5. 虚函数可以是publicprivate,其中public可以通过对象或指针访问,而private不能直接访问,但它仍然可以在派生类中被继承和重写。
  6. 构造函数、静态函数和友元函数不能是虚函数。