C++ - 函数重载

252 阅读3分钟

一. 函数重载的定义

如果同一作用域内的几个函数名字相同但形参列表不同, 我们称之为重载 (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;
}

image.png

2. 参数个数不同

void func(int m, int n)
{
    cout << "func(int m, int n)" << endl;
}

void func(int m)
{
    cout << "func(int m)" << endl;
}

image.png

三. 函数重载原理

若干个重载函数的函数名相同, 但是形参列表不同, 那么编译器在编译的时候如何区分这两个函数呢? 为了支持 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 个函数的函数签名.

image.png

符号名 (修饰后名称)

在编译器处理符号时, 使用某种名称修饰的方法, 使得每个函数签名对应一个修饰后名称(Decorated Name), 编译器在将 C++ 源代码编译成目标文件时, 会对函数签名进行修饰, 形成符号名 (也就是修饰后名称), 所以对于不同函数签名的函数, 即使函数名相同, 编译器都认为它们是不同的函数. 上表的 6 个函数签名在 g++ 编译器下, 相对应的修饰后名称如下表所示.

image.png

objdump

objdump 是用于查看目标文件或者可执行文件构成的工具.

objdump -S 查看反汇编, 每个函数签名确实都有对应的修饰后名称.

image.png

GCC 的基本 C++ 名称修饰方法如下: 所有的符号名都以 _Z 开头, 对于嵌套的符号名 (在命名空间或在类里面的), 后面紧跟 N, 然后是各个命名空间和类的名字, 每个名字前是该名字字符串长度, 若干个名字叠加后, 再以 E 间隔, 最后以形参列表结尾.

c++filt

c++filt 是一个可以用来解析修饰后名称的工具, 比如

image.png

四. 为什么 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.ctest.cpp 分别使用 gccg++ 编译形成相应的可执行文件.

image.png

objdump -S 查看两个文件的反汇编, 发现 gcc 并没有对函数签名进行修饰!

image.png

在 linux 下, 使用 g++ 对以 .cpp 为后缀的文件完成编译后, 函数签名被修饰; 而使用 gcc 对以 .c 为后缀的文件完成编译后, 函数签名未被修饰. 这也就解释了为什么 C++ 支持函数重载, 而 C 却不支持函数重载!