c++ virtual 关键字 override 关键字

2023-11-16

1.什么是virtual

virtual是一个C++的修饰符,用于修饰函数,被修饰的函数称为虚函数。

2.为什么需要?

在C++中,我们都知道要实现多态有2种方法,一种是重写,一种是重载,重载是横向的,意思是只发生在同一个类中,根据函数的参数个数,类型不同从而实现重载,而重写则是纵向的,发生在继承中,子类函数覆盖父类函数,父类指针指向子类实体时,应该实现运行时多态。

3.通常用在什么情形?

1.作为基类的类的析构函数
如果一个类做为父类,然后它被别人继承,当用子类指针指向子类时不会出现任何的问题,但是如果用父类指针指向子类时,若没有加析构函数,那只会析构父类的析构函数,这时我们需要用virtual修饰父类的析构函数。

2.需要实现多态的函数
若一个函数需要实现多态,即运行时多态。

1、定义在函数中的对象,在函数调用结束时,在对象释放前调用析构函数
2、定义在函数中的static对象,在函数调用结束时,不调用对象析构函数,在mian函数结束时,会调用对象析构函数
3、定义全局对象或者static全局对象,程序执行流程离开其作用域时,调用对象析构函数
4、用new运算法生成的对象,调用delete运算法释放该对象时,先调用对象析构函数

重写 覆盖的几个函数必须函数名、参数、返回值都相同;

case 1

#include <iostream>
using namespace std;
 
class Base{
  public:
    ~Base() {cout<<"~B"<<endl;}
};
 
class Derived:public Base{
  public:
    ~Derived() {cout<<"~D"<<endl;}
};
 
int main (){
  Base *b = new Derived; //注意这里
  delete b;
}

子类指向父类,查看打印日志:

~B

可见删除子类指向父类的指针,代表delete只删除了父类,并没有删除子类的相关内存,会产生内存泄漏。所以在多态的使用过程中,delete(指向父类的子类对象)需要格外注意是否会没有释放子类或者父类。

case 2

#include <iostream>
using namespace std;
 
class Base{
  public:
    ~Base() {cout<<"~B"<<endl;}
};
 
class Derived:public Base{
  public:
    ~Derived() {cout<<"~D"<<endl;}
};
 
int main (){
  Derived *d = new Derived; //注意这里
  delete d;
}

子类指向子类指针,查看打印日志:

~D

~B

可以得知,delete子类的析构顺序:是先释放子类,在释放父类。

这是我们期望的结果,所以没啥问题。。。

case 3
对于case 1当中的情况,如何去规避呢? 需要将父类的析构函数定义为virtual

#include <iostream>
using namespace std;
 
class Base{
  public:
    virtual ~Base() {cout<<"~B"<<endl;}
};
 
class Derived:public Base{
  public:
    ~Derived() {cout<<"~D"<<endl;}
};
 
int main (){
  Base *d = new Derived; //注意这里
  delete d;
}

打印结果:

~D

~B

原理:delete d; //d虽然为Base*,但是它的析构函数是virtual的,所以它调用析构函数,其实是调用虚函数表中的函数。而Base* d指向的虚函数表在内存中其实是new Derived类的虚函数表。所以通过Base类的virtual函数 + Base* d指向派生类(Derived)对象,从而使得在调用delete d的时候实际上是调用的是delete Derived* d的意思,也就是第二种情况的效果。

4.延伸:虚函数/纯虚函数、override 关键字


虚函数分类:

  • 纯虚函数要求派生类必须重写
  • 普通虚函数有默认实现但可以被派生类重写

使用override和default关键字可以明确指示函数的重写和默认实现。

示例

virtual void Draw() const = 0; // 1) 纯虚函数
virtual void Error(const string& msg); // 2) 普通虚函数
virtual void Error(const string& msg) override = default;	// 错误,= default只能用在默认构造、拷贝构造等默认函数中
virtual void Error(const string& msg) override;

9.问题汇总

9.1 非虚函数和虚函数都可以重写,那区别是啥?

virtual void Error(const string& msg);
void Error(const string& msg);

对于上述两个函数声明:

  1. virtual void Error(const string& msg); 是一个虚函数的声明。虚函数是在基类中声明并带有 virtual 关键字的函数,它可以被派生类重写(覆盖)。虚函数可以通过基类指针或引用来实现动态绑定,即在运行时根据对象的实际类型来调用相应的函数。

  2. void Error(const string& msg); 是一个非虚函数的声明。非虚函数是在类中声明但没有 virtual 关键字修饰的函数。非虚函数在派生类中也可以被重写,但它们不具备动态绑定的特性。当通过基类指针或引用调用非虚函数时,将根据指针或引用的类型来确定调用的函数,而不考虑指向的对象的实际类型。

因此,虚函数和非虚函数的区别在于它们的调用方式和多态性的支持。虚函数可以通过基类指针或引用实现多态性,而非虚函数的调用始终与指针或引用的类型相符,不会发生动态绑定。

9.2 基类虚函数/纯虚函数、子类有没有 override 的区别


override 的作用:用于显式地标识子类中的函数重写父类的虚函数(包括纯虚函数)的一种方式。

使用 override 关键字有以下几个好处:

  1. 明确表明意图:使用 override 关键字可以明确表示子类中的函数是对父类虚函数的重写,可以增强代码的可读性和可维护性

  2. 静态检查错误:使用 override 关键字可以让编译器在子类中发现对父类虚函数的重写错误,例如函数签名不匹配等。

  3. 避免意外创建新函数:如果没有使用 override 关键字,子类可能会意外地创建一个新的函数,而不是重写父类的虚函数。

虽然使用 override 关键字可以提高代码的可读性和安全性,但在某些情况下,如果没有使用 override 关键字,编译器仍然可以进行正确的函数匹配。所以使用 override 关键字是一种良好的编程实践,但不是强制要求。

注意:如果基类不是虚函数,则无法使用override修饰。

9.2 override 和 override = default

在C++中,只有特殊成员函数(默认构造函数、拷贝构造函数、移动构造函数、析构函数和赋值运算符)才能使用 = default; 语法进行默认。普通的成员函数不能使用 = default; 进行默认。

class Shape {
   public:
      Shape(){}
      virtual ~Shape(){  cout << "Parent class ~Shape :" <<endl; }
};
class Rectangle: public Shape{
   public:
      Rectangle(){ }
      virtual ~Rectangle() override = default; 

10.综合实例

#include <iostream> 
using namespace std;
 
class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      virtual ~Shape(){  cout << "Parent class ~Shape :" <<endl; }
      int area()
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
      virtual void test()       // 普通虚函数
      {
         cout << "Parent class test :" <<endl;
      }
      virtual void test2() = 0; // 纯虚函数:子类必须实现
};
class Rectangle: public Shape{
   public:
      Rectangle( int a=0, int b=0):Shape(a, b) { }
      virtual ~Rectangle() override = default; 
      int area ()
      { 
         cout << "Rectangle class area :" <<endl;
         return (width * height); 
      }
      void test() override 
      {
         cout << "Rectangle class test :" <<endl;
      }
      void test2() override
      {
         cout << "Rectangle class test2 :" <<endl;
      }
};
class Triangle: public Shape{
   public:
      Triangle( int a=0, int b=0):Shape(a, b) { }
      virtual ~Triangle(){  cout << "Parent class ~Triangle :" <<endl; }
      int area ()
      { 
         cout << "Triangle class area :" <<endl;
         return (width * height / 2); 
      }
      void test()   //可以不写override,但是写了一目了然
      {
         cout << "Triangle class test :" <<endl;
      }
      void test2()  //可以不写override,但是写了一目了然
      {
         cout << "Triangle class test2 :" <<endl;
      }
};
// 程序的主函数
int main( )
{
   Shape *shape;
   Rectangle rec(10,7);
   Triangle  tri(10,5);
 
    //普通函数:基类、子类:按类型
   shape = &rec;
   shape->area();//基类
 
   shape = &tri;
   shape->area();//基类
   
   //虚函数
   shape = &rec;
   shape->test();//子类
 
   shape = &tri;
   shape->test();//子类
   
   shape->Shape::test();//指定父类
   
   //纯虚函数约定的是子类必须实现、override约定的是重写基类
   
   return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++ virtual 关键字 override 关键字 的相关文章

  • 为什么在从同一解决方案引用另一个项目时会出现 FileNotFound 异常?

    我正在学习如何使用 NUnit 我的解决方案中有我的主项目 并在同一解决方案中创建了一个单独的项目 该项目将保存我的单元测试 并具有自己的命名空间 从该项目中 我添加对主项目的引用并添加 using MainProjectNamespace
  • 如何使用带有进度条的 HttpClient 下载文件?

    我创建了一个名为SiteDownload并添加了一些下载图像的链接 using System Collections Generic using System Linq using System Net using System Threa
  • strtok() 和空字段

    我正在将一些 C 结构序列化为字符串 然后将其反序列化strtok 但不幸的是 strtok 不检测空字段 例如 1 2 4 有没有替代功能 在linux上有strsep http www mkssoftware com docs man3
  • 从数组中输入多个数字,每个数字检查是否为整数

    每个人 我希望有人能帮我弄清楚C语言的一些东西 这是我第一次认真地做IT方面的作业 我没有经验 而且我正在电子学习中学习 所以老师的帮助不是很好 我需要用C语言开发控制台应用程序 用户需要输入10个整数 如果插入的数字不是整数 需要输出错误
  • Web 应用程序框架:C++ 与 Python

    作为一名程序员 我熟悉 Python 和 C 我正在考虑编写自己的简单 Web 应用程序 并且想知道哪种语言更适合服务器端 Web 开发 我正在寻找一些东西 它必须是直观的 我认识到 Wt 存在并且它遵循 Qt 的模型 我讨厌 Qt 的一件
  • 委托和接口如何互换使用?

    我可以使用接口方法代替委托吗 如何 我发现搜索接口方法比使用委托更快 我希望有一个简单的代码片段 理论上 可以通过包含单个方法的接口 例如 Java 没有委托 来完成委托完成的所有工作 然而 它使代码变得更加冗长并且没有带来什么好处 话又说
  • 如何在 C# 中启动文件

    编辑 我觉得自己像个白痴 我有一种感觉 像下面的答案会起作用 但没有看到任何与下面的答案类似的谷歌结果 所以当我看到这段复杂的代码时 我想它一定是这样的 我搜索并找到了这个Windows 列出并启动与扩展关联的应用程序 https stac
  • 用C#发送USSD?

    我想编写一个在 Windows Mobile 6 上运行的简单 C 应用程序 它可以发送 USSD 消息 有没有任何图书馆可以帮助我做到这一点 或者是否有任何示例解释如何使用线路发送USSD http msdn microsoft com
  • 如何获取 C# PriorityQueue 元素的优先级

    我正在初始化一个存储 XY 坐标的优先级队列 根据距原点的欧几里得距离确定优先级 我创建了一个自定义Comparer这使得它作为最大堆运行 PriorityQueue
  • 公共领域有哪些替代方案?

    我正在用 java 编写一个游戏 正如问题标题建议的那样 我在类中使用公共字段 暂且 据我所知 公共领域很糟糕 我有一些理解其中的原因 但如果有人能澄清为什么你不应该使用它们 那将不胜感激 问题是 从我所看到的来看 这似乎是合乎逻辑的 是使
  • 尝试缓冲区溢出

    我正在尝试使用缓冲区溢出来更改函数的结果 以使用以下代码更改堆栈上的结果 include
  • 如何修改 edmx 的默认代码生成策略?

    我想修改默认的代码生成策略 该怎么做 我只是想修改类名 lt code Escape container gt to Entities并将默认连接字符串更改为name Default 我不想为该项目创建模板文件 我想编辑它以便它可以在全球范
  • 如何在 C# 中停止程序进一步执行

    string FirstName Console ReadLine if FirstName Length gt 12 Console WriteLine if FirstName Length lt 3 Console WriteLine
  • 将数组显式衰减为指针

    最简洁 最惯用的方式是什么明确地将数组衰减为指针 例如 考虑您需要能够指导 SFINAE 或明确过载的情况 template
  • Roslyn,通过 hostObject 传递值

    我正在尝试通过 hostObject 发送一个类 但显然它不想工作 using Roslyn Compilers using Roslyn Compilers CSharp using Roslyn Scripting using Rosl
  • 仅最后一个用户控件显示内容控件

    我有一个奇怪的问题 我创建了一个带有标签和画布的用户控件 画布引用资源 但画布仅显示在我的堆栈面板中的最后一个控件上 这是我的窗户
  • 将 .NET 类库(主要定义 CRUD 操作)公开为服务

    公开现有内容的最佳 有效和最快的方法是什么 类 图书馆 主要定义 CRUD 操作 作为service 周转基金服务 or WCF数据服务 以便它可以与银光 or Ajax 在那儿tools 代码生成器 RAD 工具 哪些可以支持这个 预先感
  • 在运行时将项目添加到 ToolStrip

    您好 我有一个带有 收藏夹 菜单的 ToolStripMenu 我想在运行时在 WinForms 应用程序中添加子项目 我有一个 datagridview 右键单击它会显示一个包含 添加到收藏夹 选项的上下文菜单 当该事件被触发时 我想使用
  • 返回右值 - 这段代码有什么问题? [复制]

    这个问题在这里已经有答案了 我遇到了以下代码片段 std string test std string m Hello return std move m int main std string m test 我知道上面的代码是不正确且不安
  • InvalidOperationException:没有为方案“CookieSettings”注册身份验证处理程序

    我正在使用 ASP Net MVC core 2 1 开发一个应用程序 其中不断出现以下异常 InvalidOperationException 没有为方案 CookieSettings 注册身份验证处理程序 注册的方案有 Identity

随机推荐

  • python英文(无空格)文本分词模块wordninja使用

    在NLP中 数据清洗与分词往往是很多工作开始的第一步 大多数工作中只有中文语料数据需要进行分词 现有的分词工具也已经有了很多了 这里就不再多介绍了 英文语料由于其本身存在空格符所以无需跟中文语料同样处理 如果英文数据中没有了空格 那么应该怎
  • react在移动端的自适应布局

    1 移动端基本可以无阻碍的进行flex的弹性布局 这边对flex就不进行深究 2 可以进行依据窗口进行vw vh vmin vmax单位的布局 1 vw vh vmin vmax 是一种视窗单位 也是相对单位 它相对的不是父节点或者页面的根
  • Android USB Camera(1) : 调试记录

    1 前言 前段时间调试了一个uvc摄像头 这里做下记录 硬件平台为mt6735 软件平台为android 5 0 2 底层配置 UVC全称是usb video class 一种usb视频规范 所有遵循uvc协议的摄像头都不需要安装额外的驱动
  • integer conversion resulted in a change of sign

    Type 69 D integer conversion resulted in a change of sign MDK 出现 68 D integer conversion resulted in a change of sign 在K
  • 三行代码实时追踪你的手,只要有浏览器就够了

    栗子 发自 凹非寺 量子位 报道 公众号 QbitAI Are You OK O K 人脸不管做了多么一言难尽的表情 五官也不太会四处乱跑 手就不一样了 手势百媚千娇 镜头看到的画面就百媚千娇 所以 AI怎么识别手呢 一位叫做Victor
  • Java获取月份天数错误

    之前编写获取日期函数如下 获取某年某月有多少天 return 该月的天数 public static int getDaysAboutMonth int year int month Calendar c Calendar getInsta
  • 电力电子转战数字IC——我的IC面试(2022.10.14更新)

    目录 感谢信 HKWS10 14面试 25mins JXC10 13面试 30mins JDSK9 23面试 42mins 快速的自我介绍 介绍一下这个MCDF的项目 你这里写SV搭建的验证环境 和UVM搭建的有什么区别吗 你这里写了覆盖率
  • 正视周期,创业在衰退中砥砺前行

    比衰退更重要的是 早期投资机构正在面临结构性机遇 数科星球原创 作者丨苑晶 编辑丨大兔 周期 犹如一只隐形的手 在2023年影响着芸芸众生 从经济周期 行业周期再到货币周期 这个隐形的手牵动着消费者需求 产业链变迁 政策变动等多种因素 亦牵
  • 12.SpringBoot整合mybatis实现插入操作

    本文基于10 springboot整合mybatis环境 默认环境配置好的 下面进入正题 首先查看以下数据库表有什么内容 然后 在EmpMapper中定义插入方法 并在emp xml中加入insert语句 这里推荐使用navicat复制其中
  • 概率图论:了解概率分布、概率独立性和随机化

    作者 禅与计算机程序设计艺术 概率图模型 Probabilistic Graphical Model PGM 是现代统计学习中的一个重要工具 它通过描述变量间的依赖关系和概率分布来对复杂系统进行建模 概率图模型由两部分组成 一是概率模型 它
  • QPoint与QPointF的区别

    QPointF类使用浮点精度定义平面中的点 QPoint类使用整数精度定义平面中的点
  • C语言链表嵌套链表学生成绩管理系统

    一阶段考核标准 用C语言链表嵌套链表学生成绩管理系统 链表A 每个节点存放一个新的链表B1 B2 B3 B4 B5的头结点 场景 一个年级 相当链表A 该年级5个班 每个班5个人 相当于链表B1 B5 做一个学生成绩管理系统学生成绩有语文
  • java版工程管理系统Spring Cloud+Spring Boot+Mybatis实现工程管理系统源码

    工程项目管理软件 工程项目管理系统 对建设工程项目管理组织建设 项目策划决策 规划设计 施工建设到竣工交付 总结评估 运维运营 全过程 全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一 系统管理 1 数据字典 实现对数据字典标签
  • Java中transient关键字的详细总结

    目录 一 概要介绍 1 序列化 2 为什么要用transient关键字 3 transient的作用 二 transient使用总结 三 使用场景 一 概要介绍 本文要介绍的是Java中的transient关键字 transient是短暂的
  • the left operand of ** is a garbage value

    CapLocation location 0 if segmentedControl tag SegmentControlTag if segmentIndex 0 location CapLeft else if segmentIndex
  • 最全的前端性能优化手段回答

    前端性能优化手段 参考答案 前端性能优化手段从以下几个方面入手 加载优化 执行优化 渲染优化 样式优化 脚本优化 1 加载优化 减少HTTP请求 缓存资源 压缩代码 无阻塞 首屏加载 按需加载 预加载 压缩图像 减少Cookie 避免重定向
  • 时序预测模型汇总

    时序预测模型 一 自回归 AR 在 AR 模型中 我们使用变量过去值的线性组合来预测感兴趣的变量 术语自回归表明它是变量对自身的回归 二 移动平均模型 MA 与在回归中使用预测变量的过去值的 AR 模型不同 MA 模型在类似回归的模型中关注
  • 三角函数的向量表示的原理计算

    在 电路 中 三相电源经常用复数或者是向量来表示 但是与我们初高中熟知的空间向量不同 这里的三相交流电是一种时间向量 由于采用的形式是正弦形式 使得其也可以用空间向量中的平行四边形原则来进行计算合成 下面将介绍一下正弦量可以用向量表示的原理
  • 生成tensorrt引擎错误记录-yolov5

    warning nvinfer1 Dims type is deprecated Wdeprecated declarations note TRT DEPRECATED DimensionType type MAX DIMS lt The
  • c++ virtual 关键字 override 关键字

    文章目录 1 什么是virtual 2 为什么需要 3 通常用在什么情形 4 延伸 虚函数 纯虚函数 override 关键字 9 问题汇总 9 1 非虚函数和虚函数都可以重写 那区别是啥 9 2 基类虚函数 纯虚函数 子类有没有 over