C++ 中重写重载和隐藏的区别

news2024/9/20 10:56:20

重写(override)、重载(overload)和隐藏(overwrite)在C++中是3个完全不同的概念。我们这里对其进行详细的说明

1、重写(override)是指派生类覆盖了基类的虚函数,这里的覆盖必须满足有相同的函数签名和返回类型,也就是说有相同的函数名、形参列表以及返回类型。

2、重载(overload)是指C++允许在同一作用域中声明几个功能类似的同名函数,这些函数的函数名相同,但是函数签名不同,也就是说有不同的形参。

3、隐藏(overwrite)是指基类成员函数,无论它是否为虚函数,当派生类出现同名函数时,如果派生类函数签名不同于基类函数,则基类函数会被隐藏。如果派生类函数签名与基类函数相同,则需要确定基类函数是否为虚函数,如果是虚函数,则这里的概念就是重写;否则基类函数也会被隐藏。另外,如果还想使用基类函数,可以使用using关键字将其引入派生类。

一、重写

函数重写的基本原则:在基类中,通过使用关键字 virtual 来声明一个虚函数,派生类可以通过重新定义基类中的虚函数来实现函数重写。

例如:

class Father {
public:
     virtual void func() {
        cout << "Father" << endl;
    }
};

class Child: public Father {
public:
    void func() {
        cout << "Child" << endl;
    }
};

int main() {
    Father f;
    Child c;
    f.func();
    c.func();
}

以上代码实现了子类重写父类中的函数。所以父类子类调用同一个函数会有不同的实现。仿真如下:

image-20240515164906765

1.1、重写引发的问题

重写虚函数很容易出现错误,原因是C++语法对重写的要求很高,稍不注意就会无法重写基类虚函数。且这些错误不易被发现,编译器可能也不会提示:

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() {}
    virtual void foo(int &x) {}
    virtual void bar() {}
    virtual void baz() {}
};

Derived的4个函数都没有触发重写操作。第一个派生类虚函数sone_func的函数名与基类虚函数some_func不同,所以它不是重写。第二个派生类虚函数foo(int &x)的形参列表与基类虚函数foo(int x)不同,所以同样不是重写。第三个派生类虚函数bar()相对于基类虚函数少了常量属性,所以不是重写。最后的基类成员函数baz根本不是虚函数,所以派生类的baz函数也不是重写。

1.2、使用override说明符

重写容易出错,尤其继承关系非常复杂的时候。所以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 {}
};

如果没有override说明符,则修改基类虚函数将面临很大的风险,因为编译器不会给出错误提示,我们只能靠测试来检查问题所在。

1.3、使用final说明符

可以为基类声明纯虚函数来迫使派生类继承并且重写这个纯虚函数。但是一直以来,C++标准并没有提供一种方法来阻止派生类去继承基类的虚函数。C++11标准引入final说明符解决了上述问题,它告诉编译器该虚函数不能被派生类重写。final说明符也需要声明在虚函数的尾部。

class Father {
public:
     virtual void func() final {
        cout << "Father" << endl;
    }
};

class Child: public Father {
public:
	// 报错,不能重写final修饰的函数
    void func() {
        cout << "Child" << endl;
    }
};

最后要说明的是,final说明符不仅能声明虚函数,还可以声明类。如果在类定义的时候声明了final,那么这个类将不能作为基类被其他类继承

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

// 报错,不能继承final修饰的类
class Derived : public Base {
public:
    void foo(int x) {};
};

1.4、override和final的特别之处

在C++11标准中,override和final并没有被作为保留的关键字,其中override只有在虚函数尾部才有意义,而final只有在虚函数尾部以及类声明的时候才有意义,因此以下代码仍然可以编译通过:

void override() {}
void final() {}

二、重载

函数重载的条件:

1、参数个数不同

2、参数类型不同

3、参数顺序不同

void fun(int i) {
	cout << "打印整数: " << i << endl;
}
void fun(int i, int j) {
	cout << "打印两个整数: " << i << " 和 " << j << endl;
}
void fun(float f) {
	cout << "打印浮点数: " << f <<endl;
}

fun(4);				// 调用第一个 fun 函数  
fun(2, 3);			// 调用第二个 fun 函数
fun(1.5f);          // 调用第三个 fun 函数  

**注意:**返回值不同不是函数重载的判断标准

void fun(int i) {
	cout << "打印整数: " << i << endl;
}

int fun(int i) {
	cout << "打印整数: " << i << endl;
	return 0;
}

fun(4);             // 报错,并不知道调用哪个函数

2.1、函数重载的底层原理

为什么C++支持函数重载而C不支持?C语言中同名函数编译完还是同名的,两个重名函数的地址都是有效值,所以在重定位的时候就会产生冲突和歧义。而C++会对函数名进行修饰,例如:

void f(int a, double b) { printf("%d %lld", a, b) }

函数名 f 会被修正为 _Z1fid,Linux下的命名规则为

函数名被修饰为:_Z + 函数名长度 + 函数名 + 各个形参类型首字母的小写

这也就解释了为什么函数重载和返回值无关,为什么和参数个数,类型,顺序不同就可以重载,因为他们修饰完后的函数名就是不同的。

三、隐藏

隐藏指在某些情况下,派生类中的函数屏蔽了基类中的同名函数:

1、两个函数参数相同,但是基类不是虚函数。和重写的区别在于基类函数是否是虚函数

2、两个函数参数列表不同,无论基类函数是否虚函数,基类函数都将被覆盖。和重载的区别在于两个函数不在同一个类中

下面举例说明:

class Base {
public:
	void funA(){cout<<"funA()"<<endl;}
	virtual void funB(){cout<<"funB()"<<endl;} 
};

class Heri:public Base {
public:
    // 隐藏,基类中同名函数不是虚函数
	void funA(){cout<<"funA():Heri"<<endl;}
    // 隐藏,参数列表不同,无论基类是否是虚函数,基类函数都将被覆盖
	void funA(int a){cout<<"funA(int a):heri"<<a<<endl;}
    // 重写,基类是虚函数
	void funB(){cout<<"funB():heri"<<endl;}
};

隐藏使用的时候记住一句,派生类的指针或引用,对象调用子类和父类同名的函数,父类的同名函数被子类隐藏,调用的是子类的函数

看下面代码:

class Base {
public:
    void fun1() { cout<<"base:fun1()"<<endl; fun(); }
    virtual void fun() { cout<<"base:fun()"<<endl; }
};

class Deriverd:public Base {
public:
    virtual void fun1() { cout<<"deriverd:fun1()"<<endl; }
    void fun() { cout<<"deriverd:fun()"<<endl; }
};

int main() {
    Base *pb = new Deriverd;
    pb->fun1();
    return 0;
}

输出结果为:

image-20240515173641206

main函数中创建了父类的指针指向了子类的对象,然后通过父类的指针调用具有隐藏关系的fun1()函数,我们会以为pb->fun1()调用的是子类的函数fun1(),实际并不是,隐藏关系的函数,谁调用就用谁的函数,按照正常的函数调用使用便可得正确的结果,这里是父类指针调用,就用父类的函数fun1()。这就是隐藏和重写的区别。

四、重写与隐藏的区别

看下面的代码:

class Base {
public:
    virtual void foo(int x) { cout << "Base: " << x << endl;}
    void foo(int x, int y) { cout << "Base: " << x << ' ' << y << endl; }
};

// 报错,不能继承final修饰的类
class Derived : public Base {
public:
    void foo(int x) { cout << x << endl; };
    void foo(int x, int y) {cout << x << ' ' << y << endl; }
};
int main() {
    Base *pb = new Derived;
    pb->foo(1);
    pb->foo(1, 2);

    return 0;
}

image-20240515174346499

其中 foo(int x, int y) 函数发生了隐藏,void foo(int x)函数发生了重写, Base *pb = new Derived;发生了父类指针指向子类对象,隐藏由于是父类指针,所以调用了父类的实现,重写由于是子类对象,所以调用了子类实现。

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

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

相关文章

gitlab webhook触发jenkins任务

配置jenkins 安装gitlab插件 配置jenkins job 选择gitlab webhook触发 在高级中生成token 代码仓设置 新增webhook 配置webhook 测试连接 缺点&#xff0c;不能带gitLab事件的参数&#xff01;&#xff01;&#xff01;

Xilinx RAM IP核的使用及注意事项

对于RAM IP核(Block Memory Generator核)的使用以及界面的配置介绍&#xff0c;文章RAM的使用介绍进行了较详细的说明&#xff0c;本文对RAM IP核使用中一些注意的地方加以说明。 文章目录 三种RAM的区别单端口RAM(Single-port RAM)简单双端口RAM(Simple Dual-port RAM)真双端…

Spring Security入门教程:实现自定义用户配置

在上一篇文章中&#xff1a;Spring Security入门教程&#xff1a;利用Spring Security实现安全控制 我们学会了使用Spring Security实现安全控制&#xff0c;学会了他的基础使用&#xff0c;这节课我们来学习一下它的自定义的功能&#xff0c;更深入的了解和使用Spring Securit…

计算机组成结构—寻址方法

目录 一、指令寻址 二、数据寻址 1.立即寻址 2.直接寻址 3.间接寻址 4.隐含寻址 5.寄存器寻址 6.寄存器间接寻址 7.基址寻址 8.变址寻址 9.相对寻址 10. 堆栈寻址 寻址方式是寻找指令或操作数有效地址的方式&#xff0c;也就是指确定本条指令的数据地址&#xff0c;…

C++错题集(持续更新ing)

Day 1 一、选择题 解析&#xff1a; 在数字不会溢出的前提下&#xff0c;对于正数和负数&#xff0c;有&#xff1a; 1&#xff09;左移n位&#xff0c;相当于操作数乘以2的n次方&#xff1b; 2&#xff09;右移n位&#xff0c;相当于操作数除以2的n次方。 解析&#xff1a…

深度盘点在当今经济形势下资深项目经理或PMO的或去或从

在当今经济形势下&#xff0c;资深项目经理&#xff08;Project Manager&#xff09;或项目管理办公室&#xff08;PMO&#xff09;的去向和选择受到多种因素的影响。以下是对他们可能面临的或去或从的深度盘点&#xff1a; 1、发展去向 1. 深化专业领域&#xff1a;在经济形势…

2024年【制冷与空调设备运行操作】考试内容及制冷与空调设备运行操作考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 制冷与空调设备运行操作考试内容是安全生产模拟考试一点通生成的&#xff0c;制冷与空调设备运行操作证模拟考试题库是根据制冷与空调设备运行操作最新版教材汇编出制冷与空调设备运行操作仿真模拟考试。2024年【制冷…

如何实现Linux双网卡同时连接内网和外网的配置?

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

快速查看字符对应的ASCII码

1、借助gdb查看 打印字符串用双引号括起来打印单个字符用单引号括起来x 表示十六机制d 表示十进制t 表示二进制 2、借助二进制查看软件 第一步&#xff1a;把要查看的字符保存到文本文件中第二步&#xff1a;借助二进制查看工具&#xff08;比如&#xff1a;Hex Editor Neo&am…

Windows 10无法远程桌面连接:原因及解决方案

在信息技术日益发展的今天&#xff0c;远程桌面连接已成为企业日常运维、技术支持乃至个人用户远程办公的必备工具。然而&#xff0c;有时我们可能会遇到Windows 10无法远程桌面连接的问题&#xff0c;这无疑会给我们的工作和生活带来诸多不便。 原因分析 1、远程访问未启用&a…

对比测评3款BI分析工具

前不久&#xff0c;一位准备入职阿里的学弟问我&#xff0c;他要做电商数据分析&#xff0c;电商有庞杂的标签、模型、数据和业务逻辑&#xff0c;菜鸟应该要具备什么样的分析能力啊&#xff1f; 我看了他的岗位职责&#xff0c;主要是负责经营决策支持、专题分析和数据看板搭…

智能决策引擎架构设计

智能决策引擎概述 智能决策引擎系统是在大数据支撑下,根据行业专家经验制定规则策略、以及机器学习/深度学习/AI领域建立的模型运算,对当前的业务进行全面的评估,并给出决策结果的一套系统。 一套商业决策引擎系统动辄百万而且需要不断加钱定制,大多数企业最终仍会走上自研…

纯血鸿蒙APP实战开发——Worker子线程中解压文件

介绍 本示例介绍在Worker 子线程使用ohos.zlib 提供的zlib.decompressfile接口对沙箱目录中的压缩文件进行解压操作&#xff0c;解压成功后将解压路径返回主线程&#xff0c;获取解压文件列表。 效果图预览 使用说明 点击解压按钮&#xff0c;解压test.zip文件&#xff0c;显…

ASP.NET在线二手交易系统的设计与实现

摘 要 随着当今社会信息技术的进步&#xff0c;基于互联网的各种应用日益受到了人们的重视&#xff0c;二手商品的重新利用也逐渐被人们关注&#xff0c;二手交易系统就在这种形势下产生了&#xff0c;它利用网络&#xff0c;改变了人们的购物方式。 本文是基于现代二手交易…

网络安全护网行动:形式主义还是真有价值?

中国每年都投入大量人力物力进行护网行动&#xff0c;如网络攻防演练、黑客技术研究等。有人质疑这些行动是否只是形式主义&#xff0c;缺乏真正的价值。然而&#xff0c;本文将深入解释这些护网行动的原因&#xff0c;并阐明其对信息安全发展的真实价值。 网络信息安全问题的…

Google Ads被暂停的原因,如何防范?

跨境出海业务少不了需要做Google Ads推广业务&#xff1b;其中让投手们闻风丧胆的消息就是帐户被暂停。当 Google 检测到任何违反其政策且可能损害用户在线体验的行为时&#xff0c;就会发生这种情况。那么如何在做广告推广的同时&#xff0c;保证账号不被封禁呢&#xff1f;看…

学前端网络安全这块还不懂?细说CSRF

什么是CSRF&#xff1f; 举个栗子&#xff0c;比如我们需要在某个博客上删除一个文章&#xff0c;攻击者首先在自己的域构造一个页面&#xff0c;使用了一个img标签&#xff0c;其地址指向了删除博客的链接。攻击者诱使目标用户&#xff0c;也就是博客主访问这个页面&#xff…

Linux系统 的持续学习

昨天学习了目录结构、补充命令和配置网络&#xff0c;其中配置网络用了nat方法&#xff0c;今天学习用桥接方法&#xff0c;通配符、正则表达式的一部分内容。 桥接模式 如果重网卡失败&#xff1a; 1.检查配置文件是否正确 2.检查虚拟器编辑器有没有选对&#xff08;网卡类…

二进制搭建 k8s 单 Master 集群架构

一、单机 matser 部署环境 mater节点mater01192.168.80.7kube-apiserver、kube-controller-manager、kube-scheduler、 etcdnode节点node01192.168.80.11kubelet kube-proxy docker &#xff08;容器引擎&#xff09;node02192.168.80.12kubelet kube-proxy docker &#xff0…

【Leetcode每日一题】 动态规划 - 简单多状态 dp 问题 - 删除并获得点数(难度⭐⭐)(76)

1. 题目解析 题目链接&#xff1a;LCR 091. 粉刷房子 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 1. 状态定义 在解决这类问题时&#xff0c;我们首先需要根据题目的具体要求来定义状态。针对房屋粉刷问题&#…