网站商城建设方案,网站2个页面做首页,哪些网站平台可以做推广,求手机网站目录 一. 函数重载的概念和分类
1.1 什么是函数重载
1.2 函数重载的分类
1.3 关于函数重载的几点注意事项
二. C实现函数重载的底层逻辑#xff08;为什么C可以实现函数重载而C语言不能#xff09;
2.1 编译器编译程序的过程
2.2 为什么C可以实现函数重载而C语言不能
…目录 一. 函数重载的概念和分类
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
#includeiostream
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
#includeiostream
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
#includeiostream
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语言编译环境下报错信息为无法解析外部符号_AddC编译环境下报错信息为无法解析外部符号int _cdcel Add(int, int)(?AddYAHHz)图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程序员定义的函数名funcv表示函数没有参数
再比如int func(int x, int y)经修饰后的函数名变为_Z4funcii其中ii表示函数有两个整形参数。对更复杂一些的情况如int func(int i, int* pi)经修饰后函数名变为Z4funciPi其中Pi表示int*类型的参数。
在Window环境下函数名的修饰规则更为复杂。但是我们只需要知道C会按照一定的规则对函数原本的名称进行修饰通过修饰后的函数名查找函数地址即可没必要深究修饰规则。