详细全面讲解C++中重载、隐藏、覆盖的区别

文章目录

      • 总结
      • 1、重载
        • 示例代码
        • 特点
        • 1. 模板函数和非模板函数重载
        • 2. 重载示例与调用规则
        • 示例代码
        • 调用规则解释
        • 3. 特殊情况与注意事项
            • 二义性问题
          • 函数特化与重载的交互
      • 2. 函数隐藏(Function Hiding)
        • 概念
        • 示例代码
        • 特点
      • 3. 函数覆盖(重写,Function Overriding)
        • 概念
        • 示例代码
        • 特点

总结

1、重载:在同一个作用域,函数名相同,参数列表不同,与返回值无关
2、隐藏:在基类和派生类之间发生的关系,函数名相同,派生类的函数把基类的函数给隐藏了,只关注函数数名,不管返回值和参数
3、覆盖:覆盖是隐藏的一种特殊情况,派生类和基类的函数,1 函数名相同;2 返回值相同;3 参数列表相同(不包括this指针在内);4 基类函数为虚函数;即为覆盖。

1、重载

  • 函数重载是指在同一个作用域(通常是在同一个类中,也可以是在全局作用域下)内,存在多个同名函数,但它们的参数列表(参数的个数、类型或者顺序)不同。编译器会根据调用时传递的实际参数来决定调用哪个具体的重载函数,函数重载实现了用同一个函数名来执行相似但参数有所不同的操作,提高了代码的可读性和易用性。
    总结来说就是:在同一个作用域,函数名相同,参数列表不同,与返回值无关
示例代码
#include <iostream>// 重载函数,参数个数不同
int add(int num1, int num2) {return num1 + num2;
}int add(int num1, int num2, int num3) {return num1 + num2 + num3;
}// 重载函数,参数类型不同
double add(double num1, double num2) {return num1 + num2;
}int main() {std::cout << add(2, 3) << std::endl;std::cout << add(2, 3, 4) << std::endl;std::cout << add(2.5, 3.5) << std::endl;return 0;
}

在上述代码中,add 函数有多个重载形式,有的重载函数参数个数不同(如两个 int 参数和三个 int 参数的版本),有的重载函数参数类型不同(如 int 参数和 double 参数的版本)。在 main 函数中调用 add 函数时,编译器根据传入的实际参数情况来选择匹配的重载函数进行调用。

特点
  • 作用域相同:重载的函数必须在同一个作用域内定义,比如都在某个类的内部或者都在全局作用域中。
  • 函数名相同:这是重载的关键特征之一,多个函数共享同一个函数名,方便代码的调用和理解,从使用者角度看好像是同一个函数根据不同情况执行不同逻辑。
  • 参数列表有差异:参数个数、类型或者顺序至少有一项不同,而返回类型不同不能作为函数重载的依据(因为仅返回类型不同时,编译器无法仅根据调用情况准确判断该调用哪个函数)。

除了以上常规的重载,还有一些同学认为泛型编程也存在重载,即模板函数和非模板函数的重载,
在C++中,模板函数与非模板函数之间可以存在重载关系,以下是关于它们重载的详细介绍:

1. 模板函数和非模板函数重载
  • 重载机制:重载允许在同一作用域内存在多个同名函数(对于函数重载而言,这些函数的参数列表有所不同,而返回类型不能作为区分重载的唯一依据),编译器会根据调用时实际传入的参数情况来决定调用哪一个具体的函数。模板函数和非模板函数的重载就是利用了这一机制,在合适的场景下,编译器会依据调用参数去选择调用模板函数版本还是非模板函数版本。
2. 重载示例与调用规则
示例代码
#include <iostream>// 非模板函数
int add(int num1, int num2){return num1 + num2;
}// 模板函数
template<typename T>
T add(T num1, T num2){return num1 + num2;
}int main(){int result1 = add(3, 5);  // 调用非模板函数 add(int, int)double result2 = add(3.5, 2.5);  // 调用模板函数 add<double>(double, double)std::cout << "整数相加结果: " << result1 << std::endl;std::cout << "浮点数相加结果: " << result2 << std::endl;return 0;
}
调用规则解释
  • 精确匹配优先:当进行函数调用时,编译器首先会寻找参数类型与函数参数列表能精确匹配的非模板函数。在上述代码的 add(3, 5) 调用中,非模板函数 add(int, int) 的参数类型刚好和传入的两个整数参数完全匹配,所以编译器优先选择调用这个非模板函数,而不会去考虑模板函数版本,即使模板函数也能够通过实例化(将 T 实例化为 int)来处理这两个整数参数。
  • 模板函数实例化匹配:如果没有找到精确匹配的非模板函数,编译器会尝试对模板函数进行实例化,看能否通过实例化后的模板函数来匹配调用参数。例如在 add(3.5, 2.5) 调用中,不存在参数类型为两个 double 的非模板函数 add,此时编译器会查看模板函数,将模板参数 T 实例化为 double,生成 add(double, double) 这样一个实例化后的函数版本,它能很好地匹配传入的两个 double 类型参数,所以就调用这个实例化后的模板函数版本。
3. 特殊情况与注意事项
二义性问题
  • 当模板函数和非模板函数的参数匹配存在模糊情况时,可能会导致编译错误,出现二义性问题。例如:
#include <iostream>// 非模板函数
void func(int num){std::cout << "非模板函数func(int)" << std::endl;
}// 模板函数
template<typename T>
void func(T num) 
{std::cout << "模板函数func(T)" << std::endl;
}int main() 
{func(5);  // 编译错误,存在二义性,不知道该调用模板函数还是非模板函数return 0;
}

在这个例子中,调用 func(5) 时,传入的整数 5 既可以匹配非模板函数 func(int),也可以通过将模板函数的 T 实例化为 int 来匹配模板函数 func(T),编译器无法确定到底该调用哪一个函数,就会报二义性的编译错误。要解决这类问题,可以通过显式指定模板参数(如 func<int>(5) 就会明确调用模板函数版本)或者调整函数的参数类型等方式,使得调用具有明确的匹配对象。这个问题并不是所有的编译器都存在,在VS2022就不存在该问题。

函数特化与重载的交互
  • 函数模板可以进行特化,即针对特定的类型提供专门的模板函数实现。在存在函数特化的情况下,特化版本、模板函数的通用版本以及非模板函数之间的重载关系也需要遵循上述的调用规则。例如:
#include <iostream>// 非模板函数
void printData(int num){std::cout << "非模板函数打印整数: " << num << std::endl;
}// 模板函数
template<typename T>
void printData(T data) 
{std::cout << "模板函数通用版本打印数据: " << data << std::endl;
}// 模板函数特化,针对char类型
template<>
void printData<char>(char data) 
{std::cout << "模板函数特化版本打印字符: " << data << std::endl;
}int main() 
{int num = 10;char ch = 'A';printData(num);  // 调用非模板函数printData(int)printData(ch);  // 调用模板函数特化版本printData<char>(char)return 0;
}

在这里,对于 printData 函数,有非模板函数、模板函数通用版本以及针对 char 类型的特化版本。调用 printData(num) 时,根据精确匹配优先原则,会调用非模板函数 printData(int);而调用 printData(ch) 时,由于存在针对 char 类型的特化版本,会优先调用这个特化版本,而不是模板函数的通用版本,同样体现了编译器在选择调用函数时遵循的优先匹配规则。

2. 函数隐藏(Function Hiding)

概念
  • 函数隐藏同样出现在类的继承关系中,是指在派生类中定义了与基类同名的函数(不管参数列表是否相同),此时派生类的函数会隐藏基类中同名的所有函数(包括重载函数),在派生类的作用域内,如果不使用作用域限定符显式指定,就无法访问到基类中被隐藏的同名函数。
    隐藏:在基类和派生类之间发生的关系,函数名相同,派生类的函数把基类的函数给隐藏了,只关注函数数名,不管返回值和参数
示例代码
#include <iostream>class Base {
public:void func() {std::cout << "Base类的func函数" << std::endl;}void func(int num) {std::cout << "Base类的func(int)函数" << std::endl;}
};class Derived : public Base {
public:void func(double num) {std::cout << "Derived类的func(double)函数" << std::endl;}
};int main() {Derived derived_obj;derived_obj.func(3.0);  // 调用Derived类的func(double)函数// 以下代码编译错误,因为Derived类的func函数隐藏了Base类的func函数,// 不能直接在Derived类作用域内调用Base类的func函数// derived_obj.func();// 使用作用域限定符可以访问Base类的func函数derived_obj.Base::func();return 0;
}

在上述代码中,Derived 类中定义了 func(double num) 函数,它隐藏了 Base 类中的 func 函数和 func(int num) 函数,所以在 Derived 类的作用域内直接调用 func 函数时,编译器会认为是调用 Derived 类自身定义的函数,如果要访问基类中被隐藏的同名函数,需要通过 Base::func() 这样的作用域限定符来明确指定。

特点
  • 存在继承关系:也是基于类的继承场景出现的情况,派生类定义了与基类同名的函数。
  • 同名即隐藏:只要函数名相同就会发生隐藏,与参数列表是否相同无关,而且是隐藏基类中所有同名函数,这一点和重载、覆盖都不同,重载是通过参数差异来区分不同函数,覆盖是严格按照函数签名一致且有虚函数特性来实现的。
  • 作用域相关访问问题:隐藏导致在派生类作用域内,默认情况下无法直接访问基类中被隐藏的同名函数,需要使用作用域限定符来打破这种隐藏效果,才能调用基类的同名函数。

3. 函数覆盖(重写,Function Overriding)

概念
  • 函数覆盖发生在类的继承关系中,是指在派生类中重新定义了基类中的虚函数,并且要求函数签名(函数名、参数列表、返回类型,返回类型如果是指针或引用类型时允许协变)完全一致(除了 const 修饰符,派生类重写函数可以比基类函数多 const 修饰),当通过基类指针或引用调用该函数时,会根据对象的实际类型(是基类对象还是派生类对象)来决定调用基类的函数还是派生类重写后的函数,这是实现多态性的重要机制。

覆盖:覆盖是隐藏的一种特殊情况,派生类和基类的函数,1 函数名相同;2 返回值相同;3 参数列表相同(不包括this指针在内);4 基类函数为虚函数;即为覆盖。
其原理是子类虚函数表的函数地址覆盖了父类虚函数表里函数的指针。
在这里插入图片描述

示例代码
#include <iostream>class Base {
public:virtual void func() {std::cout << "Base类的func函数" << std::endl;}
};class Derived : public Base {
public:void func() override {std::cout << "Derived类的func函数" << std::endl;}
};int main() {Base* ptr = new Derived();ptr->func();  // 调用Derived类重写后的func函数Base base_obj;base_obj.func();  // 调用Base类的func函数Derived derived_obj;derived_obj.func();  // 调用Derived类的func函数return 0;
}

在这个例子中,Derived 类重写了 Base 类中的虚函数 func,通过基类指针 ptr 指向派生类对象时,调用 func 函数会执行 Derived 类中重写后的版本,体现了多态性。而直接使用基类对象或者派生类对象调用 func 函数时,则分别调用各自类中定义的函数。

特点
  • 存在继承关系:必须在派生类和基类之间发生,基类中声明了虚函数,派生类对其进行重写。
  • 函数签名要求严格一致:函数名、参数列表、返回类型(遵循协变规则等情况除外)要相同,比如基类函数是 void func(int num),派生类重写的函数也得是 void func(int num),目的是让编译器能准确识别这是重写关系,以便在多态调用时正确执行相应版本的函数。
  • 虚函数特性:基类中的函数必须是虚函数(通过 virtual 关键字修饰),这样编译器才会在运行时根据对象的实际类型来动态决定调用哪个类的函数,实现多态行为。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DAY15 神经网络的参数和变量

DAY15 神经网络的参数和变量 一、参数和变量 在神经网络中&#xff0c;参数和变量是两个关键概念&#xff0c;它们分别指代不同类型的数据和设置。 参数&#xff08;Parameters&#xff09; 定义&#xff1a;参数是指在训练过程中学习到的模型内部变量&#xff0c;这些变量…

git的rebase和merge的区别?

B分支从A分支拉出 1.git merge 处于A分支执行&#xff0c;git merge B分支:相当于将commit X、commit Y两次提交&#xff0c;作为了新的commit Z提交到了A分支上。能溯源它真正提交的信息。 2.git rebase 处于B分支&#xff0c;执行git rebase A分支&#xff0c;B分支那边复…

2、蓝牙打印机点灯-GPIO输出控制

1、硬件 1.1、看原理图 初始状态位高电平. 需要驱动PA1输出高低电平控制PA1. 1.2、看手册 a、系统架构图 GPIOA在APB2总线上。 b、RCC使能 GPIOA在第2位。 c、GPIO寄存器配置 端口&#xff1a;PA1 模式&#xff1a;通用推挽输出模式 -- 输出0、1即可 速度&#xff1a;5…

使用强化学习训练神经网络玩俄罗斯方块

一、说明 在 2024 年暑假假期期间&#xff0c;Tim学习并应用了Q-Learning &#xff08;一种强化学习形式&#xff09;来训练神经网络玩简化版的俄罗斯方块游戏。在本文中&#xff0c;我将详细介绍我是如何做到这一点的。我希望这对任何有兴趣将强化学习应用于新领域的人有所帮助…

基于springboot的网上商城购物系统

作者&#xff1a;学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等 文末获取“源码数据库万字文档PPT”&#xff0c;支持远程部署调试、运行安装。 目录 项目包含&#xff1a; 开发说明&#xff1a; 系统功能&#xff1a; 项目截图…

API架构风格的深度解析与选择策略:SOAP、REST、GraphQL与RPC

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

【网络协议】开放式最短路径优先协议OSPF详解(四)

前言 在本章的第一部分和第二部分中&#xff0c;我们探讨了OSPF的基本配置&#xff0c;并进一步学习了更多OSPF的概念&#xff0c;例如静态路由的重分发及其度量值。在第三部分中&#xff0c;我们讨论了多区域OSPF。在第四部分中&#xff0c;我们将关注OSPF与多访问网络&#…

上门按摩系统架构与功能分析

一、系统架构 服务端&#xff1a;Java&#xff08;最低JDK1.8&#xff0c;支持JDK11以及JDK17&#xff09;数据库&#xff1a;MySQL数据库&#xff08;标配5.7版本&#xff0c;支持MySQL8&#xff09;ORM框架&#xff1a;Mybatis&#xff08;集成通用tk-mapper&#xff0c;支持…

攻防世界 ics-07

点击之后发现有个项目管理能进&#xff0c;点进去&#xff0c;点击看到源码&#xff0c;如下三段 <?php session_start(); if (!isset($_GET[page])) { show_source(__FILE__); die(); } if (isset($_GET[page]) && $_GET[page] ! index.php) { include(flag.php);…

Spring Boot教程之四十九:Spring Boot – MongoRepository 示例

Spring Boot – MongoRepository 示例 Spring Boot 建立在 Spring 之上&#xff0c;包含 Spring 的所有功能。由于其快速的生产就绪环境&#xff0c;使开发人员能够直接专注于逻辑&#xff0c;而不必费力配置和设置&#xff0c;因此如今它正成为开发人员的最爱。Spring Boot 是…

测试ip端口-telnet开启与使用

前言 开发过程中我们总会要去测试ip通不通&#xff0c;或者ip下某个端口是否可以联通&#xff0c;为此我们可以使用telnet 命令来实现。 一、telnet 开启 可能有些人使用telnet报错&#xff0c;不是内部命令&#xff0c;可以如下开启&#xff1a; 1、打开控制面板&#xff…

SpringBoot3动态切换数据源

背景 随着公司业务战略的发展&#xff0c;相关的软件服务也逐步的向多元化转变&#xff0c;之前是单纯的拿项目&#xff0c;赚人工钱&#xff0c;现在开始向产品化\服务化转变。最近雷袭又接到一项新的挑战&#xff1a;了解SAAS模型&#xff0c;考虑怎么将公司的产品转换成多租…

爬虫学习记录

1.概念 通过编写程序,模拟浏览器上网,然后让其去互联网上抓取数据的过程 通用爬虫:抓取的是一整张页面数据聚焦爬虫:抓取的是页面中的特定局部内容增量式爬虫:监测网站中数据更新的情况,只会抓取网站中最新更新出来的数据 robots.txt协议: 君子协议,网站后面添加robotx.txt…

通过 route 或 ip route 管理Linux主机路由

目录 一&#xff1a;route 使用说明1、查看路由信息2、删除指定路由3、增加指定路由 二&#xff1a;ip route 使用说明1、查看主机路由2、新增主机路由3、删除主机路由 通过route 或者ip route修改Linux主机路由后属于临时生效&#xff0c;系统重启后就恢复默认值了&#xff0c…

el-table表格合并某一列

需求&#xff1a;按照下图完成单元格合并&#xff0c;数据展示 可以看到科室列是需要合并的 并加背景色展示&#xff1b;具体代码如下&#xff1a; <el-tableref"tableA":data"tableDataList":header-cell-style"{ backgroundColor: #f2dcdb, col…

CSS Grid 布局全攻略:从基础到进阶

文章目录 一.Grid 是什么二.示例代码1. 基础使用 - 固定宽高2.百分百宽高3.重复设置-repeat4.单位-fr5.自适应6.间距定义其他 一.Grid 是什么 CSS 中 Grid 是一种强大的布局方式&#xff0c;它可以同时处理行和列 Grid 和Flex有一些类似&#xff0c;都是由父元素包裹子元素使用…

数据结构:包装类和泛型

目录 一、包装类 1、基本数据类型和对应的包装类 2、装箱和拆箱 3、自动装箱和自动拆箱 二、泛型 1、什么是泛型 2、泛型语法 3、泛型类 4、擦除机制 5、泛型的上界 6、泛型方法 三、通配符 1、什么是通配符 2、通配符上界 3、通配符下界 &#x1f4da…

备考蓝桥杯:顺序表相关算法题

目录 询问学号 寄包柜 移动0 颜色分类 合并两个有序数组 物品移动 询问学号 我们的思路&#xff1a;创建一个顺序表存储从1开始依次存放进入教室的学生学号&#xff0c;然后查询 #include <iostream> #include <vector> using namespace std; const int N 2…

Python入门教程 —— 网络编程

1.网络通信概念 简单来说,网络是用物理链路将各个孤立的工作站或主机相连在一起,组成数据链路,从而达到资源共享和通信的目的。 使用网络的目的,就是为了联通多方然后进行通信,即把数据从一方传递给另外一方。 前面的学习编写的程序都是单机的,即不能和其他电脑上的程…

C#异步多线程——ThreadPool线程池

C#实现异步多线程的方式有多种&#xff0c;以下总结的是ThreadPool的用法。 线程池的特点 线程池受CLR管理&#xff0c;线程的生命周期&#xff0c;任务调度等细节都不需要我们操心了&#xff0c;我们只需要专注于任务实现&#xff0c;使用ThreadPool提供的静态方法把我们的任…