C++primer中文版:
重载函数:出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同。切记main
函数不能重载。
1、函数重载和重复声明的区别
如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的:
Record lookup(const Account&);
bool lookup(const Account&); // error: only return type is different
函数不能仅仅基于不同的返回类型而实现重载。
值得注意的是,形参与 const 形参的等价性仅适用于非引用形参。有 const 引用形参的函数与有非 const 引用形参的函数是不同的。类似地,如果函数带有指向 const 类型的指针形参,则与带有指向相同类型的非 const 对象的指针形参的函数不相同。
2、函数匹配与实参转换
函数重载确定,即函数匹配是将函数调用与重载函数集合中的一个函数相关联的过程。通过自动提取函数调用中实际使用的实参与重载集合中各个函数提供的形参做比较,编译器实现该调用与函数的匹配。匹配结果有三种可能:
1.编译器找到与实参最佳匹配的函数,并生成调用该函数的代码。
2.找不到形参与函数调用的实参匹配的函数,在这种情况下,编译器将给出编译错误信息。
3.存在多个与实参匹配的函数,但没有一个是明显的最佳选择。这种情况也是,该调用具有二义性。
大多数情况下,编译器都可以直接明确地判断一个实际的调用是否合法,如果合法,则应该调用哪一个函数。重载集合中的函数通常有不同个数的参数或无关联的参数类型。当多个函数的形参具有可通过隐式转换关联起来的类型,则函数匹配将相当灵活。在这种情况下,需要程序员充分地掌握函数匹配的过程。
3、重载确定的三个步骤
考虑下面的这组函数和函数调用:
void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6); // calls void f(double, double)
3.1 候选函数
函数重载确定的第一步是确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数。候选函数是与被调函数同名的函数,并且在调用点上,它的声明可见。在这个例子中,有四个名为 f 的候选函数。
3.2 选择可行函数
第二步是从候选函数中选择一个或多个函数,它们能够用该调用中指定的实参来调用。因此,选出来的函数称为可行函数。可行函数必须满足两个条件:第一,函数的形参个数与该调用的实参个数相同;第二,每一个实参的类型必须与对应形参的类型匹配,或者可被隐式转换为对应的形参类型。
对于函数调用 f(5.6),可首先排除两个实参个数不匹配的候选函数。没有形参的 f 函数和有两个 int 型形参的 f 函数对于这个函数调用来说都不可行。例中的调用只有一个实参,而这些函数分别带有零个和两个形参。
另一方面,有两个 double 型参数的 f 函数可能是可行的。调用带有默认实参的函数时可忽略这个实参。编译器自动将默认实参的值提供给被忽略的实参。因此,某个调用拥有的实参可能比显式给出的多。
根据实参个数选出潜在的可行函数后,必须检查实参的类型是否与对应的形参类型匹配。与任意函数调用一样,实参必须与它的形参匹配,它们的类型要么精确匹配,要么实参类型能够转换为形参类型。在这个例子中,余下的两个函数都是是可行的。
•f(int) 是一个可行函数,因为通过隐式转换可将函数调用中的 double型实参转换为该函数唯一的 int 型形参。
•f(double, double) 也是一个可行函数,因为该函数为其第二个形参提供了默认实参,而且第一个形参是 double 类型,与实参类型精确匹配。
如果没有找到可行函数,则该调用错误。
3.3 寻找最佳匹配(如果有的话)
函数重载确定的第三步是确定与函数调用中使用的实际参数匹配最佳的可行函数。这个过程考虑函数调用中的每一个实参,选择对应形参与之最匹配的一个或多个可行函数。这里所谓“最佳”的细节将在下一节中解释,其原则是实参类型与形参类型越接近则匹配越佳。因此,实参类型与形参类型之间的精确类型匹配比需要转换的匹配好。
在上述例子中,只需考虑一个 double 类型的显式实参。如果调用 f(int),实参需从 double 型转换为 int 型。而另一个可行函数 f(double, double) 则与该实参精确匹配。由于精确匹配优于需要类型转换的匹配,因此编译器将会把函数调用 f(5.6) 解释为对带有两个 double
形参的 f 函数的调用。
4、含有多个形参的重载确定
如果函数调用使用了两个或两个以上的显式实参,则函数匹配会更加复杂。假设有两样的名为 f 的函数,分析下面的函数调用:
f(42, 2.56);
可行函数将以同样的方式选出。编译器将选出形参个数和类型都与实参匹配的函数。在本例中,可行函数是 f(int, int) 和 f(double, double)。接下来,编译器通过依次检查每一个实参来决定哪个或哪些函数匹配最佳。如果有且仅有一个函数满足下列条件,则匹配成功:
1.其每个实参的匹配都不劣于其他可行函数需要的匹配。
2.至少有一个实参的匹配优于其他可行函数提供的匹配。
如果在检查了所有实参后,仍找不到唯一最佳匹配函数,则该调用错误。编译器将提示该调用具有二义性。
在本例子的调用中,首先分析第一个实参,发现函数 f(int, int) 匹配精确。如果使之与第二个函数匹配,就必须将 int 型实参 42 转换为 double 型的值。通过内置转换的匹配“劣于”精确匹配。所以,如果只考虑这个形参,带有两个int
型形参的函数比带有两个 double 型形参的函数匹配更佳。
但是,当分析第二个实参时,有两个 double 型形参的函数为实参 2.56 提供了精确匹配。而调用两个 int 型形参的 f 函数版本则需要把 2.56 从 double 型转换为 int 型。所以只考虑第二个形参的话,函数 f(double, double) 匹配更佳。
如果在检查了所有实参后,仍找不到唯一最佳匹配函数,则该调用错误。编译器将提示该调用具有二义性。
在本例子的调用中,首先分析第一个实参,发现函数 f(int, int) 匹配精确。如果使之与第二个函数匹配,就必须将 int 型实参 42 转换为 double 型的值。通过内置转换的匹配“劣于”精确匹配。所以,如果只考虑这个形参,带有两个int 型形参的函数比带有两个 double 型形参的函数匹配更佳。
但是,当分析第二个实参时,有两个 double 型形参的函数为实参 2.56 提供了精确匹配。而调用两个 int 型形参的 f 函数版本则需要把 2.56 从 double 型转换为 int 型。所以只考虑第二个形参的话,函数 f(double, double) 匹配更佳。
因此,这个调用有二义性:每个可行函数都对函数调用的一个实参实现更好的匹配。编译器将产生错误。解决这样的二义性,可通过显式的强制类型转换强制函数匹配:
f(static_cast<double>(42), 2.56); // calls f(double, double)
f(42, static_cast<int>(2.56)); // calls f(int, int)
在实际应用中,调用重载函数时应尽量避免对实参做强制类型转换:需要使用强制类型转换意味着所设计的形
参集合不合理。
5、实参类型转换
为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:
1.精确匹配。实参与形参类型相同。
2.通过类型提升实现的匹配。
3.通过标准转换实现的匹配。
4.通过类类型转换实现的匹配。
内置类型的提升和转换可能会使函数匹配产生意想不到的结果。但幸运的是,设计良好的系统很少会包含与下面例子类似的形参类型如此接近的函数。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
(转载)含有const形参函数的重载:
record lookup( phone);
record lookup(const phone);
这两个属于函数重载吗?
这个还需要从实参与形参之间的关系中去寻找答案,这对的区别仅仅在于是否将形参定义为const,这种差异并不影响传递到函数的对象,因此第二个函数声明被视为第一个的重复声明。
起因:实参传递方式。复制实参的副本给形参时并不考虑形参是否为const,因为函数操纵的只是副本。函数无法修改实参。因此,既可将const对象传递给const形参,也可传递给非const形参,这两种形参并无本质区别。
例如:
record lookup(15001805405);//这里就假设传递个本人的手机号码吧
此时该调用上面哪个函数呢?很明显两个都可以,形参跟实参都有属于各自的爹妈,各自的空间,互不干涉。
然而这个时候编译器就会傻眼,二义性问题,编译器就成了sb了,因此结论:这组函数不属于函数重载。
ps:形参与const形参的等价性仅仅适用于非引用形参。有const引用形参的函数与有非const引用形参的函数是不同的。近似的,如果函数带有指向const类型的指针形参,则与带有指向相同类型的非const对象的指针形参的函数不形同。
record lookup(const phone&);
record lookup(phone&);
那这两个呢?属于函数重载吗?
答案是属于。肯定属于。
例如:
const phone a=15001805405;//贡献第二次
record lookup(a);这个该调用那个呢?
很明显大家都晓得,上面那个。理由就不需要唠叨了吧。
有人会问这个问题:
phone a=15001805405;
那这个该调用那个呢?
下面那个?上面那个?
结论:这种情况优先匹配非const那个,如果没有那也可以匹配const这个。
有依据没? 有的,经试验测试得出的结论:
试验如下:
#include<iostream>
using namespace std;
int sb(const int& b)
{
cout<<"今天晚上我让她反感了!这是我的错!"<<endl;
return b;
}
int sb(int &b)
{
cout<<"不过我认为第一次嘛,可以原谅吧,做人好难,也好累!"<<endl;
return b;
}
int main()
{
int a=2;
cout<<sb(a)<<endl;
cout<<"有些事想明白了就是幸福,想不明白就是痛苦!"<<endl;
return EXIT_SUCCESS;
}
-------------------------------------------------------------------------------------------------------------------------------------
《C++ primer》中提到“仅当形参是引用或指针的时候,形参是否为const才对重载有影响。”
int add(int a, int b);
int add(const int a, const int b);
我想通过定义这两个函数来实现实参是否为const的重载,可事与愿违,这里的第二个函数并没有对第一个进行overloading,而是redefinition。因为,在此的两个函数的形参并不会直接关联到实参,在调用这两个函数的时候,形参都只是实参的一个副本,不管add函数内部对形参做什么处理,都不会影响到实参,也就是说——第二个函数形参中的const没有任何的用处,只是多此一举罢了。所以在此的第二个定义只是对第一个的重定义罢了。
int add(int &a, int &b);
int add(const int &a, const int &b);
这次定义的两个函数与上面不同的地方就是形参使用了引用。这个时候编译器就完全可以根据实参是否为const确定调用哪一个函数了。调用如下:
//非const变量x, y
int x = 1;
int y = 2;
add(x, y); //call add(int &a, int &b)
//const变量x, y
const int x = 1;
const int y = 2;
add(x, y); //call add(const int &a, const int &b)
上述第一种情况:实参为非const对象的时候,其实两个函数都可以被调用,都与之匹配,因为非const对象不但可以初始化非const引用,也可以初始化const引用。但由于非const对象初始化const引用的时候涉及到类型转换,所以此时带非const引用形参的函数为最佳匹配。
上述第二种情况:实参为const对象的时候,就不能将此对象传递给带非const引用的形参的函数了,因为const对象只能用来初始化const引用。
int add(int *a, int *b);
int add(const int *a, const int *b);
//非const对象
int x = 1;
int y = 2;
//cosnt对象
const int r = 1;
const int s = 2;
add(&x, &y); //call add(int *a, int *b);
add(&r, &s); //call add(cosnt int *a, cosnt int *b);