欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++声明/定义重载函数时如何解决命名冲突?如何解析重载的函数?

程序员文章站 2022-03-08 23:09:58
1、声明/定义重载函数时,是如何解决命名冲突的?(抛开函数重载不谈,using就是一种解决命名冲突的方法,解决命名冲突还有很多其它的方法,这里就不论述了) 2、当我们调用一个重载的函数时,又是如何去...

1、声明/定义重载函数时,是如何解决命名冲突的?(抛开函数重载不谈,using就是一种解决命名冲突的方法,解决命名冲突还有很多其它的方法,这里就不论述了)

2、当我们调用一个重载的函数时,又是如何去解析的?(即怎么知道调用的是哪个函数呢)

这两个问题是任何支持函数重载的语言都必须要解决的问题!带着这两个问题,我们开始本文的探讨。本文的主要内容如下:

1、例子引入(现象)

什么是函数重载(what)?

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

2、编译器如何解决命名冲突的?

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

3、重载函数的调用匹配

模凌两可的情况

4、常量性与函数重载

5、总结

1、例子引入(现象)

1.1、什么是函数重载(what)?

函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。(不能根据返回值区分重载)

重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。

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

#include<iostream>  
using namespace std; void print(int i)  
{  
  cout<<"print a integer :"<<i<<endl;  
}   
void print(string str)  
{  
  cout<<"print a string :"<<str<<endl;  
}   
int main()  
{  
  print(12);  
  print("hello world!"); return 0;  
}  

通过上面代码的实现,可以根据具体的print()的参数去调用print(int)还是print(string)。

1.2、为什么需要函数重载(why)?

(1)试想如果没有函数重载机制,如在c中,你必须要这样去做:为这个print函数取不同的名字,如print_int、print_string。这里还只是两个的情况,如果是很多个的话,就需要为实现同一个功能的函数取很多个名字,如加入打印long型、char*、各种类型的数组等等。这样做很不友好!

(2)类的构造函数跟类名相同,也就是说:构造函数都同名。如果没有函数重载机制,要想实例化不同的对象,那是相当的麻烦!

(3)操作符重载,本质上就是函数重载,它大大丰富了已有操作符的含义,方便使用,如+可用于连接字符串等!

2、编译器如何解决命名冲突的?

为了了解编译器是如何处理这些重载函数的,我们反编译下上面我们生成的执行文件,我们在linux下执行命令objdump -d a.out >log.txt反汇编并将结果重定向到log.txt文件中,然后分析log.txt文件。

我们可以发现编译之后,重载函数的名字变了不再都是原来的函数名!这样不存在命名冲突的问题了,但又有新的问题了——变名机制是怎样的,即如何将一个重载函数的签名映射到一个新的标识?

这个映射机制与编译器有关,在g++编译器下其规则为:“作用域+返回类型+函数名+参数列表”。

而在gcc中,其产生的函数符号仅由函数名决定。

既然返回类型也考虑到映射机制中,这样不同的返回类型映射之后的函数名肯定不一样了,但为什么不将函数返回类型考虑到函数重载中呢?

——这是为了保持解析操作符或函数调用时,独立于上下文(不依赖于上下文),看下面的例子

float sqrt(float);   
double sqrt(double);   
  
void f(double da, float fla)  
{   
  float fl=sqrt(da);//调用sqrt(double)   
  double d=sqrt(da);//调用sqrt(double)  
  
  fl=sqrt(fla);//调用sqrt(float)   
  d=sqrt(fla);//调用sqrt(float)   
}  

如果返回类型考虑到函数重载中,这样将不可能再独立于上下文决定调用哪个函数。

3、重载函数的调用匹配

现在已经解决了重载函数命名冲突的问题,在定义完重载函数之后,用函数名调用的时候是如何去解析的?为了估计哪个重载函数最适合,需要依次按照下列规则来判断:

精确匹配:参数匹配而不做转换,或者只是做微不足道的转换,如数组名到指针、函数名到指向函数的指针、t到const t;

提升匹配:即整数提升(如bool 到 int、char到int、short 到int),float到double

使用标准转换匹配:如int 到double、double到int、double到long double、derived*到base*、t*到void*、int到unsigned int;

使用用户自定义匹配;

使用省略号匹配:类似printf中省略号参数

如果在最高层有多个匹配函数找到,调用将被拒绝(因为有歧义、模凌两可)。看下面的例子:

void print(int);   
void print(const char*);   
void print(double);  
void print(long);   
void print(char);   
  
void h(char c,int i,short s, float f)  
{  
  print(c);//精确匹配,调用print(char)  
  print(i);//精确匹配,调用print(int)  
  print(s);//整数提升,调用print(int)   
  print(f);//float到double的提升,调用print(double)  
  print('a');//精确匹配,调用print(char)   
  print(49);//精确匹配,调用print(int)   
  print(0);//精确匹配,调用print(int)  
  print("a");//精确匹配,调用print(const char*)   
}  

定义太少或太多的重载函数,都有可能导致模凌两可,看下面的一个例子:

void f1(char);  
void f1(long);   
void f2(char*);  
void f2(int*);   
  
void k(int i)  
{  
  f1(i);//调用f1(char)? f1(long)?   
  f2(0);//调用f2(char*)?f2(int*)?   
}  

这时侯编译器就会报错,将错误抛给用户自己来处理:通过显示类型转换来调用等等(如f2(static_cast(0),当然这样做很丑,而且你想调用别的方法时有用做转换)。上面的例子只是一个参数的情况,下面我们再来看一个两个参数的情况:

int pow(int ,int);   
double pow(double,double);   
  
void g()  
{   
  double d=pow(2.0,2)//调用pow(int(2.0),2)? pow(2.0,double(2))?  
}  

3、常量性与函数重载

参数仅常量性不同是否可以重载?

不一定!成员函数的常量性不同可以重载;参数为引用时常量性不同可以重载;指针所指对象的常量性不同可以重载;只有传值参数以及指针本身的常量性不同时是不可以重载的。

以下情况,仅常量性不同的重载是合法的。

//成员函数的常量性不同,可以重载

class c {

public:

void funca(int i);

void funca(int i) const;

};

//参数类型为引用时,常量性不同可以重载

void funcb(int& i);

void funcb(const int& i);

//指针所指对象的常量性不同,可以重载

void funcc(int *p);

void funcc(const int *p);

成员函数的常量性可以认为是指针所指对象的常量性,当成员函数为const类型则说明调用该方法的类实例为const,相当于this指针为指向常量的指针。总结下来,引用和指针所指对象的常量性不同可以重载。(有些人将引用理解为指向对象的常量指针,这样一来剩下两种情况还可以进一步理解为:只有指针所指对象的常量性不同时可以重载。)

以下情况,仅常量性不同不可以重载。

//传值调用形参常量性不同,不可以重载

void funcd(int i);

void funcd(const int i);

//指针本身的常量性不同,不可以重载

void funce(int *p);

void funce(int * const p);

实际上以上两种情况也可以归为一种。当我们使用传址调用(传指针)时,可以被看做是传递指针值的传值调用,指针自身的常量性不同不能重载。总结来看即为:非引用类型的参数本身的常量性不同不能重载。

那么,为什么非引用类型参数的常量性不同不能重载呢?c++的这样设计的道理何在?这还得从c语言说起,c++出于与c语言保持兼容的考虑,保留了c语言中的传值调用(参考pythonjava这些面向对象语言,他们根本没有这个概念,从c++的角度来看它们的函数调用都是引用传参)。而在传值调用过程中,由于函数内部总会在执行函数体之前复制一个实参副本,并在函数体内使用副本参与运算。这就导致实参本身(实参为指针时,指针所指对象就不一定不改变了。)在函数内部永远不会改变,函数返回时实参的值总是调用时实参的值。既然如此,那么实参是否为const类型根本无关紧要,即使不是const类型函数返回时也一定不会变。

相反地,引用和指针所指对象的常量性则显得很重要了!因为这些对象在函数内部是有可能被改变的。

总而言之,判断一组同名函数能否构成重载,要看能不能通过修改形参来影响实参的值,如果两个函数实参的值都能被修改或都不能被修改,则不能构成重载;否则构成重载。