博客主页:【夜泉_ly】
本文专栏:【C++】
欢迎点赞👍收藏⭐关注❤️
C++ -函数重载-详解
- 1.是什么
- 2.怎么用
- 2.1示例
- 3.原理
- 3.1C/C++编译链接过程
- 3.2函数名修饰规则
- 3.3过程
- 1.调用函数的过程
- 2.编译阶段的函数调用
- 总结
1.是什么
如果在百度中搜索重载这个词,会得到以下结果:
不怎么容易理解吧🤣。
其实,可以简单的把重载理解为同名,而函数重载就是定义同名函数。
例如:
void Add(int a, int b);
void Add(double a, double b);
这在C语言中肯定会报错,但C++支持这样的操作,并且在调用时会根据传入的参数类型自动匹配对应的函数。
2.怎么用
函数重载不能支持所有同名函数,首先,参数列表需要不同,主要分三方面:
- 参数个数不同
- 参数类型不同
- 类型顺序不同
这意味着,如果函数名相同、参数列表相同、但是返回值不同,不构成重载!例如:
int func(int a, int b);
double func(int a, int b);
2.1示例
void func(); //无参数
void func(int a);//单参数
构成重载——参数个数不同。
void func(int a, int b);//参数顺序不同
void func(int b, int a);//参数顺序不同
有歧义,不构成重载。
报错信息如下:
void func(int a, int b);//第一个参数为整数
void func(char a,int b);//第一个参数为字符
构成重载——参数类型不同。
void func(int a, char b);//第一个参数为整数
void func(char a,int b);//第一个参数为字符
构成重载——类型顺序不同。
这时有人想到了缺省参数💡,于是出现了这种比较特殊的情况:
void func();
void func(int a = 0);
是否构成重载?
构成重载!符合重载函数的定义。
可以在上图的右下角看见,程序正常退出。
那么这样写有没有问题?
有问题!如果写出下面这句代码,则会引发歧义:
func();
3.原理
这里有两个问题:
- 为什么C语言不支持重载,C++支持重载?
- C++是怎么支持重载的?
想要解决这些问题,需要了解编译链接过程,以及函数名修饰规则。
自动识别类型-函数重载。
3.1C/C++编译链接过程
这里只是简单提两句,因为更详细的内容我也不知道:
-
预处理:
头文件展开 / 宏替换 / 条件编译 / 去掉注释……(.h/.c/.cpp
文件变为.i
文件) -
编译:
检查语法 / 生成汇编代码(.i
文件变为.s
文件)上面那个缺省参数的特殊情况在编译时不会报错,就是因为语法没错。
-
汇编:
将汇编代码转化为二进制的机器码(.s
文件变为.o
文件) -
链接:
生成符号表 / 将目标文件(.o
–>object
)链接成可执行程序(.exe/a.out
)
3.2函数名修饰规则
C++ 编译器在编译过程中会对函数名进行修饰,以便区分不同的重载函数。修饰规则通常包括以下内容:
- 参数类型:函数参数的类型信息。
- 参数数量:函数参数的数量。
- 返回类型:函数的返回类型信息。
在不同环境下,函数名修饰规则也不同,但目的都是将同名变为不同名。
3.3过程
二进制机器码与汇编代码是相互转换的关系,也就是说,如果想查看程序的底层实现,可以通过查看汇编代码来获取更直观的信息。
我在这里又建了三个文件:
func.h
:
#pragma once
#include <stdio.h>
void func(int a, int b);
void func(double a, double b);
func.c
:
#include "func.h"
void func(int a, int b)
{
printf("void func(int a, int b)\n");
}
void func(double a, double b)
{
printf("void func(double a, double b)\n");
}
test.c
:
#include "func.h"
int main()
{
func(1, 2);
func(1.0, 2.0);
return 0;
}
1.调用函数的过程
在开始调试的时候转到反汇编,会看见:
这里可以看见,在每个函数调用语句下都有一条“call
指令+地址”:
这里重点关注第一个call
指令,在调试到那里时,会跳转到jump
指令,而这个jump
指令的地址就是刚刚call
指令后跟的地址:
而这个jump
指令,会根据它后面跟的地址,真正跳转到函数的位置:
2.编译阶段的函数调用
在编译过程中,函数调用会被转化为 call
指令,后面跟着函数名(函数的地址),类似刚刚看见的:
当函数的定义和声明分离时,编译阶段无法立即获取函数的实际地址,因为此时只包含了(.h),即编译器只看到声明,而函数的定义在其他文件中。
在这种情况下,编译器能通过声明完成编译,但不会生成最终的地址。(我只会用VS2022,而且不知道怎么调出这种效果😅)
在链接阶段,链接器才会将调用语句与实际的函数定义关联起来,找到函数的真正地址。
编译错误与链接错误:
- 因为编译器在看到声明时只知道函数存在,但没有具体的实现细节,因此只要声明正确,编译可以顺利通过。
- 但是,链接阶段如果找不到函数的定义,就会发生链接错误。
当然,当函数的定义与声明在同一个源文件中时,编译器可以直接找到函数的地址,因为定义已经存在,编译器能够在编译阶段就处理地址问题。
回到函数重载:
在编译阶段,C++编译器会通过函数名修饰为每个重载函数生成唯一的符号,从而确保编译器能够区分同名但参数列表不同的重载函数。
而C语言不会进行函数名修饰,因此遇到同名函数会编译失败。
这就解释了为什么C语言不支持重载,C++支持重载?和C++是怎么支持重载的?
总结
函数重载是 C++ 中一项重要的特性,它提高了代码的灵活性和可读性。
通过函数名修饰规则,C++ 在编译过程中实现了自动类型匹配,避免了调用时的歧义。
在实际编程中,合理使用函数重载可以极大地提高编程效率和代码质量。
希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!