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 对象的指针形参的函数不相同。
在函数中局部声明的名字将屏蔽在全局作用域内声明的同名名字。这个关于变量名字的性质对于函数名同样成立:
/* Program for illustration purposes only:
* It is bad style for a function to define a local variable
* with the same name as a global name it wants to use
*/
string init(); // the name init has global scope
void fcn()
{
int init = 0; // init is local and hides global init
string s = init(); // error: global init is hidden
}
一般的作用域规则同样适用于重载函数名。如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应在同一个作用域中声明。
一般来说,局部地声明函数是一种不明智的选择。函数的声明应放在头文件中。
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 函数的局部声明。一旦找到这个名字,编译器将不再继续检查这个名字是否在外层作用域中存在,即编译器将认同找到的这个声明即是程序需要调用的函数,余下的工作只是检查该名字的使用是否有效。
第一个函数调用传递了一个字符串字面值,但是函数的形参却是 int 型的。字符串字面值无法隐式地转换为 int 型,因而该调用是错误的。print(const string&) 函数与这个函数调用匹配,但已被屏蔽,因此不在解释该调用时考虑。
当传递一个 double 数据调用 print 函数时,编译器重复了同样的匹配过程:首先检索到 print(int) 局部声明,然后将 double 型的实参隐式转换为 int 型。因此,该调用合法。
在 C++ 中,名字查找发生在类型检查之前。???????????????????????????
另一种情况是,在与其他 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 版本。