【C++】深入理解C++中的函数与运算符重载
文章目录
- 前言
- 一、什么是重载?
- 1.1 函数重载
- 1.1.1 函数重载的规则
- 1.1.2 示例:函数重载
- 1.2 运算符重载
- 1.2.1 运算符重载的规则
- 1.2.2 示例:运算符重载
- 1.2.3 运算符重载的注意事项
- 二、重载的注意事项
- 2.1 重载的二义性
- 2.2 默认参数和重载
- 2.3 运算符重载的成员函数与非成员函数
- 2.4 重载与继承
- 虚函数与重载
- 三、重载的一些问题
- 3.1 函数重载如何确定应该调用哪个函数?在编译期间还是运行期间?
- 3.2 C语言支持函数重载吗?为什么?
- 3.3 C++底层怎么支持函数重载的?
- 3.4 为什么仅仅返回值不同不能形成重载?
- 3.5 extern "C" 的作用?
- 3.6 如何在C语言中写出函数重载的效果?
前言
C++中 重载 主要体现在两种形式:函数重载和运算符重载。下面会分别进行介绍
一、什么是重载?
重载(Overloading) 指在同一个作用域内,允许使用相同名称的函数或运算符,但参数列表或类型不同。
参数列表指的是 参数类型、参数个数、参数类型次序不同;
1.1 函数重载
函数重载是指在同一个作用域中,允许存在多个函数名相同但参数列表不同的函数。编译器会根据函数调用时传入的参数个数、类型等信息来区分并调用相应的函数。
1.1.1 函数重载的规则
- 函数名相同:重载的函数必须具有相同的函数名。
- 参数个数或类型不同:重载的函数必须有不同的参数个数、类型或顺序。参数的类型、数量或顺序任何一种发生变化,都会被视作不同的函数重载。
- 返回类型无关:返回类型不能作为区分重载函数的依据,重载的函数必须通过不同的参数列表来区分。
1.1.2 示例:函数重载
#include <iostream>
using namespace std;class Printer {
public:// 打印整数void print(int i) {cout << "打印整数: " << i << endl;}// 打印浮点数void print(double d) {cout << "打印浮点数: " << d << endl;}// 打印字符串void print(const string& s) {cout << "打印字符串: " << s << endl;}
};int main() {Printer p;p.print(10); // 打印整数p.print(3.14); // 打印浮点数p.print("Hello!"); // 打印字符串return 0;
}
输出:
打印整数: 10
打印浮点数: 3.14
打印字符串: Hello!
对于上面的代码,print
函数通过参数类型的不同实现了重载。编译器根据传入的参数类型来选择对应的函数版本。
1.2 运算符重载
运算符重载是指为自定义类型(如类)定义运算符的行为,使得这些类型可以像内置类型一样使用常见的运算符(例如 +
、-
、*
等)。
1.2.1 运算符重载的规则
- 运算符重载必须是成员函数或者非成员函数。
- 运算符重载不会改变运算符的优先级和结合性。
- 可以重载大多数的运算符,但有一些运算符不能被重载(如
::
、sizeof
、typeid
等)。 - 运算符重载必须遵循特定的语法规则,通常通过类成员函数或全局函数实现。
1.2.2 示例:运算符重载
#include <iostream>
using namespace std;class Complex {
private:double real;double imag;public:// 构造函数Complex(double r, double i) : real(r), imag(i) {}// 重载+运算符Complex operator+(const Complex& other) {return Complex(real + other.real, imag + other.imag);}// 输出复数void print() const {cout << real << " + " << imag << "i" << endl;}
};int main() {Complex c1(3.0, 4.0);Complex c2(1.0, 2.0);Complex c3 = c1 + c2; // 使用重载的+运算符c3.print(); // 输出: 4.0 + 6.0ireturn 0;
}
输出:
4.0 + 6.0i
对于上面的代码,定义了一个Complex
类表示复数,并重载 +
运算符使可以通过 +
来相加两个复数对象。重载后的 +
运算符实现了两个复数的实部和虚部相加。
1.2.3 运算符重载的注意事项
- 运算符重载只是改变了运算符的行为,而不改变运算符的基本语义。
- 并非所有运算符都可以被重载,C++标准规定了一些不能被重载的运算符,比如
::
(作用域解析运算符)、sizeof
(大小运算符)、typeid
(类型信息运算符)等。 - 重载运算符时需要特别注意运算符的结合性和优先级,这些是固定的,无法改变的。
二、重载的注意事项
在使用重载时,有一些注意事项需要特别留意:
2.1 重载的二义性
有时重载可能会导致编译器无法确定使用哪个函数或运算符。这种情况通常称为二义性。例如,假设有两个重载函数,它们的参数类型很相似,编译器可能无法决定调用哪一个函数。
void func(int x);
void func(double x);
如果此时传入一个 float
类型的参数,编译器可能不知道该调用 int
版本还是 double
版本的函数。这种情况下可以通过强制类型转换来解决。
2.2 默认参数和重载
默认参数不能作为区分重载的依据。如果两个函数的参数只有默认值不同,那么它们仍然会被认为是同一个函数,而不能构成重载。
void func(int x = 5);
void func(int x = 10); // 错误:参数默认值不同,不构成重载
2.3 运算符重载的成员函数与非成员函数
-
运算符可以通过成员函数或者非成员函数来重载。成员函数可以访问类的私有数据,而非成员函数(通常是全局函数)需要通过传参来访问类的数据。
-
通常来说,如果运算符重载涉及到修改类内部状态,则使用成员函数;如果只是执行操作,则可以使用非成员函数。
2.4 重载与继承
重载的函数或运算符如果在基类和派生类中都有定义,派生类可以覆盖基类的重载版本。
比如对于下面的代码:
#include <iostream>
using namespace std;// 基类
class Shape {
public:// 基类中的重载函数virtual void draw(int radius) {cout << "Drawing a circle with radius: " << radius << endl;}virtual void draw(double side) {cout << "Drawing a square with side: " << side << endl;}// 虚拟函数,允许派生类重写virtual ~Shape() {}
};// 派生类
class Circle : public Shape {
public:// 重写基类的重载函数void draw(int radius) override {cout << "Circle class: Drawing a circle with radius: " << radius << endl;}
};class Square : public Shape {
public:// 重写基类的重载函数void draw(double side) override {cout << "Square class: Drawing a square with side: " << side << endl;}
};int main() {Shape* shape1 = new Circle();Shape* shape2 = new Square();// 使用基类指针调用派生类的覆盖版本shape1->draw(5); // 调用 Circle 类中的 draw(int)shape2->draw(3.5); // 调用 Square 类中的 draw(double)delete shape1;delete shape2;return 0;
}
会有如下的输出结果:
Circle class: Drawing a circle with radius: 5
Square class: Drawing a square with side: 3.5
虚函数与重载
如果重载的函数是虚函数,那么编译时选择哪个重载版本并不会受到虚函数机制的影响。虚函数机制(多态性)是基于运行时的动态绑定来确定调用哪个函数,而重载是根据函数签名在编译时进行解析的。
三、重载的一些问题
3.1 函数重载如何确定应该调用哪个函数?在编译期间还是运行期间?
- A:函数重载的选择是在 编译期间 确定。具体的说,编译器根据函数的参数列表来决定应该调用哪个重载版本。这个过程被称为静态绑定或早期绑定。
3.2 C语言支持函数重载吗?为什么?
- C语言不支持,C语言编译器种没有名字修饰(Name Mangling)机制,C++ 编译器可能会将 func(int) 和 func(double) 分别变成 func_int 和 func_double,从而使它们在符号表中具有不同的名字。
- 而C 的符号表通常只有函数的名称,而没有参数类型的附加信息。
3.3 C++底层怎么支持函数重载的?
- 同上一问,C++编译器会将函数的参数信息也加载到名字中;在符号表中的名字就不同了。
3.4 为什么仅仅返回值不同不能形成重载?
- 同上面的问题,编译器不会将函数的返回值加载到名字中,仅仅返回值不同,符号表上依然是相同的
3.5 extern “C” 的作用?
- extern “C” 是用于 C++ 中的声明,作用是 告诉编译器以 C 语言的方式来处理函数或变量的链接方式,即 禁用 C++ 的名字修饰(name mangling)
使用 extern "C"
修饰函数或变量,可以确保这些符号按照 C 的规则进行链接,使得 C++ 编译器生成与 C 编译器兼容的符号,从而允许 C++ 和 C 代码混合编程,进行跨语言调用。
3.6 如何在C语言中写出函数重载的效果?
C语言中可以通过利用特性实现函数重载的效果,比如 宏 与 变参函数
- 宏
可以利用宏根据不同的参数类型调用不同的函数:
#include <stdio.h>#define PRINT(x) _Generic((x), \int: print_int, \double: print_double)(x)void print_int(int x) {printf("Integer: %d\n", x);
}void print_double(double x) {printf("Double: %f\n", x);
}int main() {PRINT(10); // 调用 print_intPRINT(3.14); // 调用 print_doublereturn 0;
}
- 变参函数
C 语言允许定义变参函数,即函数的参数数量和类型可以在调用时动态变化。例如:
#include <stdio.h>
#include <stdarg.h>void print(const char *format, ...) {va_list args;va_start(args, format);if (strcmp(format, "int") == 0) {int x = va_arg(args, int);printf("Integer: %d\n", x);} else if (strcmp(format, "double") == 0) {double x = va_arg(args, double);printf("Double: %f\n", x);}va_end(args);
}int main() {print("int", 10);print("double", 3.14);return 0;
}
相关文章:
【C++】深入理解C++中的函数与运算符重载
文章目录 前言一、什么是重载?1.1 函数重载1.1.1 函数重载的规则1.1.2 示例:函数重载 1.2 运算符重载1.2.1 运算符重载的规则1.2.2 示例:运算符重载 1.2.3 运算符重载的注意事项 二、重载的注意事项2.1 重载的二义性2.2 默认参数和重载2.3 运…...

【读代码】BAGEL:统一多模态理解与生成的模型
一、项目概览 1.1 核心定位 BAGEL是字节跳动推出的开源多模态基础模型,具有70亿激活参数(140亿总参数)。该模型在统一架构下实现了三大核心能力: 多模态理解:在MME、MMBench等9大评测基准中超越Qwen2.5-VL等主流模型文本生成图像:生成质量媲美SD3等专业生成模型智能图像…...

隧道自动化监测解决方案
行业现状 隧道作为一种重要的交通运输通道,不管是缓解交通压力,还是让路网结构更趋于完善,它都有着不可估量的作用。隧道在运营过程中,由于受到材料退化、地震、人为因素等影响会发生隧道主体结构的损坏和劣化。若不及时检修和维护…...
如何通过EventChannel实现Flutter与原生平台的双向通信?
在Flutter开发中,EventChannel是处理单向数据流的核心组件,尤其适用于原生平台(Android/iOS)主动向Flutter端推送实时数据的场景,例如传感器数据、后台任务通知等。虽然EventChannel本身以原生到Flutter的单向通信为主,但结合特定设计模式,仍可实现双向交互。本文将详细…...

游戏引擎学习第307天:排序组可视化
简短谈谈直播编程的一些好处。 上次结束后,很多人都指出代码中存在一个拼写错误,因此这次我们一开始就知道有一个 bug 等待修复,省去了调试寻找错误的时间。 今天的任务就是修复这个已知 bug,然后继续排查其他潜在的问题。如果短…...

java接口自动化初识
简介 了解什么是接口和为什么要做接口测试。并且知道接口自动化测试应该学习哪些技术以及接口自动化测试的落地过程。 一、什么是接口 在这里我举了一个比较生活化的例子,比如我们有一台笔记本,在笔记本的两端有很多插口。例如:USB插口。那…...
工作流引擎-01-Activiti 是领先的轻量级、以 Java 为中心的开源 BPMN 引擎,支持现实世界的流程自动化需求
前言 大家好,我是老马。 最近想设计一款审批系统,于是了解一下关于流程引擎的知识。 下面是一些的流程引擎相关资料。 工作流引擎系列 工作流引擎-00-流程引擎概览 工作流引擎-01-Activiti 是领先的轻量级、以 Java 为中心的开源 BPMN 引擎&#x…...
时序数据库IoTDB的分片与负载均衡策略深入解析
一、引言 随着数据库服务的业务负载增加,扩展服务资源成为必然需求。扩展方式主要分为纵向扩展和横向扩展。纵向扩展通过增加单台机器的能力(如内存、硬盘、处理器)来实现,但受限于单台机器的硬件能力。而横向扩展则通过增加更多…...

NVM安装使用及问题解决
目录 一、前言 二、NVM安装 三、配置下载源 四、nvm使用 五、安装nvm list available没有的版本 六、问题解决 一、前言 如果你开发 Node.js 项目,可能会遇到这些问题: ①新项目需要 Node.js 18,但老项目只能用 Node.js 14,…...

C++学习之STL学习:string类使用
在之前的学习中,我们初步了解到了STL的概念,接下来我们将深入学习STL中的string类的使用,后续还会结合他们的功能进行模拟实验 目录 为什么要学习string类? 标准库中的string类 string类(了解) auto和范围…...
基于 STC89C52 的养殖场智能温控系统设计与实现
摘要 本文提出一种基于 STC89C52 单片机的养殖场环境温度智能控制系统,通过集成高精度温度传感器、智能执行机构及人机交互模块,实现对养殖环境的实时监测与自动调控。系统具备温度阈值设定、超限报警及多模式控制功能,可有效提升养殖环境稳定性,降低能耗与人工成本。 一…...
redis哨兵服务
配置主机Host67为master服务器配置主机host68为 slave服务器配置主机host69运行哨兵服务测试配置 IP地址主机名192.168.10.167redis167192.168.10.168redis168192.168.10.169redis169 步骤一:配置主机Host67为master服务器 [rootredis169 ~]# vim /etc/redis.c…...

5月24日day35打卡
模型可视化与推理 知识点回顾: 三种不同的模型可视化方法:推荐torchinfo打印summary权重分布可视化进度条功能:手动和自动写法,让打印结果更加美观推理的写法:评估模式 作业:调整模型定义时的超参数&#x…...
嵌入式<style>设计模式
每天分享一个web前端开发技巧。 今天分享的主题是,如何提升前端代码的内聚性。我们在写<style></style>的时候,往往把大量无关联的样式写在同一个<style>下,而且离相关的html元素很远,这样导致每次想修改某个元…...
Kotlin 中该如何安全地处理可空类型?
在 Kotlin 中,可空类型(如 String?)是语言设计的核心特性之一,旨在从编译时避免 NullPointerException(NPE)。 1 核心处理方式 1.1 安全调用操作符(?.) 直接调用可空对象的方法…...
基于大模型预测的视神经脊髓炎技术方案
目录 一、术前评估与预测1. 数据采集与预处理2. 大模型构建与训练3. 术前风险评估与预测二、术中监测与决策支持1. 实时数据采集与传输2. 术中决策支持系统三、术后管理与康复1. 术后早期预警与监测2. 康复效果预测与个性化方案四、并发症风险预测与防控1. 并发症风险预测模型2…...
使用防火墙禁止程序联网(这里禁止vscode)
everything搜一下Code.exe的安装路径:D:\downloadApp1\vscode\Microsoft VS Code\Code.exe 方法:使用系统防火墙(推荐) Windows 通过防火墙阻止 VS Code: 打开 Windows Defender 防火墙(控制面板 > 系统…...

Linux(7)——进程(概念篇)
目录 一、基本概念 二、描述进程——PCB 1.task_struct——PCB的一种 2.task_struct的内容分类 三、查看进程 1.通过系统目录查看 2.通过ps命令查看 四、通过系统调用获取进程的PID和PPID 五、通过系统调用创建进程 1.fork函数创建子进程 2.使用if来引出问题 六、L…...

前端流行框架Vue3教程:24.动态组件
24.动态组件 有些场景会需要在两个组件间来回切换,比如 Tab 界面 我们准备好A B两个组件ComponentA ComponentA App.vue代码如下: <script> import ComponentA from "./components/ComponentA.vue" import ComponentB from "./…...

Unity3D仿星露谷物语开发48之显示树桩效果
1、目标 砍完橡树之后会露出树桩,然后树桩可以用斧头收割,并将创建一个新的砍树桩的粒子效果。 这里有:一种作物收获后创造另一种作物的逻辑。 2、分析 在SO_CropDetailsList中,Harvested Transform Item Code可以指定收获后生…...

[Datagear] 实现按月颗粒度选择日期的方案
在使用 Datagear 构建数据分析报表时,常常会遇到一个问题:如果数据的目标颗粒度是“月”,默认的日期控件却是精确到“日”的,这在用户交互和数据处理层面会带来不必要的复杂度。本文将分享两种解决方案,帮助你更好地控制日期控件的颗粒度,实现以月为单位的日期筛选功能。…...

漏洞检测与渗透检验在功能及范围上究竟有何显著差异?
漏洞检测与渗透检验是确保系统安全的重要途径,这两种方法各具特色和功效,它们在功能上有着显著的差异。 目的不同 漏洞扫描的主要任务是揭示系统内已知的安全漏洞和隐患,这就像是对系统进行一次全面的健康检查,看是否有已知的疾…...

DB-GPT扩展自定义Agent配置说明
简介 文章主要介绍了如何扩展一个自定义Agent,这里是用官方提供的总结摘要的Agent做了个示例,先给大家看下显示效果 代码目录 博主将代码放在core目录了,后续经过对源码的解读感觉放在dbgpt_serve.agent.agents.expand目录下可能更合适&…...
基于SamOutV8的序列生成模型实现与分析
项目概述 本项目实现了基于SamOutV8架构的序列生成模型,核心组件包括MaxStateSuper、FeedForward和DecoderLayer等模块。通过结合自注意力机制与状态编码策略,该模型在处理长序列时表现出良好的性能。 核心组件解析 1. MaxStateSuper(状态编…...

家政维修平台实战09:推送数据到多维表格
目录 1 API调试2 创建云函数3 前端调用整体效果总结 上一篇我们搭建了服务分类的后台功能,对于分类的图标通过集成TOS拿到了可以公开访问的地址,本篇我们将写入的数据推送至多维表格中。 1 API调试 要想推送多维表格的数据,首先要利用官方的…...

前端框架token相关bug,前后端本地联调
今天我搭建框架的时候,我想请求我自己的本地!然后我自己想链接我自己的本地后端,我之前用的前端项目,都是链别人的后端,基本上很少情况会链接自己的后端!所以我当时想的是,我前后端接口一样&…...
PyQt学习系列05-图形渲染与OpenGL集成
PyQt学习系列笔记(Python Qt框架) 第五课:PyQt的图形渲染与OpenGL集成 一、图形渲染概述 1.1 为什么需要图形渲染? PyQt默认基于2D绘图(QPainter),但某些场景需要高性能3D图形或复杂视觉效果…...

卷积神经网络(CNN)可视化技术详解:从特征学到演化分析
在深度学习领域,卷积神经网络(CNN)常被称为“黑箱”,其内部特征提取过程难以直接观测。而 可视化技术 是打开这一“黑箱”的关键工具,通过可视化可直观了解网络各层学到了什么、训练过程中如何演化,以及模型…...
第十天的尝试
目录 一、每日一言 二、练习题 三、效果展示 四、下次题目 五、总结 一、每日一言 哈哈,十天缺了两天,我写的文章现在质量不高,所以我可能考虑,应该一星期或者三四天出点高质量的文章,同时很开心大家能够学到知识&a…...
WHAT - 兆比特每秒 vs 兆字节每秒
文章目录 Mbps 解释Mbps 和 MB/s(兆字节每秒)换算总结网络场景1. 在路由器设置中的 Mbps2. 在游戏下载时的 Mbps / MB/s总结 Mbps 解释 首先,Mbps 是一个常见的网络带宽单位,意思是: Megabits per second(…...