函数重载
定义重载函数
函数重载也成为函数多态,是C++在C语言的基础上新增的功能。默认参数使得能够使用不同数目的参数调用同一个函数,而函数多态使得能够使用多个同名的函数。
void print(const chat *str, int width); // #1
void print(double d, int width); // #2
void print(long l, int width); // #3
void print(int i, int width); // #4
void print(const chat *str); // #5
print("abav",15); // use #1
print("abaf"); // use #5
prin(1999.0,10); // use #2
print(1999, 12) // use #4
print(1999L, 15); // use #3
// 但是,如果有以下情况
unsigned int year = 3210;
print(year, 6); // ambiguous call 调用失败 不与任何原型匹配
如果没有匹配的原型,编译器将尝试使用标准类型转换强制进行匹配。就是说如果#2原型是唯一的原型,则函数调用print(year, 6)将会把year转换成double类型。但在上面代码中,有三个数字作为第一个参数的原型,因此有三种转换方式,这种情况下编译器拒绝调用,并视为错误。
double cube(double x);
double cube(double &x);
上面所示的代码示例是错误的,此处并没有使用函数重载。虽然他们的特征标量看起来不一样,但实际上都是类似的,只不过一个引用类型,一个非引用类型。从编译器角度来考虑此问题,假设有以下代码
cout << cube(x);
显然,参数x与double x原型和double &x原型都匹配,因此编译器无法分辨使用哪个原型。
所以,为避免混乱,编译器在检查函数特征标时,将把类型引用和类型本身视为同一个特征标。
此外,匹配函数时,要区分const和非const变量。非const值赋给const值这种操作时合法的,反之非法。
long gronk(int n, float m);
double gronk(int n,float m);
不允许以上面的方式重载gronk()。返回类型可以不同,但特征标必须不同。
重载和const形参
如果对const的使用不太熟悉,请先看这一篇文章 C++之const限定符 !!!
顶层const不影响传入函数的对象。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开:
// 重复声明了Record lookup(Phone)
Record lookup(Phone);
Record lookup(const Phone); // 拥有顶层const的形参
// 重复声明了Record lookup(Phone *)
Record lookup(Phone *);
Record lookup(Phone * const); // 拥有顶层const的形参
这两组函数声明中,每一组的第二个声明和第一个声明是等价的。
如果形参是某种类型的指针和引用,则通过区分其指向是常量对象还是非常量对象可以实现函数重载,此时const是底层的:
// 对于接受引用或指针的函数来说,对象是常量还是非常量对应形参不同
Record lookup(Account&); // 用于Account的引用
Record lookup(const Account&); // 新函数 作用于常量引用
Record lookup(Account*); // 新函数 作用于只想Account的指针
Record lookup(const Account*); // 新函数 作用于指向常量的指针
上述例子中,编译器可以通过实参是否是常量来判断应该调用哪个函数。因为const指针不能转换成其他类型,所以只能把const对象传递给const形参。相反,因为非常量可以转换为const,所以上面的4个函数都能作用域非常量对象或者指向非常量对象的指针。
当传递一个非常量对象或者指向非常量对象的指针时,会优先选用非常量版本的函数!
const_cast 和重载
现有如下函数:
// 比较两个string对象的长度,返回较短的那个引用
const string &shorterString(const string &s1, const string &s2){
return s1.size() <= s2.size() ? s1 : s2;
}
函数的参数和返回类型都是const string的引用。可以对两个非常量string实参调用同这个函数,但返回结果仍然是const string的引用(存在隐式转换)。
现在需要一种新的shorterString函数,当它的实参不是常量时,得到的结果是一个普通的引用,使用const_cast可以做到这一点:
// 这个函数重载是被允许的!
string &shorterString(string &s1, string &s2){
auto &r = shorterSting(const_cast<const string&>(s1), const_cast<const string&>(s2));
return const_cast<string>(r);
}
执行流程:首先将它的实参强制转换为成对const的引用,然后调用shorterString函数的版本。const版本返回对const string的引用,可将其转换成一个普通的string&,这显然是安全的!
调用重载的函数
函数匹配是指一个过程,在这个过程中把函数调用与一组重载函数中的某一个关联起来,函数匹配也叫做重载确定。编译器首先将调用的实参与重载集合中每一个函数的形参进行比较,然后根据比较的结果决定到底调用哪个函数。
但是在一些情况下选择函数就比较困难,比如当两个重载函数参数数量相同且参数类型可以相互转换时,转换需要进行决策!
重载与作用域
一般来说将函数声明放置于剧本作用域内并不是一个明智的选择
在C++语言中,名字查找(函数查找)发生在类型检查(参数类型检查)之前!
重载对作用域的一般性质并没有什么改变:如果我们在内层作用域中声明名字,将会隐藏外层作用域中声明的同名实体。在不同的作用域中无法重载函数名:
string read();
void print(const string &);
void print(double); // 重载print函数
void fooBar(int ival)
{
bool read = false; // 新作用域:隐藏了外层的read
string s = read(); // 错误:read是一个布尔值,而非函数
void print(int); // 新作用域:隐藏了之前的print
print("Value: "); // 错误:隐藏了之前的print
print(ival); // 正确:当前print(int)可见
print(3.14); // 正确:调用print(int); print(double)被隐藏了
}
在fooBar内声明的print(int)隐藏了之前两个print函数,因此只有一个print函数是可用的:该函数以int值作为参数!
执行流程:当调用print函数时,编译器首先寻找对该函数名的声明,找到的是接收int值的那个局部声明。一旦在当前作用域中找到了所需的名字,编译器就会忽略掉外层作用域中的同名实体。