【C++笔记】C++中的函数重载中为什么不考虑返回值类型

点击打开链接

点击打开链接

####################

1. 问题描述

函数重载是指在同一作用域内,可以有一组具有相同函数名不同参数列表的函数,这组函数被称为重载函数。那为什么不可以是函数名相同,参数列表相同,函数的返回值不同呢?


2. 从一个函数重载实例说起

          看下面的一个例子,来体会一下:实现一个打印函数,既可以打印int型、也可以打印字符串型。在C++中,我们可以这样做:

  1. #include<iostream>
  2. using namespace std;
  3. void print(int i)
  4. {
  5. cout<< "print a integer :"<<i<< endl;
  6. }
  7. void print(string str)
  8. {
  9. cout<< "print a string :"<<str<< endl;
  10. }
  11. int main()
  12. {
  13. print( 12);
  14. print( "hello world!");
  15. return 0;
  16. }
          通过上面代码的实现,可以根据具体的print()的参数去调用print(int)还是print(string)。上面print(12)会去调用print(int),print("hello world")会去调用print(string)


3. 为什么需要函数重载(why)?

  • 试想如果没有函数重载机制,如在C中,你必须要这样去做:为这个print函数取不同的名字,如print_int、print_string。这里还只是两个的情况,如果是很多个的话,就需要为实现同一个功能的函数取很多个名字,如加入打印long型、char*、各种类型的数组等等。这样做很不友好!
  • 类的构造函数跟类名相同,也就是说:构造函数都同名。如果没有函数重载机制,要想实例化不同的对象,那是相当的麻烦!
  • 操作符重载,本质上就是函数重载,它大大丰富了已有操作符的含义,方便使用,如+可用于连接字符串等!

通过上面的介绍我们对函数重载,应该唤醒了我们对函数重载的大概记忆。下面我们就来分析,C++是如何实现函数重载机制的


4. 函数重载为什么不考虑返回值类型

         为了弄清楚这个问题,我们先看看声明/定义重载函数时,是如何解决命名冲突的?

        为了了解编译器是如何处理这些重载函数的,我们反编译下上面我们生成的执行文件,看下汇编代码(全文都是在Linux下面做的实验,Windows类似,你也可以参考《一道简单的题目引发的思考》一文,那里既用到Linux下面的反汇编和Windows下面的反汇编,并注明了Linux和Windows汇编语言的区别)。我们执行命令objdump -d a.out >log.txt反汇编并将结果重定向到log.txt文件中,然后分析log.txt文件。

        发现函数void print(int i) 编译之后为:(注意它的函数签名变为——_Z5printi


        发现函数void print(string str) 编译之后为:(注意它的函数签名变为——_Z5printSs



        我们可以发现编译之后,重载函数的名字变了不再都是print!这样不存在命名冲突的问题了,但又有新的问题了——变名机制是怎样的,即如何将一个重载函数的签名映射到一个新的标识?我的第一反应是:函数名+参数列表,因为函数重载取决于参数的类型、个数,而跟返回类型无关。但看下面的映射关系:

void print(int i)                    -->         _Z5printi 
void print(string str)         -->         _Z5printSs

        进一步猜想,前面的Z5表示返回值类型,print函数名,i表示整型int,Ss表示字符串string,即映射为返回类型+函数名+参数列表。最后在main函数中就是通过_Z5printi_Z5printSs来调用对应的函数的:

80489bc:       e8 73 ff ff ff          call   8048934 <_Z5printi> 
…………… 
80489f0:       e8 7a ff ff ff          call   804896f <_Z5printSs>

         我们再写几个重载函数来验证一下猜想,如:

void print(long l)           -->           _Z5printl 
void print(char str)      -->           _Z5printc 
        可以发现大概是int->i,long->l,char->c,string->Ss….基本上都是用首字母代表,现在我们来现在一个函数的返回值类型是否真的对函数变名有影响,如:

  1. #include<iostream>
  2. using namespace std;
  3. int max(int a,int b)
  4. {
  5. return a>=b?a:b;
  6. }
  7. double max(double a,double b)
  8. {
  9. return a>=b?a:b;
  10. }
  11. int main()
  12. {
  13. cout<< "max int is: "<<max( 1, 3)<< endl;
  14. cout<< "max double is: "<<max( 1.2, 1.3)<< endl;
  15. return 0;
  16. }
int  max( int  a, int  b) 映射为 _Z3maxii double  max( double  a, double  b) 映射为 _Z3maxdd, 这证实了我的猜想,Z后面的数字代码各种返回类型。更加详细的对应关系,如那个数字对应那个返回类型,哪个字符代表哪重参数类型,就不去具体研究了,因为这个东西跟编译器有关,上面的研究都是基于g++编译器,如果用的是vs编译器的话,对应关系跟这个肯定不一样。但是规则是一样的:“ 返回类型 + 函数名 + 参数列表 ”。 然返回类型也考虑到映射机制中,这样不同的返回类型映射之后的函数名肯定不一样了,但为什么不将函数返回类型考虑到函数重载中呢?——这是为了保持解析操作符或函数调用时,独立于上下文(不依赖于上下文),看下面的例子

  1. float sqrt( float);
  2. double sqrt( double);
  3. void f( double da, float fla)
  4. {
  5. float fl=sqrt(da); //调用sqrt(double)
  6. double d=sqrt(da); //调用sqrt(double)
  7. fl=sqrt(fla); //调用sqrt(float)
  8. d=sqrt(fla); //调用sqrt(float)
  9. }
        如果返回类型考虑到函数重载中,这样将不可能再独立于上下文决定调用哪个函数。


5. 总结

        从重载的机制来看,并不是没法实现返回值类型不同情况下的函数重载,只是从独立于上下文的出发点考虑,所以才没有支持。

关于c++函数重载的更多介绍可以参考C++的函数重载


猜你喜欢

转载自blog.csdn.net/HelloZEX/article/details/81017217
今日推荐