C++:函数重载详解


函数重载

自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”

函数重载概念

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数类型顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。

int Add(int left, int right)
{
	return left+right;
}
double Add(double left, double right)
{
	return left+right;
}
long Add(long left, long right)
{
	return left+right;
}

int main() {
	Add(10, 20);
	Add(10.0, 20.0);
	Add(10L, 20L);
	
	return 0; 
}

下面两个函数属于函数重载吗?

short Add(short left, short right)
{
	return left+right;
}
int Add(short left, short right)
{
	return left+right;
}

不属于!!!
C++ 允许两个重名函数同时存在,但他们的参数列表不能完全相同,哪怕返回值类型不同也不可以。

名字修饰(name Mangling)

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。

Name Mangling是一种在编译过程中,将函数、变量的名称重新改编的机制,简单来说就是编译器为了区分各个函数,将函数通过某种算法,重新修饰为一个全局唯一的名称。

C语言名字修饰规则非常简单,只是在函数名字前面添加了下划线。比如,对于以下代码,在最后链接时就会出错:

int Add(int left, int right);

int main() {
	Add(1, 2);
	return 0; 
}

在vs2019下,对上述代码进行编译链接,最后编译器报错:
在这里插入图片描述
上述Add函数只给了声明没有给定义,因此在链接时就会报错,提示:在main函数中引用的Add函数找不到函数体。从报错结果中可以看到,C语言只是简单的在函数名前添加下划线。因此当工程中存在相同函数名的函数时,就会产生冲突。

由于C++要支持函数重载,命名空间等,使得其修饰规则比较复杂,不同编译器在底层的实现方式可能都有差异。

扫描二维码关注公众号,回复: 9657450 查看本文章
int Add(int left, int right);
double Add(double left, double right);

int main() {
    Add(1, 2);
    Add(1.0, 2.0);
    return 0;
}

在vs2019下,对上述代码进行编译链接,最后编译器报错:
在这里插入图片描述
通过上述错误可以看出,编译器实际在底层使用的不是Add名字,而是被重新修饰过的一个比较复杂的名字, 被重新修饰后的名字包含了:函数的名字以及参数类型。这就是为什么函数重载中几个同名函数要求其参数列表不同的原因。只要参数列表不同,编译器在编译时通过对函数名字进行重新修饰,将参数类型包含在最终的名字中,就可保证名字在底层的全局唯一性

在C++中:
在这里插入图片描述

总结:

  • 在C语言中,名字修饰只是在函数名前加下划线,所以只要函数名相同,就会导致冲突。
  • 在C++中,名字修饰是?函数名@域名1@域名2。。。@@参数列表@z“的格式构成的,包含:
    A、函数名
    B、所在区域
    C、参数列表
    所以在C++中,以上三个必须完全相同,才会出现冲突这就是函数重载的原理。

C++中函数重载底层是怎么处理的?

编译器调用重载函数的准则:

  1. 将所有同名函数作为候选者
  2. 尝试寻找可行的候选函数
  3. 精确匹配实参
  4. 通过默认参数能够匹配实参
  5. 通过默认类型转换匹配实参
  6. 匹配失败
  7. 最终寻找到的可行候选函数不唯一,则出现二义性,编译失败
  8. 无法匹配所有候选者,函数未定义,编译失败。

重载函数的调用匹配

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

  • 精确匹配:参数匹配而不做转换,或者只是做微不足道的转换,如数组名到指针、函数名到指向函数的指针、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<int *>(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))?
}

编译器是如何解析重载函数调用的?

在这里插入图片描述

  1. 由匹配文法中的函数调用,获取函数名;
  2. 获得函数各参数表达式类型;
  3. 语法分析器查找重载函数,符号表内部经过重载解析返回最佳的函数
  4. 语法分析器创建抽象语法树,将符号表中存储的最佳函数绑定到抽象语法树上

重载函数解析大致可以分为三步:

  • 根据函数名确定候选函数集
  • 从候选函数集中选择可用函数集合
  • 从可用函数集中确定最佳函数,或由于模凌两可返回错误

extern “C”

使用extern “C”修饰一个语句或者将一段代码包起来那么这段代码会以C语言的风格进行编译

extern "C" {
	int Add(int left, int right);

	int main() {
		Add(1, 2);
		return 0;
	}
}

在vs2019下,对上述代码进行编译链接,最后编译器报错:
在这里插入图片描述


如有不同见解,欢迎留言讨论!

发布了96 篇原创文章 · 获赞 37 · 访问量 5156

猜你喜欢

转载自blog.csdn.net/AngelDg/article/details/104716324