函数重载:
出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数(overloaded function)。一定要注意函数重载的两个关键词:形参列表和作用域。
任何程序有且仅有一个main函数的实例,main函数不能重载。
对于函数重载来说,它们应该在形参数量和形参类型上有所不同。
下面论述形参列表和作用域对函数重载的影响。
函数重载与形参列表
函数重载和函数声明的区别:
如果两个函数声明的返回类型和形参表完全匹配,则将第二个声明视为第一个的重复声明。
如果两个函数的形参列表相同(参数个数和类型)相同但是返回类型不同,那么第二个函数的声明将会出现编译错误。
函数不能仅仅基于不同的返回类型而实现重载。
基于const形参的重载:
当参数是非引用形参时,形参与const形参的两个同名函数不能构成函数重载。下面的第二个函数只是第一个函数的重复声明。
1 A func(B);
2 A func(const B); // 重复声明
仅当形参是引用或指针是,形参是否为const才有影响。
A func(B&);
A func(const B&) //基于const引用形参的重载
A func(B*);
A func(const B*); //基于const指针形参的重载
可基于函数的引用形参是指向const对象还是指向非const对象,实现函数重载。将引用形参定义为const来重载函数是合法的,因为编译器可以根据实参是否为const
确定调用哪一个函数。
如果实参为const对象,那么将调用const引用形参的版本。如果实参为非const对象,非const对象既可以用于初始化const引用,也可以用于初始化非const引用。但是将const引用初始化为非const对象,需要转换,因为非const形参的初始化则是精确匹配。
对于指针形参也是如出一辙。如果实参是const对象,则调用带有const*类型形参的函数。否则,如果实参不是const对象,将调用带有普通指针形参的对象。
不能基于指针本身是否为const来实现函数重载。
1 A func(B*);
2 A func(B *const); //重复声明,不是重载
在上述两种情况,都复制了指针,指针本身为const并没有带来区别。
重载与作用域:
1 string read(); 2 void print(const string &); 3 void print(double); //重载print函数 4 void fooBar(int ival) { 5 bool read = false; //新作用域:隐藏了外层的read,函数名和变量名都是符号。可以相互屏蔽 6 string s = read(); //错误:read是一个布尔值,而非函数 7 8 //不好的习惯:通常来说,在局部作用域中声明函数不是一个好的选择 9 void print(int); //新作用域:隐藏了之前的print 10 print("Value:"); //错误print(const string&)被隐藏了 11 print(ival); //正确:当前print(int)可见 12 print(3.14); //正确:调用print(int); print(double)被隐藏了 13 }
在函数中声明的名字将屏蔽在全局作用域内声明的同名名字。这个关于变量名字的性质对于函数名同样成立。一般的作用域规则同样适用于重载函数名。如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应在同一个作用域中声明。在局部作用域中,编译器首先寻找被调用函数的名字。一旦知道到该名字,编译器就不再继续检查这个名字是否咋外层作用域中存在,编译器将认同找到的这个声明即是程序需要调用的函数,余下的工作只是检查名字的使用是否有效。
当编译器处理调用read的请求时找到的是定义在局部作用域中的read。这个名字是布尔变量,而我们显然无法调用一个布尔值,因此该语句非法。
当调用print函数的过程类似。在fooBar内声明的print(int)隐藏了之前两个print函数,因此只有一个print函数是可用的:该函数以int值作为参数
当我们调用print函数时,编译器首先寻找对该函数名的声明,找到的是接受int值那个局部声明。一旦在当前作用域中找到了所需的名字。编译器就会忽略掉外层作用域的同名实体。
假设我们把print(int)和其他print函数声明放在同一个作用域中,则它将称为另外一种形式重载。此时编译器将能看到所有的三个函数,上述调用结果的处理将会不同:
void print(const string &); void print(double); //print函数的重载形式 void print(int); //print函数的另一种重载形式 void fooBar2(int ival) { print("Value: "); //调用print(const string&) print(ival); //调用print(int) print(3.14); //调用print(double) }
函数匹配与实参转换:
函数重载确定(overloaded resolution, 即函数匹配 function matching) 是将函数调用与重载函数集合中的一个函数相关联的一个过程。
重载函数的三个步骤:
1. 候选函数
函数重载确定的第一是确定该调用所考虑的重载函数集合,该集合中的函数为候选函数(candidate function)。候选函数是与被调用的函数同名的函数,并且在调用点上,它的声明可见。
2. 选择可行函数
第二步是从候选函数选一个或多个函数,它们能够用调用中指定的实参来调用。因此,选出来的函数称为可行函数(viable function)。可行函数必须满足条件:第一,函数的形参个数与该调用的实参个数相同;第二,每个实参的类型必须与对应的形参的类型匹配,或者可以被隐式转换为对应的形参类型。
如果没有找到可行函数,则编译器将报告无匹配函数的错误。
3.寻找最佳匹配
函数重载确定的第三部就是确定与函数调用使用的市价参数匹配最佳的可行函数。这个过程考虑函数调用中的每一个实参,选择对应形参与之最匹配的一个或多个可行函数。这个所谓的“最佳”的原则是实参类型与形参类型越接近则越匹配。因此,实参类型与形参类型之间的精确类型匹配比需要转换的匹配好。
成员函数可被重载:
与非成员函数一样,成员函数也可以被重载。
成员函数只能重载本类的其他成员函数。类的成员函数与普通的非成员函数以及在其他类中声明的函数不相关,也不能重载它们。重载的成员函数和普通函数应用相同的规则:两个重载成员的形参数量和类型不能完全相同。
类中成员函数的重载:
成员函数的重载:
类层次中的同名函数来说,有3种关系:重载(overload),覆盖(override)和隐藏(hide)。
重载的概念相对简单,只有在同一类中的同名成员函数才存在重载关系,主要特点是函数的参数类型和数目有所不同,但不能出现函数参数的个数和类型均相同,仅仅依靠返回值类型不同来区分的函数,这和普通函数的重载是完全一致。另外,重载和成员函数是否是虚函数无关,举例来说:
class { ... virtual int fun(); void fun(int); void fun(double, double); static int fun(char); ... };
上述A类定义中的4个fun函数便是重载关系:
1)相同的范围(在同一个类),父类子类的同名函数是其他关系(覆盖、隐藏)
2)相同的函数名字;
3)不同的参数列表;
4)virtual 关键之可有可无
基于const的重载:
基于成员函数是否为const,可以重载一个成员函数:同样地,基于第一个指针形参那是否指向const,可以重载一个函数。const对象只能使用const成员。非const对象可以使用任意成员,但是非const版本是一个更好的匹配。
1 class A { 2 public: 3 int Func1(int) const; 4 int Func1(int); //基于const的重载 5 6 //返回类型不同也是可以的 7 int Func2() const; 8 double Func2(); //正确,可以运行。 9 };
这篇文章:http://www.cnblogs.com/skynet/archive/2010/09/05/1818636.html较好地讲解了函数重载的底层实现
参考资料:
1. 《C++ Primer》(第五版) 电子工业出版社