C++虚函数表

news2024/9/26 3:25:34

一、虚函数和纯虚函数

1.1 虚函数

在类成员方法的声明 (不是定义) 语句前加 “virtual”,如 virtual void func()

class ISpeaker {
public:
    virtual void func();
};

1.2 纯虚函数

在虚函数后加 “=0”,如 virtual void func()=0

class ISpeaker {
public:
    virtual void func() = 0;
};

1.3 override和final说明符

1、分清重写、重载和隐藏

重载 (overload) ,它通常是指在同一个类中有两个或者两个以上函数,它们的函数名相同,但是函数形参不同

重写 (override) 的意思更接近覆盖,在C++中是指派生类覆盖了基类的虚函数

隐藏 (overwrite) 是指基类成员函数,无论它是否为虚函数,当派生类出现同名函数时,如果派生类函数形参基类函数,则基类函数会被隐藏。如果派生类函数形参与基类函数相同,则需要确定基类函数是否为虚函数,如果是虚函数这里的概念就是重写,否则基类函数也会被隐藏。

2、为什么要用override

重写会引发下列问题。下面的代码可以编译成功,但是派生类的四个函数都没有触发重写操作,如果测试人员没有经过仔细的测试,在软件发布前很难意识到这些问题

class Base {
public:
    virtual void some_func() { }
    virtual void foo(int x) { }
    virtual void bar() const { }
    void baz() { }
};

class Derived : public Base {
public:
    virtual void sone_func() { }  // (1)
    virtual void foo(int &x) { }  // (2)
    virtual void bar() { }        // (3)
    virtual void baz() { }        // (4)
};

(1) 派生类虚函数的函数名与基函数的虚函数不同,派生类的函数不是重写
(2) 派生类虚函数的形参列表与基类虚函数的不同,派生类的函数不是重写
(3) 派生类虚函数相对于基类的虚函数少了常量属性,派生类的函数不是重写
(4) 基类成员函数根本不是虚函数,派生类的函数不是重写

让编译器去检查继承中是否发生了重写是非常简单的一件事,只需要告诉编译器,我要重写哪些函数就行了,C++11 标准引入了 override 这个说明符,它就是用来做这件事的

class Derived : public Base {
public:
    virtual void sone_func() override { }
    virtual void foo(int &x) override { }
    virtual void bar() override { }
    virtual void baz() override { }
};

这样一修改,四个函数都引发了编译失败,比人工检查要方便很多

3、final说明符

C++11 引入 final 关键字,用来阻止派生类去继承基类的虚函数。它告诉编译器,该虚函数不允许被重写

class Base {
public:
    virtual void foo(int x) { }
};

class Derived : public Base {
public:
    void foo(int x) final { }  // (1)
};

class Derived2 : public Derived {
public:
    void foo(int x) { }
};

(1) 中间层的基函数 derived 的虚函数 foo 声明位 final,所以派生类 Derived2 重写 foo 函数时,编译器会给出错误提示

处理用于声明虚函数,final 还可以声明 类,在类名的后面声明 final,可以阻止该类被继承。如下代码所示,最终会编译失败

class Base final {
public:
    virtual void foo(int x) { }
};

class Derived : public Base {
public:
    void foo(int x) { }
};

1.4 其他区别

虚函数纯虚函数
子类可以 (也可以不) 重新定义基类的虚函数,该行为称之为复写Override子类必须提供纯虚函数的个性化实现
子类如果不提供虚函数的实现,将会自动调用基类的缺省虚函数实现,作为备选方案子类如果不提供纯虚函数的实现,编译将会失败。尽管在基类中可以给出纯虚函数的实现,但无法通过指向子类对象的基类类型指针来调用该纯虚函数,也即不能作为子类相应纯虚函数的备选方案

二、多态

2.1 多态

两个不同的子类强转为基类,调用这个基类的相同方法,两个不同的子类执行各自的函数

#include <iostream>

class ISpeaker
{
protected:
    size_t b;
public:
    ISpeaker(size_t _v) : b(_v) { }
    virtual void speak() = 0;
};

class Dog : public ISpeaker {
public:
    Dog() : ISpeaker(0) { }
    virtual void speak() override { 
        printf("woof! %llu\n", b);
    }
};

class Human : public ISpeaker {
private:
    size_t c;
public:
    Human() : ISpeaker(1), c(2) { }
    virtual void speak() override { 
        printf("hello! %llu\n", c);
    }
};

int main(int argc, char** _argv) {
    Human* pHuman = new Human();
    Dog* pDog = new Dog();

    ISpeaker* speaker1 = (ISpeaker*)pHuman;
    ISpeaker* speaker2 = (ISpeaker*)pDog;

    speaker1->speak();
    speaker2->speak();
}

运行结果:
C:\Users\huan\Desktop>.\a.exe 
hello! 2
woof! 0

2.2 多态的实现——虚函数表

如果一个类实现了一个虚函数,编译器在处理它的时候会生成一个虚函数表

虚函数表的定义如下:

struct SpeakerTable {
    void(*speak)(void* _ptr);
};

子类的内存模型如下,狗和人类的虚函数表指针指向的结构体不同

2.3 代码实现编译器内容

相当于原来的子类结构体强转为父类的结构体,子类和父类第一位都是虚函数表指针,然后调用父类虚函数表中的函数,对应着调用子类虚函数表中的函数

#include <iostream>

struct SpeakerTable {
    void(*speak)(void* _ptr);
};

void __dog_speak(void* _ptr) {
    uint8_t* p = (uint8_t*)_ptr;
    p += sizeof(SpeakerTable*);
    size_t b = *((size_t*)p);
    printf("woof! %11u\n", b);
}

void __human_speak(void* _ptr) {
    uint8_t* p = (uint8_t*)_ptr;
    p += sizeof(SpeakerTable*);
    p += sizeof(size_t);
    size_t b = *((size_t*)p);
    printf("hello! %11u\n", b);
}

const static SpeakerTable __dogSpeakTable = {
    __dog_speak
};

const static SpeakerTable __humanSpeakTable = {
    __human_speak
};

struct __ispeaker {
    const SpeakerTable* vt;
    size_t b;
};

struct __dog {
    const SpeakerTable* vt;
    size_t b;
};

struct __human {
    const SpeakerTable* vt;
    size_t b;
    size_t c;
};

__dog* createDog() {
    __dog* ptr = (__dog* )malloc(sizeof(__dog));
    ptr->vt = &__dogSpeakTable;
    ptr->b = 0;
    return ptr;
}

__human* createHuman() {
    __human* ptr = (__human*)malloc(sizeof(__human));
    ptr->vt = &__humanSpeakTable;
    ptr->b = 1;
    ptr->c = 2;
    return ptr;
}

int main(int _argc, char** _argv)
{
    __dog* dog = createDog();
    __human* human = createHuman();

    __ispeaker* speaker1 = (__ispeaker*)dog;
    __ispeaker* speaker2 = (__ispeaker*)human;

    speaker1->vt->speak(speaker1);
    speaker2->vt->speak(speaker2);

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1026538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JAVA-Day29】 多维数组和一维数组的区别:简明对照

多维数组和一维数组的区别&#xff1a;简明对照 多维数组和一维数组的区别&#xff1a;简明对照摘要&#xff08;博主语气&#xff09;&#xff1a;多维数组和一维数组是编程中常用的数据结构&#xff0c;它们在定义和使用上有很大的不同。本文将详细介绍它们的区别&#xff0c…

优麒麟下载、安装、体验

下载 官网 优麒麟 点击增强版、或者基础版进行下载 虚拟机安装 选择镜像 修改名称和存储路径 设置为50G 下一步&#xff0c;点击完成 开启安装 设置语言 去掉下载更新选项 继续 点击restart now 输入密码 出现下图说明安装成功&#xff0c;可以畅快的使用了

React中插槽处理机制

React中插槽处理机制 需求&#xff1a;假如底部可能有按钮&#xff0c;根据需求判断需要展示或不展示&#xff0c;或者需要展示不同的按钮或者其他DOM 解决1&#xff1a;需要的按钮可以在组件中写死&#xff0c;后期基于传递进来的属性来进行判断 解决2&#xff1a;我们也可以…

Nacos安装指南(Windows环境)

Windows安装 开发阶段采用单机安装即可。 1.下载安装包 在Nacos的GitHub页面&#xff0c;提供有下载链接&#xff0c;可以下载编译好的Nacos服务端或者源代码&#xff1a; GitHub主页&#xff1a;https://github.com/alibaba/nacos GitHub的Release下载页&#xff1a;https:…

基于微信小程序的音乐播放器设计与实现(源码+lw+部署文档+讲解等)

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…

Matlab学习笔记(不定期更新)

MATLAB学习笔记 个人上了一点网课之后&#xff0c;感觉MATLAB自带的学习指导真好用 添加所需要的变量 >> a 1 a 1 >> ans sin(a) ans 0.8415edX提供的各项MATLAB函数 遇到不会使用的函数怎么办&#xff1f; 通过在搜索文档里键入自己需要搜索的函数&#xff…

数据结构---AVL树

AVL树 AVL树的概念AVL树节点的定义AVL树的插入源代码 AVL树的概念 二叉搜索树虽然可以缩短查找的效率&#xff0c;但是&#xff0c;如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率就会变低。因此&#xff0c;两…

Java移除链表元素

目录 1.题目描述 2.题解 题解1 题解2 1.题目描述 给你一个链表的头节点 head 和一个整数 val&#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 输入&#xff1a;head [1,2,6,3,4,5,6]&#xff0c;val 6 输出&#xff1a;…

饥饿游戏搜索算法(HGS)(含java实现代码)

Hunger games search: Visions, conception, implementation, deep analysis, perspectives, and towards performance shifts 期刊:Expert Systems With Applications SCI1区 主体框架 public HGS(){initialize();calculateFitness();sortTheFitness();calculateHungry();for…

分享团队在软件开发中用到的神仙工具

目前使用的是JNPF框架。 技术栈上使用的SpringBoot、SpringCloud、Spring Web、MyBatis、Swagger、Vue、Element。 这些都是比较主流的技术&#xff0c;无论是技术层面的先进性还是学习难度都是比较低的&#xff0c;目前网络上有大量可供参考学习的资料。 并且它支持前后端分离…

中文转拼音(带音调)

导入maven依赖 <!--导入pinyin4j库--><dependency><groupId>com.belerweb</groupId><artifactId>pinyin4j</artifactId><version>2.5.1</version></dependency>demo如下&#xff1a; import com.github.stuxuhai.jpiny…

方案:数智化视频AI技术为智慧防汛筑基,构建防汛“数字堤坝”

一、背景分析 在过去的几年中&#xff0c;全球气候变化导致许多城市在雨季面临严重的洪涝灾害。这些灾害不仅对人们的生命安全和财产造成威胁&#xff0c;也影响了城市的正常运转。传统的防汛手段主要依赖人力监控和应急指挥&#xff0c;但存在响应速度慢、处理效率低等问题。…

【操作系统】进程控制与进程通信

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 操作系统 一、进程控制1.1 什么是进程控制1…

vue element 搜索框根据后台的接口实现模糊查询 + 分页特殊处理+重置表格

模糊查询效果图 1.配置接口 search: "/api/goods/search", //搜索接口/goods/search 2.get接口 search(params) { return axios.get(base.search, { params });//后台传参 再写这个params }, 3.异步请求接口 // 搜索接口async search(search){let res await this…

基于Java社区生鲜电商平台设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

离子风刀的特点以及应用领域

静电消除器离子风刀是一种用于消除静电的设备&#xff0c;它通过在空气中产生离子&#xff0c;将静电从物体表面或人体上释放掉&#xff0c;以保护电子设备和人体免受静电的损害。 离子风刀的特点包括&#xff1a; 1、高效&#xff1a;离子风刀能够快速消除静电&#xff0c;提…

CRM软件系统趣味性——游戏化销售管理

对于企业销售来说&#xff0c;高薪酬也伴随着更高的压力与挑战。高强度的单一工作会让销售人员逐渐失去对工作的兴趣&#xff0c;导致售状态缺少动力和激情&#xff0c;工作开展愈加困难。您可以通过CRM系统进行游戏化销售管理&#xff0c;让销售人员重新干劲满满。 游戏并不是…

SkyWalking使用讲解

文章目录 1 SkyWalking1.1 简介1.2 如何选择1.3 Skywalking架构1.4 服务端搭建1.4.1 下载安装包1.4.2 配置修改1.4.2.1 config/application.yml1.4.2.2 webapp/webapp.yml 1.4.3 启动服务 1.5 客户端搭建1.6 数据持久化1.6.1 修改配置文件1.6.2 添加MySQL的jdbc依赖 1.7 日志监…

Java面经整理(1)

一)Java中支持多继承吗,为什么? 答案:在JAVA中是不支持多继承的,原因是多继承会存在菱形继承的问题 菱形继承: 1)菱形继承也被称之为是钻石继承,是一种在JAVA面向对象编程的时候遇到的一个可能出现的继承问题; 2)假设JAVA支持多继承,那么就有可能一个类D继承两个不同的类…

一文总结提示工程框架,除了CoT还有ToT、GoT、AoT、SoT、PoT

夕小瑶科技说 原创 编译 | 谢年年 大语言模型LLM被视为一个巨大的知识库&#xff0c;它可以根据你提出问题或陈述的方式来提供答案。就像人类可能会根据问题的不同提供不同的答案一样&#xff0c;LLM也可以根据输入的不同给出不同的答案。因此&#xff0c;你的问题或陈述方式就…