关于C++中函数重载是在C语言基础上的一大特色,不过有好也有坏,虽然C++的函数重载大大方便了编程人员,但是却有时候使用不当会引起问题,最典型的就是函数重载的二义性问题。首先我们知道C++函数重载的条件,以及C++中为什么可以函数重载,这样才可以避免C++函数重载中的二义性问题。
C++函数重载的条件有三个:
(1)函数必须位于同一作用域之中。(重载顾名思义是地位相同的两个函数,可以说两个函数是平等的,所以凭什么有的函数作用域大呢,那自然就是同一作用域)
(2)函数名必须相同。
(3)最重要的就是参数列表不同。(参数列表不同又可以分为1--参数个数不同 2--参数类型不同 3--参数顺序不同,满足以上三条任意一条就可以了。)
还有一点要说的是函数的返回值可以相同,也可以不同。
下面就来说说为什么C++中可以进行函数的重载,我们都知道,如果在C语言中定义一个或者多个同名的函数,哪怕是参数的类型、个数、顺序都不同可不可以进行重载。
如:以下代码用C++编译没有任何问题
int My_add(int a, int b)
{
return (a + b);
}
int My_add(char a, char b)
{
return (a + b);
}
int main()
{
int m, n, ret = 0;
m = 10;
n = 20;
ret = My_add(m, n);
return 0;
}
但是我们用C编译器编译会报错:
extern "C"
{
int My_add(int a, int b)
{
return (a + b);
}
int My_add(char a, char b)
{
return (a + b);
}
int main()
{
int m, n, ret = 0;
m = 10;
n = 20;
ret = My_add(m, n);
return 0;
}
}
1>d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(52): error C2733: “My_add”: 不允许重载函数的第二个 C 链接
1> d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(47) : 参见“My_add”的声明
顺便提一下用extern “C” (C是大写)表面该范围内采用C风格编译。C语言内引起了重定义但是C++却没有这又是为什么呢?
主要就是C与C++编译器在修饰函数名的规则有不同之处,修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。
调用约定C++中比C语言中多一个__thiscall,其余均相同。
(关于调用约定可以参考http://blog.csdn.net/loving_forever_/article/details/51472040)
回到我们的问题,那么C语言中的函数被修饰成什么了呢?还是刚刚的代码,让我们转到反汇编看一看:
01051ABB call _My_add (010511E0h)
01051AC0 add esp,8
在C语言中My_add()被修饰成了_My_add(),这通常是C语言中的默认调用约定(__cdecl)产生的。
但是C++就不是那么简单了,C++的函数名修饰规则有些复杂,但是信息更充分,通过分析修饰名不仅能够知道函数的调用方式,返回值类型,参数个数甚至参数类型。不管__cdecl,__fastcall还是__stdcall调用方式,函数修饰都是以一个“?”开始,后面紧跟函数的名字,再后面是参数表的开始标识和按照参数类型代号拼出的参数表。对于__stdcall方式,参数表的开始标识是“@@YG”,对于__cdecl方式则是“@@YA”,对于__fastcall方式则是“@@YI”
怎么看被修饰成什么了呢?我们不给函数的主体,只给定义,看看下面的报错就知道了。
1>Main.obj : error LNK2019: 无法解析的外部符号 "int __cdecl My_add(int,int)" (?My_add@@YAHHH@Z),该符号在函数 _main 中被引用
HHH分别是返回值,参数类型。参数表的拼写代号如下所示:X--void
D--char
E--unsigned char
F--short
H--int
I--unsigned int
J--long
K--unsigned long(DWORD)
M--float
N--double
_N--bool
U--struct
也正是因为C++与C语言修饰函数风格的不同,C++更加复杂,所以C++编译器可以识别函数名相同但参数列表不同的原因。说到这里又有一个新问题产生了,既然函数的返回值也修饰在其中那为什么不能通过返回值的不同来重载函数呢?因为如果返回值也被当作可以重载的条件的话,那么函数重载的二义性就太多了?(什么是二义性下面会提到)来看一个例子:
假设你有两个返回值不同的函数,比如
int getvalue(viod) {return value1;}
float getvalue(viod) {return value;}
那么当你去调用他们的时候,由于你调用的时候
写的是
getvalue();
于是你的编译器就无法知道 你调用的是上面哪个 函数(因为两个函数都不用传参数,编译器无法区分它们,产生二义性), 所以就会报错。
又有人可能会说既然函数修饰过程返回值也在其中,那么编译器应该可以区分啊,那么我告诉你的是这是微软的编译器,只能说我们用的VS是这样编译的,但是不代表其他C++编译器也是这样编译的(比如Linux中的gcc编译器),所以C++标准就这样规定了。
下面还剩下最后一个问题就是函数重载的二义性。
先来看看什么是二义性:二义性说简单点就是编译器不知道你需要调用哪个函数。
如下面两个函数:
int My_add(int a, int b)
{
return (a + b);
}
char My_add(int a, int b)
{
return (a + b);
}
编译器报错如下:
1>d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(54): error C2556: “char My_add(int,int)”: 重载函数与“int My_add(int,int)”只是在返回类型上不同
1> d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(48) : 参见“My_add”的声明
再如:
int fun()
{
return 0;
}
int fun(int a = 5)
{
return a;
}
int main()
{
int ret = 0;
ret = fun();
return 0;
}
首先编译期间就会指出错误:
1>d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(72): error C2668: “fun”: 对重载函数的调用不明确
这也是一种产生二义性的例子。
再来看一种:
int fun(int a)
{
return 0;
}
int fun(int &b)
{
b = 20;
return b;
}
int main()
{
int m, ret = 0;
m = 10;
ret = fun(m);
return 0;
}
同样也会产生二义性,报错如下:1>d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(73): error C2668: “fun”: 对重载函数的调用不明确
1> d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(62): 可能是“int fun(int &)”
1> d:\myproject\visual studio 2013\projects\myfirstc++\main.cpp(58): 或 “int fun(int)”
总结一下就是以下三个原因会产生二义性:
1、形参个数一致,仅仅是形参名或者返回值不同
2、重载函数有一个形参有默认参数时
3、重载函数形参在同位置分别类型为传值或者传引用