当前位置: 首页 > article >正文

C++之虚函数

对基类中的方法进行重写;
主要是通过继承机制 + V 表实现;
虚函数的引入与不加入虚函数的主要区别在于 动态多态性。通过将 Entity 类的 GetName 函数声明为 virtual,可以实现 运行时多态,这意味着程序会根据对象的实际类型调用相应的函数,而不是根据指针或引用的类型调用函数。

1. 没有虚函数的情况

如果你不使用虚函数(即 Entity 类中的 GetName 不是 virtual),则即使 Entity 的指针指向了 Player 对象,调用 GetName() 时,依然会执行 Entity 类中的 GetName() 函数。也就是说,调用的是编译时决定的函数,而不是运行时根据实际对象类型选择的函数。

举个例子:

class Entity
{
public:
    std::string GetName() { return "Entity"; }  // 非虚函数
};

class Player : public Entity
{
private:
    std::string m_Name;
public:
    Player(const std::string& name) : m_Name(name) {}
    std::string GetName() { return m_Name; }  // 不会覆盖 Entity 中的 GetName
};

void PrintName(Entity* entity)
{
    std::cout << entity->GetName() << std::endl;  // 总是调用 Entity::GetName()
}

int main()
{
    Entity* e = new Entity;
    PrintName(e);  // 输出 "Entity"

    Player* p = new Player("Cherno");
    PrintName(p);  // 依然输出 "Entity",因为没有虚函数
}

在这种情况下,PrintName(p) 也会输出 "Entity",即使 p 实际上指向的是一个 Player 对象。

2. 引入虚函数后的情况

GetName 函数声明为 virtual 时,C++ 会使用 虚函数表(vtable) 来确定调用哪个函数。虚函数表在运行时动态决定哪个类的版本的 GetName 被调用。因此,当你调用 entity->GetName() 时,程序会根据实际指向的对象类型(无论是 Entity 还是 Player)调用相应的函数。

修改代码后,程序的行为会有所不同:

class Entity
{
public:
    virtual std::string GetName() { return "Entity"; }  // 声明为虚函数
};

class Player : public Entity
{
private:
    std::string m_Name;
public:
    Player(const std::string& name) : m_Name(name) {}
    std::string GetName() override { return m_Name; }  // 重写虚函数
};

void PrintName(Entity* entity)
{
    std::cout << entity->GetName() << std::endl;  // 运行时多态,调用正确的函数
}

int main()
{
    Entity* e = new Entity;
    PrintName(e);  // 输出 "Entity"

    Player* p = new Player("Cherno");
    PrintName(p);  // 输出 "Cherno",因为 GetName 被 Player 重写了
}

现在,程序会根据指针的实际类型来选择调用的 GetName 函数:

  • PrintName(e) 输出 Entity,因为 eEntity 类型的对象。
  • PrintName(p) 输出 Cherno,因为 p 实际上指向的是 Player 类型的对象,GetNamePlayer 重写了。

3. 虚函数的工作原理

在 C++ 中,虚函数的工作原理如下:

  1. 虚函数表(vtable): 每个含有虚函数的类都会生成一个虚函数表,虚函数表存储了类中所有虚函数的地址。在创建对象时,编译器会为每个类对象(包括派生类)设置一个指向该类虚函数表的指针。

  2. 运行时决定: 在调用虚函数时,程序会根据对象的实际类型(即动态类型)来选择虚函数表中的函数地址,进而调用正确的函数。这个过程称为 动态绑定(dynamic binding)或 运行时多态

    • 如果你使用 Entity* e = new Player("Cherno");,即使指针类型是 Entity*,但由于虚函数的存在,程序会正确调用 Player 类中的 GetName,而不是 Entity 类中的 GetName
  3. 虚析构函数: 虚函数不仅可以使类支持多态,还可以使类支持正确的析构过程,避免内存泄漏。例如,Entity 类应该将析构函数声明为虚拟函数,以确保删除 Player 类型的对象时能正确调用 Player 的析构函数。

    class Entity
    {
    public:
        virtual ~Entity() {}  // 虚析构函数
    };
    

总结

  • 没有虚函数:如果不使用虚函数,基类指针指向派生类对象时,调用的会是基类中的函数,无法实现多态。
  • 使用虚函数:如果使用虚函数,基类指针指向派生类对象时,调用的会是派生类中的函数,实现了动态多态。
  • 弊端:虚函数有着额外的内存消耗(存储V表),以及额外的查询查询时间,对于cpu很差的嵌入式设备可能会有影响
  • 好处:实现了同一函数对于不同对象引用时候的不同方法;

下一节

纯虚函数


http://www.kler.cn/a/418697.html

相关文章:

  • Qt 窗口类型、窗口标志和窗口属性
  • IAR中编译下载未下载问题
  • 利用Python爬虫精准获取淘宝商品详情的深度解析
  • 5. langgraph实现高级RAG (Adaptive RAG)
  • 服务熔断-熔断器设计
  • 【ETCD】etcd中配置参数详解
  • 力扣3373.连接两棵树后最大目标节点数目II
  • 网页开发的http基础知识
  • Mysql实现定时自动备份(Windows环境)
  • 如何正确处理和解析 GitHub API 返回的 JSON 数据:详细指南与示例
  • 多线程相关案例
  • 文本内容处理命令和正则表达式
  • 使用springBoot的freemarker生成按模板生成word
  • pycharm(一)安装
  • electron学习 渲染进程与主进程通信
  • ArrayList和LinkedList的区别(详解)
  • Mybatis:CRUD数据操作之多条件查询及动态SQL
  • 基于RISC-V 的代理内核实验(使用ub虚拟机安装基本环境)
  • Vivado程序固化到Flash
  • 「Mac畅玩鸿蒙与硬件34」UI互动应用篇11 - 颜色选择器
  • 【VUE3】【Naive UI】<NCard> 标签
  • Redis 3 种特殊数据类型详解
  • 详解Qt 之QSwipeGesture手势滑动
  • unity中:Unity 中异步与协程结合实现线程阻塞的http数据请求
  • OGRE 3D----2. QGRE + QQuickView
  • 【博主推荐】C#中winfrom开发常用技术点收集