C++函数重载

一.基本概念

函数重载即函数名相同,函数形参列表不同(函数特征标不同)的一类函数称为函数重载。注意函数重载的依据只有形参列表不同。函数的返回类型不同不可以作为函数重载的依据。
如果两个函数的参数数目和类型相同,那么这两个函数的函数特征标就相同,不能重载。
如下举例一组重载函数:
void fun(int a);
void fun(int a,int b);
void fun(double a,int b);
void fun(double a,double b);
void fun(const char* str);
void funn(char* str);
注意:函数的返回类型和重载没有任何关系,即两个重载函数的函数返回类型可以不同,也可以相同。

二.函数重载遇上引用参数

1.类型引用和类型本身被看作同一个函数特征标

当函数重载遇上引用参数的时候,我们就需要特别注意一下。
如:
void fun(int x);
void fun(int& x);
形参列表不同,看起来可以函数重载,但是当我们调用的时候,编译器看不出来不同。比如我们调用函数:
int a = 10;
fun(a);
这个时候编译器就不知道该调用哪一个函数,因为参数a和两个fun函数形参int x和int& x都匹配,所以编译器这个时候不知道该选用哪一个函数,就会出错。
编译器在检查函数特征的时候,会把类型引用和类型本身看成是同一个函数特征标。

2.重载引用参数

void fun(int& a);//①
void fun(const int& a);//②
void fun(int&& a);//③
①与可修改的左值参数匹配
②与可修改的左值参数匹配、与const左值参数匹配、与右值参数匹配
③与右值参数匹配
我们发现,这些函数的参数匹配有相同的情况,即能与①和③形参匹配的实参都能与②的形参匹配。那么这种情况下怎么办呢?会因为产生二义性,二编译运行错误?答案并非如此。我们看以下代码运行情况:

void fun(int& a)
{
	cout << "this is fun(int& a)" << endl;
}
void fun(const int& a)
{
	cout << "this is fun(const int& a)" << endl;
}
void fun(int&& a)
{
	cout << "this is fun(int&& a)" << endl;
}

int main()
{
	int a = 10;
	const int b = 10;
	fun(a);
	fun(b);
	fun(a + b);
	return 0;
}

运行结果:
在这里插入图片描述
表面上会产生二义性,但是实际上都可以找出最匹配的函数,编译器也是,编译器会根据调用函数传入的实参来找到最匹配的那个函数。这里需要注意一下(a+b)是表达式,是相加后的结果,是右值。
a是int类型,那么fun(int& a)就与之最匹配。
b是const int类型,那么fun(const int&)就与之最匹配。
(a+b)是表达式,是个右值,那么fun(int&& a)就与之最匹配。
如果只有fun(const int& a)这一个函数的话,那么编译器就会调用这一个函数,看以下代码及运行情况:

//void fun(int& a)
//{
//	cout << "this is fun(int& a)" << endl;
//}
void fun(const int& a)
{
	cout << "this is fun(const int& a)" << endl;
}
//void fun(int&& a)
//{
//	cout << "this is fun(int&& a)" << endl;
//}

int main()
{
	int a = 10;
	const int b = 10;
	fun(a);
	fun(b);
	fun(a + b);
	return 0;
}

在这里插入图片描述
我们发现编译器调用的是仅剩的fun(const int& a)函数。这个时候传入的实参都能和这个函数的形参匹配,而且不存在其他更匹配的函数,那么编译器自然而然就调用这个函数。
但是我们就剩下fun(int& a)这个函数是肯定不行的,因为我们都知道,const int类型的值是不能以左值引用形式传给int&的。也就是编译器调用函数的最低标准是传入的实参得和函数的形参匹配,如果可以辨别传入的实参和哪个函数的形参更匹配,那么编译器不会产生二义性,而是会选择最匹配的那个函数调用。

三.函数重载剩余问题

1.用函数重载的时候,调用函数必须使用正确的参数类型。

比如说:
void fun(int a);
void fun(double a);
void fun(long a);
这三个函数构成函数重载。

void fun(int a)
{
	cout << "this is fun(int&& a)" << endl;
}
void fun(double a)
{
	cout << "this is fun(int&& a)" << endl;
}
void fun(long a)
{
	cout << "this is fun(int&& a)" << endl;
}

int main()
{
	unsigned int a = 10;
	fun(a);
	return 0;
}

我们用fun(a)调用函数的时候,发现没有函数真正意义上和传入的实参类型(unsigned int)匹配,但是这并不能阻止编译器,C++会尝试使用标准类型转换进行强制匹配。如果上面三个重载函数只存在一个函数,那么编译器会将unsigned int类型强制转换成与这个函数形参相匹配的类型,但是有三个重载函数,编译器会发现三个重载函数都可以将实参进行类型转换与之匹配,所以编译器会报错。报错原因是:有多个重载函数与之匹配。而不会报成没有重载函数与之匹配。

2.注意float和int

void fun(int a)
{
	cout << "this is fun(int a)" << endl;
}
void fun(float a)
{
	cout << "this is fun(float a)" << endl;
}

int main()
{
	fun(10);
	//fun(10.0);//error
	return 0;
}

注意fun(10)会调用函数fun(int a),而fun(10.0)不会顺利调用fun(float a)。
这是因为10就是明显的整形,直接调用fun(int a)。但是10.0在编译器这里是double类型,所以fun(10.0)涉及实参类型转换,达到和函数形参强制匹配的目的。编译器发现转换成int或者float都可以,所以就会报错,报错内容是:有多个函数可以重载。
但是我们float b = 10.0,fun(b)这样就不涉及类型转换了,因为b是float类型,编译器直接调用函数fun(float)。

3.编译器选择使用哪个函数版本

1.类型转换的优先性

对于函数重载,C++有一个定义良好的策略来决定为函数调用选用哪一个函数定义,尤其是有多个参数时,这个过程称为重载解析
第一步:创建候选函数列表。(包含与被调用函数的名称相同的函数和模板函数)。
第二步:使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式转换序列,比如我们传的实参是float类型时,函数调用可以将该参数转换为double类型从而与double类型的形参匹配,而模板可以为float生成一个实列。
第三步:确定是否有最佳的可行函数,如果有,则使用,否则,函数调用出错。
如果调用重载函数的时候传入的实参没有完全匹配的形参,那么那么编译器会进行类型转换,如果有多个可行函数,那么必须有最佳的选择,否则编译器将会编译出错。它查看为使函数调用参数(即传入的实参)与可行的候选函数的参数(即函数形参)匹配所需要进行的转换,通常,从最佳到最差的顺序如下:
1.完全匹配(即传入的实参与函数的形参类型完全匹配),但常规函数优先于模板。
2.提升转化(如char、short、自动转换为int,float自动转换为double)。
3.标准转换(如int转换为char,long转换为double)。
4.用户定义的转换。(如类声明中定义的转换)
在这里我们对2和3进行举例说明:

void fun(int a)
{
	cout << "this is fun(int a)" << endl;
}

void fun(char a)
{
	cout << "this is fun(char a)" << endl;
}

int main()
{
	short a = 10;
	fun(a);

	return 0;
}

short类型的实参可以被转换为int类型或者char类型,与可行函数匹配,但是转换为哪一个呢?short转换为int类型为提升转化,short转换为char类型是标准转换,提升转换优先于标注转换,所以在这里调用的是第一个fun函数。运行结果:
在这里插入图片描述
如果没有提升转换的话(即没有第一个fun函数的话),那么毫无疑问,调用将会用fun(char a)。再举例:

void fun(short a)
{
	cout << "this is fun(int a)" << endl;
}

void fun(char a)
{
	cout << "this is fun(char a)" << endl;
}

int main()
{
	int a = 10;
	fun(a);

	return 0;
}

int到short或者char都是标准转换,也就是说这两个转换是同等级的,没有哪个转换最好这一说,所以编译器会报错:有多个函数重载可供使用。

2.最佳匹配选择

如果调用重载函数传入的实参有多个可行函数完全匹配,编译器会在两种情况下有个最佳匹配选择,从而完成重载解析:
(1)非const的指针和引用优先与非const的指针和引用匹配配。(注意:是指针和引用)
(2)其中一个是非模板函数,而另一个不是。这种情况下,非模板函数优先于模板函数。(模板函数和显示具体化在另一篇博客中提出)。
对于(1)我们举例说明:

void fun(int* a)//非const的指针
{
	cout << "this is fun(int* a)" << endl;
}
void fun(const int* a)
{
	cout << "this is fun(const int* a)" << endl;
}

void fun(int& a)//非const的引用
{
	cout << "this is fun(int& a)" << endl;
}

void fun(const int& a)
{
	cout << "this is fun(const int& a)" << endl;
}

int main()
{
	int* p = new int(2);//非const的指针
	fun(p);
	int a = 10;
	fun(a);//在这里旨在传非const的引用

	return 0;
}

运行结果:
在这里插入图片描述
这就是我们所说的传入的实参是非const的指针和引用的话,那么优先与形参是非const的指针和引用的函数匹配。如果传入的实参是const类型的指针和引用的话,毫无疑问,在这里是只能(无可选择的意思)跟形参是const类型的指针和引用的函数匹配。
我们刚才强调了,非const的指针和引用才有这种最佳选择情况。我们看下面这段代码:

void fun(int a)//非const的普通变量
{
	cout << "this is fun(int a)" << endl;
}
void fun(const int a)//const的普通变量
{
	cout << "this is fun(const int a)" << endl;
}


int main()
{
	int a = 10;
	fun(a);//传入非const的变量

	return 0;
}

编译运行结果:
在这里插入图片描述
这是因为“const+普通类型”与“普通类型”这俩参数在函数重载中不能作为参数区别来进行函数重载。
包括上面函数,我们const int a;fun(a)这样也不行。
所以要注意:非const的指针和引用才有这种最佳选择情况。
非const的普通类型与const的普通类型不能作为函数重载形参条件。
对于(2)在这里举例说明(模板函数主要内容在另一篇博客上记录):

void fun(int* a)//非const的普通变量
{
	cout << "this is fun(int a)" << endl;
}
void fun(const int* a)//const的普通变量
{
	cout << "this is fun(const int* a)" << endl;
}

template<class Type>
void fun(Type a)
{
	cout << "this is template<>fun(Type a)" << endl;
}

int main()
{
	const int* p = new int(2);
	fun(p);//传入const类型的指针变量

	return 0;
}

首先传入的是const int*类型的实参,第一个fun()函数肯定不能匹配,第二个fun()函数和第三个的模板函数可以完全匹配。如果非模板函数与模板函数都完全匹配,这时候编译器将会选择非模板函数。运行结果:
在这里插入图片描述
当然,如果我们把非模板函数去掉的话,编译器肯定会选择非模板函数,因为非模板函数也完全匹配:

void fun(int* a)//非const的普通变量
{
	cout << "this is fun(int a)" << endl;
}
//void fun(const int* a)//const的普通变量
//{
//	cout << "this is fun(const int* a)" << endl;
//}

template<class Type>
void fun(Type a)
{
	cout << "this is template<>fun(Type a)" << endl;
}

int main()
{
	const int* p = new int(2);
	fun(p);//传入const类型的指针变量

	return 0;
}

运行结果:
在这里插入图片描述
注意这里Type代表const int*,也就是模板函数生成了一个fun(const int*)函数实体。
所以注意:在完全匹配的函数有模板函数和非模板函数的时候,非模板函数优先于模板函数。

4.何时使用函数重载

一般情况下,仅当函数执行相同的任务,但需要使用不同类型的数据时(即函数接收的数据类型不同),我们就用函数重载。

C++任何跟踪重载函数?有一个名词叫做名称修饰。编译器会根据函数名和每一个形参类型对一个函数名进行修饰。注意修饰的时候,是根据函数名和形参类型为根据的,这也是为什么我们不能以函数返回值类型作为函数重载依据的原因。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孟小胖_H

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值