函数重载
出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。
可以定义一组函数,它们执行同样的一般性动作,但是应用在不同的形参类型上,调用这些函数时,无需担心调用的是哪个函数。
通过省去为函数起名并记住函数名字的麻烦,函数重载简化了程序的实现,使程序更容易理解。
任何程序都仅有一个 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 版本。