C++入门:函数重载
创始人
2024-05-26 02:51:27
0

目录

一. 函数重载的概念和分类

1.1 什么是函数重载

1.2 函数重载的分类

1.3 关于函数重载的几点注意事项

二. C++实现函数重载的底层逻辑(为什么C++可以实现函数重载而C语言不能)

2.1 编译器编译程序的过程

2.2 为什么C++可以实现函数重载而C语言不能

2.3 Linux环境下C++编译器对函数名的修饰规则


一. 函数重载的概念和分类

1.1 什么是函数重载

函数重载是函数的一种特殊情况,C++允许在同一作用域中定义或声明几个名称相同的函数,这些同名函数经常用于处理形参不同(形参个数、类型或顺序不同),但是实现功能类似的函数。如:int Add(int a, int b)和int Add(double a, double)就可以构成一组函数重载。

注意:C语言不支持函数重载。

1.2 函数重载的分类

函数重载有3种情况:参数类型不同、参数个数不同、参数顺序不同。满足上述3种情况至少其中一种,才能构成函数重载。

参数类型不同

两个重名函数,如果参数类型不同,可以构成函数重载。演示代码1.1定义了两个求加法函数Add,一个是针对整形数据的int Add(int x, int y),另一个是针对双精度浮点型数据的double Add(double x, double y)。在调用Add函数时,如果传入两个整形数据,就调用nt Add(int x, int y),如果传入两个双精度浮点型数据,就调用Add(double x, double y)。

编译器会自动识别调用两个重名函数中的哪一个,无需任何额外语句。

演示代码1.1:

#include
using namespace std;//对整形数据的加法函数
int Add(int x, int y)
{cout << "int Add(int x, int y)" << endl;return x + y;
}//对浮点型数据的Add函数
double Add(double x, double y)
{cout << "double Add(double x, double y)" << endl;return x + y;
}int main()
{int add_int = Add(2, 5);  //整形数据加法printf("add_int = %d\n", add_int);  //7double add_double = Add(2.1, 3.5);  //浮点型数据加法printf("add_double = %lf\n", add_double);  //5.6return 0;
}
图1.1 演示代码1.1的运行结果

参数个数不同

演示代码1.2定义了两个Add函数,分别实现对两个数据求加法和对三个数据求加法。通过控制调用函数时传给函数的参数个数,来确定调用哪个Add函数。

演示代码1.2:

#include
using namespace std;//对2个整形数据的加法函数
int Add(int x, int y)
{cout << "int Add(int x, int y)" << endl;return x + y;
}//对3个整形数据的加法函数
int Add(int x, int y, int z)
{cout << "int Add(int x, int y, int z)" << endl;return x + y + z;
}int main()
{int add_two = Add(1, 2);printf("add_two = %d\n", add_two);  //3int add_three = Add(1, 2, 3);printf("add_three = %d\n", add_three);  //6return 0;
}
图1.2  演示代码1.2的运行结果

形参顺序不同

演示代码1.3定义了两个func函数,第一个func函数的两个形参char类型数据在前、int类型数据在后,第二个func函数int类型数据在前、char类型数据在前。在调用函数时,通过控制传给函数的参数类型的顺序,来确定调用哪个func函数。

演示代码1.3:

#include
using namespace std;void func(char c, int i)
{cout << "void func(char c, int i)" << endl;
}void func(int i, char c)
{cout << "void func(int i, char c)" << endl;
}int main()
{func('A', 5);func(5, 'A');return 0;
}
图1.3  演示代码1.3的运行结果

1.3 关于函数重载的几点注意事项

返回值不同,不能构成重载

double func(int a)和int func(int a)不构成重载,因为其不满足函数的参数类型、个数或顺序不同中的任意一个,因此不能构成重载。

缺省值不同,不能构成重载

func(int a)和func(int a = 10)不能构成重载,这里的原因与返回值不同时的类似, 不满足函数的参数类型、个数或顺序不同中的任意一个。

总结:判断两个重名函数是否能构成重载,只需看函数参数的个数、类型和顺序是否满足条件,不用关注缺省值、返回值等任何其余问题。

func()和func(int a = 10)可以构成重载

虽然这两个同名函数可以构成重载,但在调用时,会出现歧义。如果通过语句func()调用函数,编译器无法确定这里应当处理为没有传入参数调用func()还是存在缺省参数调用func(10)。 

二. C++实现函数重载的底层逻辑(为什么C++可以实现函数重载而C语言不能)

2.1 编译器编译程序的过程

要弄清楚C++实现函数重载的底层逻辑,首先要清楚编译器编译程序的全过程。将一份程序文献生成可执行文件,要经历编译和链接两段过程,其中编译又可以细分为预编译、编译和汇编三个小过程。汇编过程结束时,每个.c(.cpp)文件都会生成一份目标文件(后缀名为.obj或.o),完成链接过程后,就会生成可执行程序。

图2.1 编译器编译程序的过程

预编译阶段

预编译阶段完成的工作包括:

  • 注释的删除
  • #define定义符号和宏的替换
  • 头文件的包含
  • 条件编译

编译阶段

编译阶段将C++代码或C语言代码转换为汇编代码,完成的工作有:

  • 语法检查
  • 符号汇总(只汇总全局符号,不汇总局部作用域中定义的临时变量名或函数名等)

汇编阶段

将汇编语言转换为计算机能够读懂的机器语言(二进制代码),这个过程完成的具体工作有:

  • 符号表的生成(每个.c/.cpp文件都会生成一份符号表)

符号表中存有符号的名称和其存储在内存中的地址,如果在当前.c\.cpp文件中仅声明了某个符号而没有定义,这样对这个.c/.cpp文件编译时就无法在内存中找到这个符号,这是,符号表中就会存储一份虚拟地址。

链接阶段

链接阶段完成的具体工作有:

  • 合并段表
  • 符号表的合并和重定位

如果在.c文件中调用一个没有被定义的函数或多次被定义的函数,编译器会在链接阶段检测出函数未定义或重复定义。理解链接阶段也是理解为什么C++能够支持函数重载而C语言不能支持函数重载的关键。

2.2 为什么C++可以实现函数重载而C语言不能

假设在主函数中调用Add函数,该函数的函数原型为int Add(int x, int y),而该函数仅被声明而没有被定义。在Windows环境先使用VS2019编译器,分别在C语言和C++编译环境下对程序进行编译,可以观察的报错信息:

  • 在C语言编译环境下,报错信息为:无法解析外部符号_Add
  • C++编译环境下,报错信息为:无法解析外部符号int _cdcel Add(int, int)(?Add@@YAHH@z)
图2.2  C语言报错信息
图2.3  C++报错信息

根据报错信息的不同,可以初步推断,C++编译器在汇总函数名符号时,会对函数名进行修饰。根据初步的推断,我们在VS2019编译器中,定义并调用Add函数(Add函数的定义和调用不再同一个.cpp文件中)。对演示代码2.1进行调试(其中的Add函数已有定义),观察其汇编代码(如图2.4所示),汇编指令 call 表示调用函数,图中Add后面括号里的内容为函数地址。

call指令通俗来讲就是实现“跳转过程”,程序在执行main函数中的命令时,在某一位置跳转去执行被调函数地址处的命令。

对于Add后面括号里的地址在什么阶段填入问题,分以下两种情况讨论:

  • 如果调用Add的.cpp文件中定义了Add函数,那么在编译阶段生成符号表时就会填入函数地址。
  • 如果Add函数定义在了其他.cpp文件而调用Add函数的.cpp文件中仅有函数的声明,那么就需要在链接阶段才会填入函数地址。
图2.4  函数调用的汇报代码

 如果此时发现了两个互相冲突的函数,则无法确定应该执行存储在哪一地址处的函数指令。

  • 对于C语言编译器,在生成符号表时,使用的函数名是原本程序中程序员定义的函数名,根据函数名标识查找函数所在的地址,此时如果存在两个相同的函数,函数名就会发生冲突。
  • 对于C++编译器,生成符号表时使用的函数名是经过一定的修饰规则修饰后的函数名,在函数调用时也是采用经修饰后的函数名标识查找函数所在的地址,只要函数的参数不同,调用两个名称相同的函数就不会存在歧义。

2.3 Linux环境下C++编译器对函数名的修饰规则

Linux环境下C++对函数名的修饰规则为:_Z + 函数名长度 + 函数名 + 参数信息

如:int func()在Linxu环境下被修饰后,函数名变为:_Z4funcv。其中:

  • _Z:表示前缀
  • 4:表示函数名有4个字符
  • func:程序员定义的函数名func
  • v:表示函数没有参数

再比如:int func(int x, int y),经修饰后的函数名变为:_Z4funcii,其中ii表示函数有两个整形参数。对更复杂一些的情况,如int func(int i, int* pi),经修饰后函数名变为:Z4funciPi,其中Pi表示int*类型的参数。

在Window环境下,函数名的修饰规则更为复杂。但是,我们只需要知道C++会按照一定的规则对函数原本的名称进行修饰,通过修饰后的函数名查找函数地址即可,没必要深究修饰规则。

 

相关内容

热门资讯

你的晚安是我的早安是什么歌曲 你的晚安是我的早安是什么歌曲苏天伦《你的早安是我的晚安》“你的晚安是我的早安”是单小源的歌曲《东京遇...
积极进取的小故事 积极进取的小故事现代的普通人,不要名人的。不能与别人重复,尽快回答   啄木鸟的故事       啄...
熊出没之探险日记3什么时候播出... 熊出没之探险日记3什么时候播出?熊出没之探险日记3,春节前播放。熊出没只是探险日记三2020年5月4...
谁知道所有有关“七”的歌?拜托... 谁知道所有有关“七”的歌?拜托了各位 谢谢就是歌曲名里有“七”这个字的!谢谢七月七迅谈日晴 看我七十...
求一本小说 女主穿越了三次 每... 求一本小说 女主穿越了三次 每次都在福临身边 后来怀孕了孩子被打掉了那个 女主叫什么雯?那个女主就是...
如果记忆不说话,流年也会开出花... 如果记忆不说话,流年也会开出花的基本信息书 名:《如果记忆不弯饥好说话,流年也会开出花》埋铅 作 者...
你好,旧时光漫画版在哪里可以看... 你好,旧时光漫画版在哪里可以看?暂时在绘心上连载
一首英文歌,男的组合唱的,MV... 一首英文歌,男的组合唱的,MV是一个婚礼的过程。求歌名。是不是darin的can'tstoplove...
为什么很多人喜欢用胶片相机? 为什么很多人喜欢用胶片相机?有一种情怀叫做“怀旧“吧,现在数码相机越来越普遍了,已经到了”全民摄影“...
女主先爱上男主,男主却不喜欢女... 女主先爱上男主,男主却不喜欢女主或者是另有所爱,最后女主男主还是在一起的穿越小说。有木有再生缘:我的...
爱情失恋伤感句子 爱情失恋伤感句子越是美好的从前,越幸福的曾经,现在只能带来锥心的疼痛,痛到撕心裂肺,肝肠寸断,终于痛...
24岁穿这个会不会显老 24岁穿这个会不会显老有点显老,这个颜色款式,颜色有点暗,没有活力,属于那种气质佳,长得高雅的女人,...
哈尔的移动城堡英语版 哈尔的移动城堡英语版可以发给我吗度盘~请查收~
秦时明月之万里长城什么时候播 秦时明月之万里长城什么时候播据说是今年暑假开播别急,官网什么的信他你就输了,12年之前底应该会出,杭...
孩子会得抽动症吗? 孩子会得抽动症吗?我天生的气性比较大,有时跟别人斗嘴时候就会手脚哆嗦,麻木,我问一下这是不是抽动症就...
亨德尔一生为音乐献出了怎样的贡... 亨德尔一生为音乐献出了怎样的贡献?亨德尔一生写了歌剧41部,清唱剧21部,以及大量的管乐器与弦乐器的...
礼仪起源和发展的经典故事? 礼仪起源和发展的经典故事?一、礼仪的起源;1、天神生礼仪;2、礼为天地人的统一体;3、礼产生于人的自...
描写桂林山水的句子有哪些? 描写桂林山水的句子有哪些?天下风光数桂林有杨万里的“梅花五岭八桂林,青罗带绕碧玉簪”;有邹应龙的“无...
避免与强敌正面对决的成语 避免与强敌正面对决的成语避免与强敌正面对决的成语避实就虚 【近义】避重就轻、避难就易、声东击西【反义...
多愁善感类的成语 多愁善感类的成语心细如发【解释】:极言小心谨慎,考虑周密。亦作“心细于发”。【出自】:吴梅《题天香石...