C++之虚函数

news2024/12/24 8:50:00

都说面向对象的三大特性是封装、继承、多态。C++作为一门面向对象编程语言,肯定也是具备了面向对象的三大特性,那么在C++中是如何实现多态的呢?

在C++中是通过虚函数动态绑定的方式实现多态的。

虚函数与纯虚函数

首先我们来回顾一下虚函数,在C++中是使用virtual关键字修饰的函数就是虚函数,下面是一个简单的虚函数例子:

class Base{
public:
    // 虚函数,必须实现,否则编译报错
    virtual void f1() const{
        std::cout << "我是Base的f1函数" << std::endl;
    }
};

class A:public Base{
    void f1() const override{
        std::cout << "我是A的f1函数" << std::endl;
    }
};

虚函数必须在基类中实现,如果不实现的话就会编译报错。

如果我们不想实现虚函数的话可以将其声明为纯虚函数,纯虚函数的声明方式如下:

virtual 返回值类型 函数名(函数参数) = 0;

声明了纯虚函数的类称为抽象类,继承抽象类的最终子类必须父类的纯虚函数,否则不能生产对应的类对象。以下是一个纯虚函数的例子:

class Base{
public:
    // 虚函数,必须实现,否则编译报错
    virtual void f1() const{
        std::cout << "我是Base的f1函数" << std::endl;
    }

    // 纯虚函数,不需要实现
    virtual void f2() const = 0;
};

class A:public Base{
public:
    void f1() const override{
        std::cout << "我是A的f1函数" << std::endl;
    }

    void f2() const override{
        std::cout << "我是A的纯虚函数f2" << std::endl;
    }
};

有了虚函数我们就能通过基类的的指针进行动态绑定,在运行时访问到子类的函数,但是动态绑定只能发生在指针或引用上。例如在以下的例子中,函数test3是不会访问到子类的函数的, 它访问的函数依然是基类的虚函数,也就是说它没有发生动态绑定,因为它既不是指针也不是引用。

class Base{
public:
    // 虚函数,必须实现,否则编译报错
    virtual void f1() const{
        std::cout << "我是Base的f1函数" << std::endl;
    }
};

class A:public Base{
public:
    void f1() const override{
        std::cout << "我是A的f1函数" << std::endl;
    }
};

// 引用传递参数
void test1(const Base &base){
    base.f1();
}

// 指针传递参数
void test2(Base *base){
    base->f1();
}

// 非引用、非指针传递参数
void test3(Base base){
    base.f1();
}

int main(int arg,char** argv) {
    Base *base =  new A();
    test1(*base);
    test2(base);
    test3(*base);
    return 0;
}

运行打印结果:

我是A的f1函数
我是A的f1函数
我是Base的f1函数

【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~

  

如果一个类可能会被继承,这个类的析构函数应该被声明为一个虚函数,否则会引发内存泄漏。例如以下例子:

class Base{
public:
    // 虚函数,必须实现,否则编译报错
    virtual void f1() const{
        std::cout << "我是Base的f1函数" << std::endl;
    }

    ~Base(){
        std::cout << "Base的析构函数" << std::endl;
    }
};

class A:public Base{
public:
    void f1() const override{
        std::cout << "我是A的f1函数" << std::endl;
    }

    ~A(){
        std::cout << "A的析构函数" << std::endl;
    }
};

int main(int arg,char** argv) {
    Base *base =  new A();
    delete base;
    return 0;
}

运行输出如下:

Base的析构函数

从运行结果可以看出A没有被正确析构,这是因为它的基类Base的析构函数没有被声明为虚函数的原因,此时只要我们把Base类的析构函数声明为虚函数即可修复这个内存泄漏的问题,也就是:

class Base{
public:
    // 虚函数,必须实现,否则编译报错
    virtual void f1() const{
        std::cout << "我是Base的f1函数" << std::endl;
    }

    // 可能被继承的类的析构函数应该是一个虚函数
   virtual ~Base(){
        std::cout << "Base的析构函数" << std::endl;
    }
};

虚函数总结:

1、当我们在派生类中覆盖了某个虚函数时,可以再一次使用virtual关键字指出该函数的性质。然而这么做并非必须,因为一旦某个函数被声明成虚函数,则在所有派生类中它都是虚函数。
2、虚函数只有在引用或者指针调用时才会发生动态绑定;
3、基类的析构函数需要声明为虚函数;
4、虚函数必须要在基类实现,不实现,编译会报错;
5、如果子类没有实现父类的纯虚函数,则该子类不能被构造成一个对象。

多态实现原理-虚函数表

通过上面的例子我们知道了在C++中通过引用或指针的形式进行虚函数的动态绑定而实现多态,那么动态绑定在C++中是如何实现呢?答案是虚函数表。

所谓的虚函数表就是:

当编译器在编译过程中遇到virtual关键字时,它不会对函数调用进行绑定,而是为包含虚函数的类建立一张虚函数表Vtable。在虚函数表中,编译器按照虚函数的声明顺序依次保存虚函数地址。同时,编译器会在类中添加一个隐藏的虚函数指针VPTR,指向虚函数表。在创建对象时,将虚函数指针VPTR放置在对象的起始位置,为其分配空间,并调用构造函数将其初始化为虚函数表地址。需要注意的是,虚函数表不占用对象空间。

虚函数表总结:
1、单继承下的虚函数表

虚函数表中的指针顺序,按照虚函数声明的顺序排序;基类的虚函数指针在派生类的前面。

2、多继承下的虚函数表

多继承关系下会有多个虚函数表,也会有多个指向不同虚函数表的指针;

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

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

相关文章

DAX:GROUPBY函数

DAX 中的 SUMMARIZE 函数功能强大&#xff0c;但同时也很难使用。 它可用于执行表之间的分组和连接&#xff0c;正如我们之前在分组数据一文中描述的那样。 不幸的是&#xff0c;它在计算聚合值时存在一些众所周知的性能问题。除了性能之外&#xff0c;SUMMARIZE 的计算限制是它…

ArrayList中的 subList 强转 ArrayList 导致异常

阿里《Java开发手册》上提过 [强制] ArrayList的sublist结果不可強转成ArrayList,否则会抛出ClassCastException异常&#xff0c;即java.util.RandomAccesSubList cannot be cast to java. util.ArrayList. 说明: subList 返回的是ArrayList 的内部类SubList, 并不是ArrayList …

一块RTX 3090加速训练YOLOv5s,时间减少11个小时,速度提升20%

作者&#xff5c;BBuf 很高兴为大家带来One-YOLOv5的最新进展&#xff0c;在《一个更快的YOLOv5问世&#xff0c;附送全面中文解析教程》发布后收到了很多算法工程师朋友的关注&#xff0c;十分感谢。 不过&#xff0c;可能你也在思考一个问题&#xff1a;虽然OneFlow的兼容性做…

SQL之substrate()函数用法

测试表字段查询如下&#xff1a; 测试在hive中截取前5位字符的第1种写法&#xff1a; 测试在hive中截取前5位字符的第2种写法&#xff1a; 测试在impala中截取前5位字符的第1种写法&#xff1a; 测试在impala中截取前5位字符的第2种写法&#xff1a; 结果&#xff1a; 1、在h…

带分数(蓝桥杯C/C++B组真题详解)

目录 题目 题目思路 题目代码 注解&#xff1a; 1.题目给定的判定条件为 2.关于next_permutation(start,end); 题目&#xff1a; 题目思路&#xff1a; 因为题目要求是满足在1到9中 不重复、不遗漏的所有满足条件的情况 所以我们可以通过全排列 把整数、分子、分母 …

B-树(B-Tree)与二叉搜索树(BST):讲讲数据库和文件系统背后的原理(读写比较大块数据的存储系统数据结构与算法原理)...

人类总喜欢发明创造一些新名词&#xff08;比如说&#xff0c;简写/缩写/简称什么的&#xff09;&#xff0c;并通过这些名词把人群分成了三六九等。弄到最后&#xff0c;把自己都绕晕了。你看&#xff0c;首先就是&#xff0c;B树&#xff0c;不要与Binary tree或Btree混淆。B…

有奖征文 | 当我们谈操作系统时,我们在谈什么?

OS&#xff0c;Operating System&#xff0c;操作系统&#xff0c;计算机中最基本也是最重要的基础性系统软件。1991 年&#xff0c;大二学生 Linus Torvalds 写出 Linux0.01&#xff0c;经过几十年的发展&#xff0c;以 Linux 为代表的服务器操作系统&#xff0c;成长为一个既…

lambda之Stream流式编程

lambda之Stream流式编程 一、什么是 Stream Stream中文称为 “流”&#xff0c;通过将集合转换为这么一种叫做“流”的元素序列&#xff0c;通过声明性方式&#xff0c;能够对集合中的每个元素进行一系列并行或串行的流水线操作。换句话说&#xff0c;你只需要告诉流你的要求…

led护眼台灯对眼睛好?过来人说说led护眼灯是否真的能护眼

众所周知&#xff0c;现在绝大部分光源都是使用led发光&#xff0c;无论是室内照明灯、室外装饰灯、气氛调节灯、工作学习护眼台灯等等&#xff0c;都是使用led灯珠&#xff0c;那么也就有人会问了&#xff1a;led灯真的对眼睛好吗&#xff1f;Led护眼台灯真的能护眼吗&#xf…

TH7-搜附近

TH7-搜附近说明1、探花1.1、查询推荐列表dubbo服务1.1.1、实体对象1.1.2、定义接口1.1.3、编写实现1.1.4、单元测试1.2、查询推荐列表APP接口实现1.2.1、TanHuaController1.2.2、TanHuaService1.2.3、测试1.3、喜欢的dubbo服务1.3.1、定义接口1.3.2、编写实现1.4、左滑右滑1.4.…

fofa搜索漏洞技巧

fofa搜索漏洞技巧整理,主要有以下十个方面:搜索HTTP响应头中含有"thinkphp"关键词的网站和IP;加上标题带有后台的;加上时间,现在新网站有thinkphp日志泄露的有很多; 搜索html正文中含有"管理后台"关键词的网站和IP body="管理后台"等。 …

Linux内核缓存

【推荐阅读】 轻松学会linux下查看内存频率,内核函数,cpu频率 纯干货&#xff0c;linux内存管理——内存管理架构&#xff08;建议收藏&#xff09; 一篇长文叙述Linux内核虚拟地址空间的基本概括 页缓存和块缓存 内核为块设备提供了两种通用的缓存方案&#xff1a; 页缓存&a…

光华股份深交所上市:市值51亿 应收账款余额超5亿

雷递网 雷建平 12月8日浙江光华科技股份有限公司&#xff08;简称&#xff1a;“光华股份”&#xff0c;证券代码&#xff1a;001333&#xff09;今日在深交所主板上市。光华股份本次发行3200万股&#xff0c;发行价为27.76元&#xff0c;募资8.88亿元。光华股份开盘价为33.31元…

开源,是不道德的!

原创&#xff1a;小姐姐味道&#xff08;微信公众号ID&#xff1a;xjjdog&#xff09;&#xff0c;欢迎分享&#xff0c;非公众号转载保留此声明。请删掉你的github开源代码&#xff0c;让CV工程师成为真正的工程师。不要做真正的代码分享&#xff0c;因为除了满足一下你的虚荣…

vue-cli中学习vue

vue部分知识 大部分学习内容及代码在gitee仓库 生命周期 基本介绍 生命周期描述beforeCreate组件实例被创建之初created组件实例已经完全创建beforeMount组件挂载之前mounted组件挂载到实例上去之后beforeUpdate组件数据发生变化&#xff0c;更新之前updated组件数据更新之后…

springboot知识点

基本介绍 微服务最早由Martin Fowler与James Lewis于2014年共同提出&#xff0c;微服务架构风格是一种使用一套小服务来开发单个应用的方式途径&#xff0c;每个服务运行在自己的进程中&#xff0c;并使用轻量级机制通信&#xff0c;通常是HTTP API&#xff0c;这些服务基于业…

TH8-小视频方案

TH8-小视频方案说明1、我的访客1.1、dubbo服务1.1.1、实体对象1.1.2、定义接口1.1.3、编写实现1.2、记录访客数据1.3、首页谁看过我1.3.1、VO对象1.3.2、MovementController1.3.3、MovementService2、小视频功能说明3、FastDFS2.1、FastDFS是什么&#xff1f;2.2、工作原理2.1.…

会员消费占比高达96%,孩子王究竟是怎么做到的?

&#x1f446;点击关注公众号&#x1f446;1.孩子王&#xff1a;依靠会员“稳江山”2021年上半年&#xff0c;增长黑盒独家发布了一篇关于孩子王的研究文章《万字拆解孩子王&#xff1a;充满矛盾的母婴零售之王》&#xff0c;彼时&#xff0c;孩子王尚在二度上市的前夕等待敲钟…

JAVA SCRIPT设计模式--行为型--设计模式之Template Method模板方法(22)

JAVA SCRIPT设计模式是本人根据GOF的设计模式写的博客记录。使用JAVA SCRIPT语言来实现主体功能&#xff0c;所以不可能像C&#xff0c;JAVA等面向对象语言一样严谨&#xff0c;大部分程序都附上了JAVA SCRIPT代码&#xff0c;代码只是实现了设计模式的主体功能&#xff0c;不代…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java课程教学过程f6oz5

对于即将毕业或者即将做课设的同学而言&#xff0c;由于经验的欠缺&#xff0c;面临的第一个难题就是选题&#xff0c;确定好题目之后便是开题报告&#xff0c;如果选题首先看自己学习那些技术&#xff0c;不同技术适合做不同的产品&#xff0c;比如自己会些简单的Java语言&…