分享

重载函数

 sun317 2012-12-07
出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数
 
函数重载和重复声明的区别
        如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的:
Record lookup(const Account&);
bool lookup(const Account&); // error: only return type is different
函数不能仅仅基于不同的返回类型而实现重载。
        值得注意的是,形参与 const 形参的等价性仅适用于非引用形参。有 const 引用形参的函数与有非 const 引用形参的函数是不同的。类似地,如果函数带有指向 const 类型的指针形参,则与带有指向相同类型的非 const 对象的指针形参的函数不相同。
 
重载与作用域
        在函数中局部声明的名字将屏蔽在全局作用域(内声明的同名名字。这个关于变量名字的性质对于函数名同样成立.一般的作用域规则同样适用于重载函数名。如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应在同一个作用域中声明。
        一般来说,局部地声明函数是一种不明智的选择。函数的声明应放在头文件中。
 
函数匹配与实参转换
       函数重载确定,即函数匹配是将函数调用与重载函数集合中的一个函数相关联的过程。通过自动提取函数调用中实际使用的实参与重载集合中各个函数提供的形参做比较,编译器实现该调用与函数的匹配。匹配结果有三种可能:
1. 编译器找到与实参最佳匹配的函数,并生成调用该函数的代码。
2. 找不到形参与函数调用的实参匹配的函数,在这种情况下,编译器将给出编译错误信息。
3. 存在多个与实参匹配的函数,但没有一个是明显的最佳选择。这种情况也是,该调用具有二义性。
       大多数情况下,编译器都可以直接明确地判断一个实际的调用是否合法,如果合法,则应该调用哪一个函数。重载集合中的函数通常有不同个数的参数或无关联的参数类型。当多个函数的形参具有可通过隐式转换关联起来的类型,则函数匹配将相当灵活。在这种情况下,需要程序员充分地掌握函数匹配的过程。
 
重载确定的三个步骤
考虑下面的这组函数和函数调用:
void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6); // calls void f(double, double)
候选函数
       第一步是确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数。候选函数是与被调函数同名的函数,并且在调用点上,它的声明可见。
选择可行函数
        第二步是从候选函数中选择一个或多个函数,它们能够用该调用中指定的实参来调用。因此,选出来的函数称为可行函数。可行函数必须满足两个条件:第一,函数的形参个数与该调用的实参个数相同;第二,每一个实参的类型必须与对应形参的类型匹配,或者可被隐式转换为对应的形参类型。
        如果函数具有默认实参,则调用该函数时,所用的实参可能比实际需要的少。默认实参也是实参,在函数匹配过程中,它的处理方式与其他实参一样。
寻找最佳匹配(如果有的话)
       第三步是确定与函数调用中使用的实际参数匹配最佳的可行函数。这个过程考虑函数调用中的每一个实参,选择对应形参与之最匹配的一个或多个可行函数,其原则是实参类型与形参类型越接近则匹配越佳。因此,实参类型与形参类型之间的精确类型匹配比需要转换的匹配好。
        在上述例子中,只需考虑一个 double 类型的显式实参。如果调用 f(int),实参需从 double 型转换为 int 型。而另一个可行函数 f(double, double) 则与该实参精确匹配。由于精确匹配优于需要类型转换的匹配,因此编译器将会把函数调用 f(5.6) 解释为对带有两个 double 形参的 f 函数的调用。
 
含有多个形参的重载确定
        如果函数调用使用了两个或两个以上的显式实参,则函数匹配会更加复杂。假设有两样的名为 f 的函数,分析下面的函数调用:f(42, 2.56);
        可行函数将以同样的方式选出。编译器将选出形参个数和类型都与实参匹配的函数。在本例中,可行函数是 f(int, int) 和 f(double, double)。接下来,编译器通过依次检查每一个实参来决定哪个或哪些函数匹配最佳。如果有且仅有一个函数满足下列条件,则匹配成功:
1. 其每个实参的匹配都不劣于其他可行函数需要的匹配。
2. 至少有一个实参的匹配优于其他可行函数提供的匹配。
       如果在检查了所有实参后,仍找不到唯一最佳匹配函数,则该调用错误。编译器将提示该调用具有二义性。
       在本例子的调用中,首先分析第一个实参,发现函数 f(int, int) 匹配精确。如果使之与第二个函数匹配,就必须将 int 型实参 42 转换为 double 型的值。通过内置转换的匹配“劣于”精确匹配。所以,如果只考虑这个形参,带有两个int 型形参的函数比带有两个 double 型形参的函数匹配更佳。
        但是,当分析第二个实参时,有两个 double 型形参的函数为实参 2.56 提供了精确匹配。而调用两个 int 型形参的 f 函数版本则需要把 2.56 从 double 型转换为 int 型。所以只考虑第二个形参的话,函数 f(double, double) 匹配更佳。
        因此,这个调用有二义性:每个可行函数都对函数调用的一个实参实现更好的匹配。编译器将产生错误。解决这样的二义性,可通过显式的强制类型转换强制函数匹配:
f(static_cast<double>(42), 2.56); // calls f(double, double)
f(42, static_cast<int>(2.56)); // calls f(int, int)
       在实际应用中,调用重载函数时应尽量避免对实参做强制类型转换:需要使用强制类型转换意味着所设计的形参集合不合理。
 
实参类型转换
        为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:
1. 精确匹配。实参与形参类型相同。
2. 通过类型提升实现的匹配
3. 通过标准转换实现的匹配
4. 通过类类型转换实现的匹配
        内置类型的提升和转换可能会使函数匹配产生意想不到的结果。但幸运的是,设计良好的系统很少会包含与下面例子类似的形参类型如此接近的函数。
需要类型提升或转换的匹配
        类型提升或转换适用于实参类型可通过某种标准转换提升或转换为适当的形参类型情况。必须注意的一个重点是较小的整型提升为 int 型。假设有两个函数,一个的形参为 int 型,另一个的形参则是 short 型。对于任意整型的实参值,int 型版本都是优于 short 型版本的较佳匹配,即使从形式上看 short 型版本的匹配较佳:
void ff(int);
void ff(short);
ff('a'); // char promotes to int, so matches f(int)
         字符字面值是 char 类型,char 类型可提升为 int 型。提升后的类型与函数ff(int) 的形参类型匹配。char 类型同样也可转换为 short 型,但需要类型转换的匹配“劣于”需要类型提升的匹配。结果应将该调用解释为对 ff (int) 的调用。
        通过类型提升实现的转换优于其他标准转换。例如,对于 char 型实参来说,有int 型形参的函数是优于有 double 型形参的函数的较佳匹配。其他的标准转换也以相同的规则处理。例如,从 char 型到 unsigned char 型的转换的优先级不比从 char 型到 double 型的转换高。
参数匹配和枚举类型
这种类型的对象只能用同一枚举类型的另一个对象或一个枚举成员进行初始化。整数对象即使具有与枚举元素相同的值也不能用于调用期望获得枚举类型实参的函数。
enum Tokens {INLINE = 128, VIRTUAL = 129};
void ff(Tokens);
void ff(int);
int main()
{

Tokens curTok = INLINE;
ff(128); // exactly matches ff(int)
ff(INLINE); // exactly matches ff(Tokens)
ff(curTok); // exactly matches ff(Tokens)
return 0;
}
传递字面值常量 128 的函数调用与有一个 int 型参数的 ff 版本匹配。虽然无法将整型值传递给枚举类型的形参,但可以将枚举值传递给整型形参。此时,枚举值被提升为 int 型或更大的整型。具体的提升类型取决于枚举成员的值。
 
重载和const 形参
       仅当形参是引用或指针时,形参是否为 const 才有影响。可基于函数的引用形参是指向 const 对象还是指向非 const 对象,实现函数重载。将引用形参定义为 const 来重载函数是合法的,因为编译器可以根据实参是否为 const 确定调用哪一个函数:
Record lookup(Account&);
Record lookup(const Account&); // new function
const Account a(0);
Account b;
lookup(a); // calls lookup(const Account&)
lookup(b); // calls lookup(Account&)
        如果形参是普通的引用,则不能将 const 对象传递给这个形参。如果传递了const 对象,则只有带 const 引用形参的版本才是该调用的可行函数。
       如果传递的是非 const 对象,则上述任意一种函数皆可行。非 const 对象既可用于初始化 const 引用,也可用于初始化非 const 引用。但是,将 const 引用初始化为非 const 对象,需通过转换来实现,而非 const 形参的初始化则是精确匹配。
对指针形参的相关处理如出一辙。可将 const 对象的地址值只传递给带有指向const 对象的指针形参的函数。也可将指向非 const 对象的指针传递给函数的const 或非 const 类型的指针形参。如果两个函数仅在指针形参时是否指向const 对象上不同,则指向非 const 对象的指针形参对于指向非 const 对象的指针(实参)来说是更佳的匹配。重复强调,编译器可以判断:如果实参是 const对象,则调用带有 const* 类型形参的函数;否则,如果实参不是 const 对象,将调用带有普通指针形参的函数。注意不能基于指针本身是否为 const 来实现函数的重载:
f(int *);
f(int *const); // redeclaration
       此时,const 用于修改指针本身,而不是修饰指针所指向的类型。在上述两种情况中,都复制了指针,指针本身是否为 const 并没有带来区别

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多