一. 函数重载的定义
如果同一作用域内的几个函数名字相同但形参列表不同, 我们称之为重载 (overload) 函数. 函数重载是函数的一种特殊情况, C++ 允许在同一作用域中声明几个功能类似的同名函数, 这些同名函数的形参列表不同, 常用来处理实现功能类似数据类型不同的问题.
二. 函数重载的几种情况
1. 参数类型不同
void func(int m, int n)
{
cout << "func(int m, int n)" << endl;
}
void func(double m, double n)
{
cout << "func(double m, double n)" << endl;
}
2. 参数个数不同
void func(int m, int n)
{
cout << "func(int m, int n)" << endl;
}
void func(int m)
{
cout << "func(int m)" << endl;
}
三. 函数重载原理
若干个重载函数的函数名相同, 但是形参列表不同, 那么编译器在编译的时候如何区分这两个函数呢? 为了支持 C++ 函数重载, 发明了名称修饰 (Name Decoration) 的机制.
以下面一段代码为实例, 讲解 C++ 函数重载的原理.
#include <iostream>
using namespace std;
int func(int m) { return m; };
float func(float m) { return m; };
class C {
public:
int func(int m) { return m; };
class C2 {
public:
int func(int m) { return m; };
};
};
namespace N {
int func(int m) { return m; };
class C {
public:
int func(int m) { return m; };
};
}
int main()
{
C c1;
C::C2 c2;
N::C c3;
func(1);
func(1.1f);
c1.func(1);
c2.func(1);
N::func(1);
c3.func(1);
return 0;
}
函数签名
这段代码中有6个函数名同为 func 的函数, 不过它们的返回类型, 形参列表, 以及所在的类和命名空间不尽相同. 此处引入一个术语叫做函数签名 (Function Signature), 函数签名用于识别不同的函数, 就像身份证用于识别不同的人一样, 函数签名包含了一个函数的诸多信息, 包括它的函数名, 参数类型, 所在的类和命名空间及其他信息, 下表显示了上述 6 个函数的函数签名.
符号名 (修饰后名称)
在编译器处理符号时, 使用某种名称修饰的方法, 使得每个函数签名对应一个修饰后名称(Decorated Name), 编译器在将 C++ 源代码编译成目标文件时, 会对函数签名进行修饰, 形成符号名 (也就是修饰后名称), 所以对于不同函数签名的函数, 即使函数名相同, 编译器都认为它们是不同的函数. 上表的 6 个函数签名在 g++ 编译器下, 相对应的修饰后名称如下表所示.
objdump
objdump 是用于查看目标文件或者可执行文件构成的工具.
objdump -S 查看反汇编, 每个函数签名确实都有对应的修饰后名称.
GCC 的基本 C++ 名称修饰方法如下: 所有的符号名都以 _Z 开头, 对于嵌套的符号名 (在命名空间或在类里面的), 后面紧跟 N, 然后是各个命名空间和类的名字, 每个名字前是该名字字符串长度, 若干个名字叠加后, 再以 E 间隔, 最后以形参列表结尾.
c++filt
c++filt 是一个可以用来解析修饰后名称的工具, 比如
四. 为什么 C 不支持函数重载
以下面两段代码为实例, 讲解为什么 C 不支持函数重载, C++ 支持函数重载.
test.c
#include <stdio.h>
void func(int m, double n)
{
printf("func(int m, double n)\n");
}
int main()
{
func(1, 1.1);
return 0;
}
test.cpp
#include <iostream>
using namespace std;
void func(int m)
{
cout << "func(int m)" << endl;
}
void func(int m, double n)
{
cout << "func(int m, double n)" << endl;
}
int main()
{
func(1);
func(1, 1.1);
return 0;
}
对 test.c 和 test.cpp 分别使用 gcc 和 g++ 编译形成相应的可执行文件.
objdump -S 查看两个文件的反汇编, 发现 gcc 并没有对函数签名进行修饰!
在 linux 下, 使用 g++ 对以 .cpp 为后缀的文件完成编译后, 函数签名被修饰; 而使用 gcc 对以 .c 为后缀的文件完成编译后, 函数签名未被修饰. 这也就解释了为什么 C++ 支持函数重载, 而 C 却不支持函数重载!