在c++中,存在一种用法,名叫函数重载,函数重载是C++中一个非常重要的特性,它允许程序员定义同名参数列表不同的函数,这样可以方便地实现代码复用和提高程序的可读性。但在c的学习过程中,我却从未见过这样的用法,所以写了如下的一篇博客,记录c++为何支持重载的原因。

🐶函数重载

函数重载指的是在同一个作用域内,可以定义多个同名但参数列表不同的函数。例如:
如下是两个同名的add函数,功能是将两数相加并返回。

1
2
3
4
5
6
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}

虽然都是add,但是它们的参数列表不同,一个是两个整数,一个是两个浮点数。当调用add函数时,编译器会根据实参的类型和数量来选择调用哪一个函数。例如:

1
2
int x = add(1, 2);         // 调用int add(int, int)
double y = add(1.0, 2.0); // 调用double add(double, double)

这就是函数重载。函数重载的好处在于,可以让程序员使用同一个名字来表示多个不同的操作,这样可以提高程序的可读性和可维护性。

🐭c++为何支持函数重载

在c/c++中,要想将一个程序跑起来,就需要经过如下的几个阶段(合起来就被称为编译):

预处理,编译,汇编,链接

预处理是C++程序在编译过程中的第一步,它主要负责处理头文件、宏定义和条件编译等。在预处理阶段,预处理器会对函数的声明进行处理,通常,此文件是以.i为后缀。

编译器在编译阶段主要负责将源代码翻译成汇编代码。编译器通过分析源代码,将其转换为计算机可以理解的指令。汇编语言程序中的每条语句都以一种标准的文本格式确切的描述了一条低级机器语言指令。此时,文件后缀为.s

汇编阶段,编译器会将汇编代码翻译成机器代码。汇编器将.s文件翻译成机器语言指令,把这些指令打包成一种可重定位目标程序的格式,并将结果保存在目标文件.o中。.o文件是一个二进制文件,它的字节编码是机器语言指令而不是字符,如果我们在文本文件中打开.o文件,所呈现的就是一串串的乱码。在汇编阶段,会生成一份符号表

链接阶段,编译器会将多个目标文件链接成一个可执行文件。

c++存在函数名修饰规则,而c没有,这就是c++可以实现重载的关键。
规则规定:在 C++ 中,为了支持函数重载,编译器会对每个函数名进行修饰,从而生成唯一的符号来标识该函数。函数名修饰规则是由编译器定义的,不同的编译器可能会采用不同的修饰规则。Microsoft Visual C++ 编译器将函数名后面加上下划线和参数个数,参数类型和参数个数之间用 @ 分隔。例如,函数 void foo(int a, float b) 的修饰名为 _foo@8。

规则的体现就在汇编阶段。在汇编阶段,会生成一份符号表,这份符号表将会记录函数的名称地址

  • 若无函数名修饰规则,那么编译器只根据原始函数名来生成放在符号表中的函数名;如果有修饰规则,那么会将形参个数,类型,顺序也添加进考虑范围,以形成新的函数名。

需要注意的是,在编译的过程中,是不会执行用户的自定义函数的。
在链接的阶段,链接器会解析所有的符号,并将被调用的函数的目标代码包含在可执行文件中。当我们在一个源文件中定义一个函数时,编译器会将该函数编译成目标代码,并在生成的目标文件中生成相应的符号。如果在其他源文件中调用了该函数,链接器会在链接阶段将相应的目标文件合并成一个可执行文件,并将各个符号解析成实际的地址,从而使得程序能够正确地执行。