C++函数重载及其背后的原理

news2025/7/23 6:47:34

写在前面

先说说我的状态吧,五一假期五天假,这些天都在玩,很少学习,我不是后悔,也没必要,本来假期就是为了让自己放松.我唯一要反思看到别人在学,我心里也想学但是却做不到,这是我的缺点,后面我会克服的.尽量快点和大家分享知识.今天和大家分享的是C++关于函数重载的关系,我们不仅仅需要学会重载的使用,更要了解C++为什么支持函数重载.

函数重载

我们已经对函数很是熟悉了,但是函数重载又是什么意思呢?我们先来用一个具体的场景来分享.

一天,张三的老板要你写一个两位数相加的函数,张三心想这不很简单吗?手指一动,结果就出来了,挺简单的嘛.

int add(int x, int y)
{
	return x + y;
}

老板看到张三的代码立马生气了,你是怎么想的,要是我想12,10.9相加呢?你这个就只能两个整型相加,回去修改!!!张三听到老板的话不由得反驳道:这怎么改,总不能再写一个add1,add2…老板听到了张三的嘟囔,生气道,你没有学过函数重载吗?看看下面的代码,回去好好学习学习,基础都不扎实.张三看到代码,不由大吃一惊,C++还可以这么写?

int add(int x, int y)
{
	return x + y;
}

double add(double x, int y)
{
	return x + y;
}

double add(int x, double y)
{
	return x + y;
}

我们可不希望张三这种事发生在我们身上,先来看看函数重载的定义

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题 .

可能大家不喜欢看定义,我这里给一个总结.函数重载要满足下面的要求.

  • 函数名相同
  • 参数的类型,个数,顺序有一个不同就可以了
  • 返回类型不做要求

函数重载的原理

一般情况下,我们知道了函数重载以及会应用就可以了,但是对于我们来说需要知道他们的原理.为什么C语言不支持重载,C++支持重载?这些都是问题.

为何C++可以支持重载

我们先用C++的编译器简单的看看如何执行程序,下面是我在Linux环境下使用g++来完成的,大家要是不太懂,可以先不管,直接理解C++的原理.我们先来看看现象,发现C++可以精准的找到需要匹配的函数,这是我们所疑惑的.

// test.h
#pragma once 
#include <iostream>
#include <stdio.h>

using std::cout;
void func(int a, double b);
void func(double a, int b);
//test.cpp

#include "test.h"
//写两个函数   函数形成重载
void func(int a, double b)
{
	printf("%d %lf", a, b);
}

void func(double a, int b)
{
	printf("%lf %d", a, b);
}


//Mian.cpp

#include "test.h"
int main()
{
	func(10, 2.20);
	return 0;
}

image-20230119133324390

image-20220505152053177

image-20220505152115035

程序的编译链接

关于这一点,我们先简单的说说,之前我们详细的谈过.一个文件变成一个可执行程序需要经过下面4个步骤.

  1. 预处理 宏替换 头文件展开 注释替换 main.cpp -> main.i test.cpp -> test.i
  2. 编译 检查语法 ,代码变换成汇编语言 main.i -> main.s test.i -> test.s
  3. 汇编 汇编语言变成二进制语言,各个文件变成目标文件 main.s -> main.o test.s -> test.o
  4. 链接 多个目标文件+链接库发生链接

这里我们需要重点谈谈链接部分,这是我们今天最重要的一部分

链接就仅仅只是目标文件的合并吗?不是的,它要完成的任务很多,其中最重要的就是找到函数的地址,链接对应上,合并到一起

当我们进行过头文件的展开后,Main.cpp中有func函数的声明和调用.每一个源文件在编译和汇编的过程中产生一个符号表,这个符号表记录了函数的定义以及相应的映射.这是很重要的.符号表里面包含了函数名和函数的地址.每一个目标文件(.o)都包含一个符号表和一系列指令,我们看看入和完成函数链接.

image-20220505161543359

现在到mian.o的指令这里了,前面的一些列指令都正常经行,直到它遇到了func这个函数,要是看过C语言的汇编语言的朋友们可能对下面的比较熟悉.

到了func这里,编译器开始call (func: ?),此时编译器不知道func的地址,但是前面头文件的的展开中func函数已经声明了,这是一个函数,所以编译器知道了func是一个函数.就先给它一个暂时无效的地址.当程序进行链接时,编译器一看它是一个无效地址,会拿函数名和其他的.o文件里面的符号表去碰,碰到了就填上,找不到就会报连接错误.

C语言为何不支持重载

C语言中,当我们拿函数名去碰的时候,符号表里面存在多个相同的函数名,编译器就不会识别该用哪个.更何况存在相同函数名的.c文件都不可能编译过.gcc对函数名都不会做任何处理,这也是C语言不支持函数重载的原因.

image-20220505191719639

C++为何可以支持函数重载

这里我们就可以得到了结果,既然在链接的时候无效的函数会拿函数名去其他目标文件的符号表里面去碰,那么只要我们看看重载的函数名相不相同就可以了,大家可能会有些疑惑,重载的函数名不是相同的吗?是的,但是C++编译器会做一定的处理.这里每个编译器都有自己的函数名修饰规则 这就是C++ 支持重载的原理.

这就是C++可以支持重载的原因,g++的函数修饰后变成【_Z+函数名长度+函数名+类型首字母1+类型首字母2…】,这也是我们只对参数列表做了要求,对返回值不做要求的原因.

image-20220505190609679

C++和C语言相互调用

我们都知道C++支持C语言的大部分语法,C++和C语言可以相互调用吗?实际上是可以的,在一个大型程序中,有的部门可能使用的是C写的的函数,有的部门可能用的C++,要是他们不能相互使用那就打脸了.

创建静态库

我们可以把自己写的代码编译成一个静态库或者动态库,这里我以静态库举例,看看如何在VS中中创建一个静态库.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZjYikmVJ-1678159080480)(https://qkj0302.oss-cn-beijing.aliyuncs.com/img2/screenshot202205051943345.png)]

C++调用C

我们已经有了一个C语言的静态库,现在有一个C++的项目需要使用这个静态库,我们该如何使用呢?需要分为下面几个步骤

下面这两张图片都是修改环境的设置,我使用的是VS2013,其他的大概应该差不多,大家依次来修改就可以了.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CMqbhtHT-1678159080481)(https://qkj0302.oss-cn-beijing.aliyuncs.com/img2/screenshot202205052017609.png)]

image-20220505201829485

到这里我们就可以调用C语言的静态库了,让我们来看看结果吧.

#include "../../Heap/Heap/heap.h"  //相对路径

int main()
{
	MyHeap myHeap;
	InitMyHeap(&myHeap);
	HeapPush(&myHeap, 1);
	HeapPush(&myHeap, 2);
	HeapPush(&myHeap, 3);
	Display(&myHeap);
	return 0;
}

image-20220505202112711

这为什么报错?我们不是已经设置好了静态库了吗?实际上这种错误是很容易分析出来的,当C++去调用C语言的函数时,C++会自动修改函数名,当时C语言函数名是没有被修改的,所以函数在寻找的时候就不会碰到一起,链接就会出错.

extern “C”

既然编译器不能自动识别C语言的函数名,我们告诉编译器一下不就可以了吗.extern “C” 就是这种作用.

有时候在C++工程中可能需要将某些函数按照 C 的风格来编译在函数前加 extern “C”,意思是告诉编译器,将该函数按照 C 语言规则来编译。比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree两个接口来使用,但如果是C项目就没办法使用,那么他就使用extern “C”来解决

extern "C"   // 告知这是C语言的函数声明
{
	#include "../../Heap/Heap/heap.h"
}

int main()
{
	MyHeap myHeap;
	InitMyHeap(&myHeap);
	HeapPush(&myHeap, 1);
	HeapPush(&myHeap, 2);
	HeapPush(&myHeap, 3);
	Display(&myHeap);
	return 0;
}

image-20220505202831963

extern “C” 原理

我们需要来看看extern “C” 的原理,使用了extern “C” 后,在C++在进行编译的时候函数名字就依据C语言的方法来修改了,不在变成C++ 的规则.extern "C"可以单独修饰函数,也可以修饰一系列函数,使用代码块.

// test.h
#pragma once 
#include <iostream>
#include <stdio.h>

extern "C" void func(int a, double b);

//test.cpp

#include "test.h"
//写两个函数   函数形成重载
void func(int a, double b)
{
	printf("%d %lf", a, b);
}

//Mian.cpp

#include "test.h"
int main()
{
	func(10, 2.20);
	return 0;
}

image-20220505211314864

C语言调用C++

那么C语言可以调用C++ 的吗?可以了,不过也需要一些段来完成.如何让C语言去识别C++的规则呢?这是我们需要考虑的.我们已经把库改成的了C++的静态库了.

#include "../../Heap/Heap/heap.h"


int main()
{
	MyHeap myHeap;
	InitMyHeap(&myHeap);
	HeapPush(&myHeap, 1);
	HeapPush(&myHeap, 2);
	HeapPush(&myHeap, 3);
	Display(&myHeap);
	return 0;
}

image-20220505204409151

我们无法让C语言的编译器去识别C++ 的函数的命名,那么我们是不是可以在C++中函数编译的时候就完成函数名依照C语言来说.这就很简单了.

image-20220505205316985

但是即使是这样,C语言仍旧会报错,原因在于在头文件展开的时候,C语言根本不识别extern “C”,所以我们就需要条件编译了.使用条件编译来修改的静态库的方法如下,需要再次编译.

//方法一
#ifdef __cplusplus    // C++独有的宏
	#define EXTERNC extern "C"
#else 
	#define EXTERNC
#endif


EXTERNC extern void InitMyHeap(MyHeap * pHeap);

EXTERNC extern void HeapPush(MyHeap* pHeap, HPDataType x);
EXTERNC extern bool IsFull(MyHeap* pHeap);
EXTERNC extern bool IsEmpty(MyHeap* pHeap);
EXTERNC extern int HeapSize(MyHeap* pHeap);
EXTERNC extern void adjustDown(MyHeap* pHeap);
EXTERNC extern void adjustUp(MyHeap* pHeap);
EXTERNC extern void Display(MyHeap* pHeap);
EXTERNC extern HPDataType HeapTop(MyHeap* pHeap);
EXTERNC extern void HeapPop(MyHeap* pHeap);

//方法 二
#ifdef __cplusplus
extern "C"
{
#endif

	extern void InitMyHeap(MyHeap * pHeap);

	extern void HeapPush(MyHeap* pHeap, HPDataType x);
	extern bool IsFull(MyHeap* pHeap);
	extern bool IsEmpty(MyHeap* pHeap);
	extern int HeapSize(MyHeap* pHeap);
	extern void adjustDown(MyHeap* pHeap);
	extern void adjustUp(MyHeap* pHeap);
	extern void Display(MyHeap* pHeap);
	extern HPDataType HeapTop(MyHeap* pHeap);
	extern void HeapPop(MyHeap* pHeap);
#ifdef __cplusplus
}
#endif

image-20220505210448002

注意,这里有一点需要注意的,当我们C语言调用C++静态库的时候,最起码我们实际需要的的那部分代码在extern "C"修饰的函数中不能发生重载.

C++ 注意事项

这个注意事项主要是依据extern "C"来谈的,有些比较偏僻的内容需要关注下.

## extern "C"修饰的函数和一个函数完全一样

在C++中,在extern "C"修饰的函数模块外面存在了一个完全一摸一样的的函数,这个编译器不会给通过的,这一点我们注意就可以了.按照我想的是应该可以过去的,可惜不行.

#ifdef __cplusplus
extern "C"
{
#endif 

	void func(int a, int b)
	{
		printf("C : %d %d\n", a, b);
	}

#ifdef __cplusplus
}
#endif 
//完全一样
void func(int a, int b)
{
	printf("C : %d %d\n", a, b);
}

image-20220505213801895

extern "C"修饰的函数和一个函数构成重载

在extern "C"修饰的函数模块外面一个函数构成重载这种编译器可以通过的,但是extern "C"修饰的命名方法仍旧还是按照C语言的方式,构成重载的是C++的方式.

#include <iostream>
using namespace std;

#ifdef __cplusplus
extern "C"
{
#endif 

	void func(int a, int b)
	{
		printf("C : %d %d\n", a, b);
	}

#ifdef __cplusplus
}
#endif 

void func(double a, int b)
{
	printf("C++: %lf %d\n", a, b);
}

int main()
{
	func(1, 2);
	func(1.11, 2);
	return 0;
}

image-20220505214015081

image-20220505214033515

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

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

相关文章

运维提质增效,有哪些办法可以做

凡是代码&#xff0c;难免有 bug。 开发者们的日常&#xff0c;除了用一行行代码搭产品外&#xff0c;便是找出代码里的虫&#xff0c;俗称 debug。 随着移动互联网的快速发展&#xff0c;App 已经成为日常生活中不可或缺的一部分。但是在开发者/运维人员的眼里简直就是痛苦的…

使用R语言包clusterProfiler做KEGG富集分析时出现的错误及解决方法

使用enrichKEGG做通路富集分析时&#xff0c;一直报错&#xff1a;显示No gene can be mapped....k <- enrichKEGG(gene gene, organism "hsa", pvalueCutoff 1, qvalueCutoff 1)但是之前用同样的基因做分析是能够成功地富集到通路&#xff0c;即便是网上的数据…

Appium+Python连接真机、跳过登录页、Unexpected error while obtaining UI hierarchy问题

Appium连接真机 使用数据线连接电脑&#xff0c;然后选择文件传输方式 打开手机设置拉至底部&#xff0c;点击关于手机&#xff0c;连续点击7次版本号打开开发者模式 点击设置中的系统与更新&#xff0c;找到开发者选项----> 打开USB调试即可 在终端中输入adb devices确定…

c语言经典例题-数组的使用

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 选择法排序&#xff1a; 题目&#xff1a; 本关任务&#xff1a;使用选择法排序&#xff08;http://t.csdn.cn/…

统计学 一元线性回归

统计学 一元线性回归 回归&#xff08;Regression&#xff09;&#xff1a;假定因变量与自变量之间有某种关系&#xff0c;并把这种关系用适当的数学模型表达出来&#xff0c;利用该模型根据给定的自变量来预测因变量 线性回归&#xff1a;因变量和自变量之间是线性关系 非线…

看板组件:Bryntum Task Board JS 5.3.0 Crack

一个超级灵活的看板组件&#xff0c;Bryntum Task Board 是一个灵活的看板 Web 组件&#xff0c;可帮助您可视化和管理您的工作。 功能丰富 任务板非常灵活&#xff0c;允许您完全自定义卡片、列和泳道的渲染和样式。借助丰富的 API&#xff0c;您甚至可以在运行时打开或关闭功…

MSE 诊断利器上线

作者&#xff1a;子葵 背景 在日常开发和生产环境中&#xff0c;可能会遇到由于网络或者其他因素导致客户端连接 MSE 集群出现异常&#xff0c;此时需要排查集群以及客户端状态&#xff0c;通常需要通过文档查询对应的异常解释来定位问题&#xff0c;排查问题的链路比较长&am…

JNI内通过参数形式从C/C++中传递string类型数据至Java层

目录 0 前言 1 string类型参数形式传值 2 测试和结果 0 前言 类似之前我写过的两篇文章&#xff1a;一篇介绍了在JNI中基础类型int的传值方式&#xff1b;一篇详细梳理了在JNI层中多维数组的多种传值方式。 JNI内两种方式从C/C中传递一维、二维、三维数组数据至Java层详细…

如何实现接口幂等性

1 什么是幂等 幂等操作的特点是一次或者任意多次执行所产生的影响均与一次执行的影响相同&#xff0c;不会因为多次的请求而产生不一样的结果。换句话说&#xff0c;就是我使用相同的请求参数&#xff0c;去请求同一个接口&#xff0c;不管请求多少次获取到的响应数据应该是一…

JUC并发编程——Park Unpark

一、Park & Unpark 1.1 基本使用 它们是 LockSupport 类中的方法 // 暂停当前线程 LockSupport.park(); // 恢复某个线程的运行 LockSupport.unpark(暂停线程对象)先 park 再 unpark import lombok.extern.slf4j.Slf4j; import java.util.concurrent.locks.LockSuppor…

Baumer工业相机堡盟相机如何使用PnPEventHandler实现相机掉线自动重连(C++新)

项目场景&#xff1a; Baumer工业相机堡盟相机传统开发包BGAPI SDK进行工业视觉软件整合时&#xff0c;常常需要将SDK中一些功能整合到图像处理软件中&#xff0c;方便项目的推进使用&#xff1b; 在项目的图像处理任务中&#xff0c;可能会因为一些硬件比如线缆网卡的原因导…

五点CRM系统核心功能是什么

很多企业已经把CRM客户管理系统纳入信息化建设首选&#xff0c;用于提升核心竞争力&#xff0c;改善企业市场、销售、服务、渠道和客户管理等几个方面&#xff0c;并进行创新或转型。CRM系统战略的五个关键要点是&#xff1a;挖掘潜在客户、评估和培育、跟进并成交、分析并提高…

传输层--UDP协议

目录 一.补充知识 1.1传输层​ 1.2端口号 1.3netstat 二.UDP 2.1UDP协议格式 2.2UDP如何将有效载荷上交给上层 2.3UDP如何将报头与有效载荷进行分离&#xff1f; 2.4理解报头 2.5.UDP协议特点 2.6UDP缓冲区 2.6基于UDP的应用层协议 一.补充知识 1.1传输层 之前介绍…

相恨见晚的office办公神器(不坑盒子/打工人Excel插件2023年最新版)

不坑盒子 这是一个非常好用的插件工具&#xff0c;专门应用在Word文档和wps&#xff0c;支持Office 2010以上的版本&#xff0c;操作也简单且实用。 不坑盒子下载及使用说明 一键排版功能 像是下面的自动排版功能&#xff0c;可以在配置里面先设定好需要的格式&#xff0c;…

站内SEO排名不上?或许是这些常见问题导致的

在当今数字化的时代&#xff0c;几乎所有的企业和个人都有自己的网站。 然而&#xff0c;拥有一个网站并不代表着它就一定能够被搜索引擎优先展示。 SEO&#xff08;搜索引擎优化&#xff09;是一门需要技巧和耐心的艺术。在实践SEO的过程中&#xff0c;站内SEO是一个重要的环…

Hive 运行环境搭建

文章目录Hive 运行环境搭建一、Hive 安装部署1、安装hive2、MySQL 安装3、Hive 元数据配置到 Mysql1) 拷贝驱动2) 配置Metastore 到 MySQL3) 再次启动Hive4) 使用元数据服务的方式访问Hive二、使用Dbaver连接HiveHive 运行环境搭建 HIve 下载地址&#xff1a;http://archive.a…

剑指-Offer-09-用两个栈实现队列

剑指-Offer-09-用两个栈实现队列 题目描述&#xff1a; 用两个栈实现一个队列。队列的声明如下&#xff0c;请实现它的两个函数 appendTail 和 deleteHead &#xff0c;分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素&#xff0c;deleteHead 操作…

Qt 事件循环

一、QT消息/事件循环机制   Qt作为一个可视化GUI界面操作系统&#xff0c;是基于事件驱动的&#xff0c;我们程序执行的顺序不再是线性的&#xff0c;而是由一个个应用程序内部或外部的事件进行驱动的&#xff0c;无事件时便阻塞。这个有点类似于while循环&#xff0c;函数体…

智能交通数据集Rope3D(仅限科研使用)

Rope3D Dataset 官网&#xff1a;https://thudair.baai.ac.cn/index &#xff01;&#xff01;&#xff01;如想要使用Rope3D数据集进行2D检测&#xff0c;最后有我们处理完的数据集链接。 &#xff01;&#xff01;&#xff01; 介绍&#xff1a; DAIR-V2X数据集是首个用于…

11 款全能的苹果设备激活锁移除工具

出于安全原因&#xff0c;每个 Apple 用户都可以设置一个 Apple ID和密码来保护他们的信息。但如果您忘记了 Apple ID 和密码&#xff0c;您将无法使用 iCloud 激活锁。如果发生这种情况&#xff0c;您应该怎么办&#xff1f;不用担心。iCloud 激活锁移除工具专为在 iOS 设备上…