C++中的函数重载

函数重载在很多语言中都存在,那么什么是函数重载呢?为什么要有函数重载?

带着这个问题,我们来进行一次思考…

首先从很多书本上都可以看到函数重载的概念:函数重载是指在同一作用域内,可以有一组具有相同函数名不同参数列表的函数,这组函数被称为重载函数。

重载函数通常用来命名一组功能相似的函数,这样做减少了命名污染,对程序可读性有很大的提示,也间接降低了维护的成本。

我们来一段代码体会一下函数重载:

#include<iostream>
#include<string>
using namespace std;

void Print(int i)
{
 cout<<i<<endl;
}

void Print(string str)
{
 cout<<str<<endl;
}

void Print(char c)
{
 cout<<c<<endl;
}

int main()
{
    Print(10);
    Print("hello overload");
    Print('C');
    return 0;
}

通过上面的代码,编译器会自动的为我们匹配到最适合的函数。

为什么需要函数重载?

  • 在没有函数重载的情况下,我们实现上面函数的功能只能一个一个的去起名字,比如:Print_i,Print_St,Print_c。打过网游的朋友们都知道,起名字很难搞的!!!所以,这就是函数重载的作用之一!
  • 类的构造函数和类名相同,所以所有的构造函数名字都相同。如果没有重载,要实例化不同的对象,那你又得忙了~
  • 操作符重载,实际上就是函数重载,它可以使我们自行定义已有操作符的含义。

既然已经晓得了函数重载有这么多好处,那么我们来说说它是如何实现的?

我们来研究一下它底层的汇编代码,在调用的时候,是如何解析这些函数的!(下面的代码都在Linux系统Ubuntu16.0分支进行实验)

把上面的代码用g++ -S 进行汇编,然后观察函数名编译之后为:

  • void Print(int i)->_Z5Printi
  • void Print(String str)->_Z5PrintNSt7
  • void Print(char c)->_Z5Printc

这里写图片描述

我们可以发现,重载函数的名字全都变了,名字不在是我们当初写的了,那么名字是通过怎样的条件变换的?看完上面的名字,我们猜想可能是原本的函数名+参数列表,因为在定义中没有返回值这一项。但是进一步猜想,前面的_Z5是什么意思,为什么要放在前面呢?也许要加上返回值才对,那就是返回值类型+函数名+参数列表

我们可以通过代码测试一下自己的猜想,把返回值换成别的。

 int Add(int a, int b)
 {
     return a+b;
 }

 double Add(double a, double b)
 {
      return a+b;
 }

汇编结果如下:

  • int Add(int a, int b)-> _Z3Addii
  • double Add(double a, double b)->_Z3Adddd

    果然如此,并且大胆的猜测,函数参数列表中的int可能对应i,double可能对应d,char可能对应c,诸如此类的…而前面的Z+数字对应的可能是返回类型。

既然返回类型也在我们的命名改变的映射机制之中,那么为什么不将函数返回值类型考虑到函数重载之中呢? ——这是为了保持解析操作符或函数调用时,独立于上下文。

如果返回值类型考虑到函数重载中,这样将不可能再独立于上下文决定调用哪个函数,也就是说,当返回值类型与接收参数的类型不一致时,就不是编译器判断的范围了。我们的编程语言和人类语言很大的区别就是,编程语言采用上下文无关文法,而人类语言通常通过上下文推断其含义。

至此我们已经明白了函数重载的过程。等等,我们是不是少了点什么?作用域呢?

作用域当然是不可少的一环,我们依然通过代码来解析:

#include<iostream>
#include<string>
using namespace std;
class test{
public:
    void Print(int i)
    {
        cout<<i<<endl;
    }

    void Print(string str)
    {
        cout<<str<<endl;
    }

    void Print(char c)
    {
        cout<<c<<endl;
    }
};

int main()
{
    test t;
    t.Print(10);
    t.Print("hello overload");
    t.Print('C');
    return 0;
}

来看看它映射后的函数名:

这里写图片描述

  • void Print(int i)->_ZN4test5PrintEi
  • void Print(string str)->_ZN4test5PrintENST
  • void Print(char c)->_ZN4test5PrintEc

我们通过和前面的对比就可以知道,N4应该表示作用域。那么我们的结论又要改一改了,编译器对命名的映射机制为:作用域+返回值类型+函数名+参数列表

现在已经解决了一大问题,在定义完函数重载之后,用函数名调用的时候是如何去解析和匹配最佳重载函数的:

  • 精确匹配:参数完全匹配,包括顺序、类型、const、volatile
  • 提升匹配:即整数类型的提升(bool到int、char到int等),float到double
  • 使用标准转换匹配:如隐式类型转换
  • 使用用户自定义匹配:操作符重载
  • 使用省略号匹配:可变参数列表

如果通过重载的机制匹配到多个函数,调用就被拒绝,向上抛出异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值