出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。
可以定义一组函数,它们执行同样的一般性动作,但是应用在不同的形参类型上,调用这些函数时,无需担心调用的是哪个函数。
通过省去为函数起名并记住函数名字的麻烦,函数重载简化了程序的实现,使程序更容易理解。
任何程序都仅有一个 main 函数的实例。main 函数不能重载。
函数重载和重复声明的区别如果两个函数声明的返回类型和形参表完全匹配, 则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则也是重复声明,函数不能仅仅基于不同的返回类型而实现重载:
Record lookup(const Account&);
bool lookup(const Account&); // error: only return type is different
有些看起来不相同的形参表本质上是相同的:
// each pair declares the same function
Record lookup(const Account &acct);
Record lookup(const Account&); // parameter names are ignored
typedef Phone Telno;
Record lookup(const Phone&);
Record lookup(const Telno&); // Telno and Phone are the same type
Record lookup(const Phone&, const Name&);
// default argument doesn't change the number of parameters
Record lookup(const Phone&, const Name& = "");
// const is irrelevent for nonreference parameters
Record lookup(Phone);
Record lookup(const Phone); // redeclaration
在第一对函数声明中,第一个声明给它的形参命了名。形参名只是帮助文档,并没有修改形参表。
在第二对函数声明中,看似形参类型不同,但注意到 Telno 其实并不是新类型,只是 Phone 类型的同义词。typedef 给已存在的数据类型提供别名,但并没有创建新的数据类型。所以,如果两个形参的差别只是一个使用 typedef 定义的类型名,而另一个使用 typedef 对应的原类型名,则这两个形参并无不同。
在第三对中,形参列表只有默认实参不同。默认实参并没有改变形参的个数。无论实参是由用户还是由编译器提供的,这个函数都带有两个实参。
最后一对的区别仅在于是否将形参定义为 const。这种差异并不影响传递至函数的对象; 第二个函数声明被视为第一个的重复声明。其原因在于实参传递的方式。复制形参时并不考虑形参是否为 const——函数操纵的只是副本。函数的无法修改实参。 结果, 既可将 const 对象传递给 const 形参, 也可传递给非 const 形参,这两种形参并无本质区别。
值得注意的是,形参与 const 形参的等价性仅适用于非引用形参。有 const 引用形参的函数与有非 const 引用形参的函数是不同的。类似地,如果函数带有指向 const 类型的指针形参, 则与带有指向相同类型的非 const 对象的指针形参的函数不相同。
重载与在函数中局部声明的名字将屏蔽一般的作用域规则同样适用于重载函数名。如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应在同一个作用域中声明。
一般来说,局部地声明函数是一种不明智的选择。函数的声明应放在头文件中。
作为例子,考虑下面的程序:
void print(const string &);
void print(double); // overloads the print function
void fooBar(int ival)
{
void print(int); // new scope: hides previous instances of
print
print("Value: "); // error: print(const string &) is hidden
print(ival); // ok: print(int) is visible
print(3.14); // ok: calls print(int); print(double) is hidden
}
函数 fooBar 中的 print(int) 声明将屏蔽 print 的其他声明,就像只有一个有效的 print 函数一样:该函数仅带有一个 int 型形参。在这个作用域或嵌套在这个作用域里的其他作用域中,名字 print 的任何使用都将解释为这个print 函数实例。
调用 print 时,编译器首先检索这个名字的声明,找到只有一个 int 型形参的print 函数的局部声明。一旦找到这个名字,编译器将不再继续检查这个名字是否在外层作用域中存在, 即编译器将认同找到的这个声明即是程序需要调用的函数,余下的工作只是检查该名字的使用是否有效。
另一种情况是,在与其他 print 函数相同的作用域中声明 print(int),这样,它就成为 print 函数的另一个重载版本。此时,所有的调用将以不同的方式解释:
void print(const string &);
void print(double); // overloads print function
void print(int); // another overloaded instance
void fooBar2(int ival)
{
print("Value: "); // ok: calls print(const string &)
print(ival); // ok: print(int)
print(3.14); // ok: calls print (double)
}
现在,编译器在检索名字 print 时,将找到这个名字的三个函数。每一个调用都将选择与其传递的实参相匹配的 print 版本。