• 重载函数

出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数

使用某种程序设计语言编写过算术表达式的程序员都肯定使用过重载函数。表达式

     1 + 3

调用了针对整型操作数加法操作符,而表达式

     1.0 + 3.0

调用了另外一个专门处理浮点操作数的不同的加法操作。根据操作数的类型来区分不同的操作,并应用适当的操作,是编译器的责任,而不是程序员的事情。

类似地,程序员可以定义一组函数,它们执行同样的一般性动作,但是应用在不同的形参类型上,调用这些函数时,无需担心调用的是哪个函数,就像我们不必操心执行的是整数算术操作还是浮点数自述操作就可以实现 int 型加法或 double 型加法一样。

通过省去为函数起名并记住函数名字的麻烦,函数重载简化了程序的实现,使程序更容易理解。函数名只是为了帮助编译器判断调用的是哪个函数而已。例如,一个数据库应用可能需要提供多个 lookup 函数,分别实现基于姓名、电话号码或账号之类的查询功能。函数重载使我们可以定义一系列的函数,它们的名字都是 lookup,不同之处在于用于查询的值不相同。如此可传递几种类型中的任一种值调用 lookup 函数:

Record lookup(const Account&);  // find by AccountRecord lookup(const Phone&);    // find by PhoneRecord lookup(const Name&);     // find by Name
     Record r1, r2;r1 = lookup(acct);                  // call version that takes an Accountr2 = lookup(phone);                 // call version that takes a Phone

这里的三个函数共享同一个函数名,但却是三个不同的函数。编译器将根据所传递的实参类型来判断调用的是哪个函数。

要理解函数重载,必须理解如何定义一组重载函数和编译器如何决定对某一调用使用哪个函数。任何程序都仅有一个 main 函数的实例。main 函数不能重载。

  • 重载函数和重复声明的区别

如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的:

 Record lookup(const Account&);bool lookup(const Account&); // error: only return type is different

函数不能仅仅基于不同的返回类型而实现重载

注意,有些看起来不相同的形参表本质上是相同的:

Record lookup(const Account &acct);Record lookup(const Account&); 

形参名只是帮助文档,并没有修改形参列表。

typedef Phone Telno;Record lookup(const Phone&);Record lookup(const Telno&); 

看似形参类型不同,但注意到 Telno 其实并不是新类型,只是 Phone 类型的同义词。typedef 给已存在的数据类型提供别名,但并没有创建新的数据类型。所以,如果两个形参的差别只是一个使用 typedef 定义的类型名,而另一个使用 typedef 对应的原类型名,则这两个形参并无不同。

Record lookup(const Phone&, const Name&);// default argument doesn't change the number of parametersRecord lookup(const Phone&, const Name& = "");

形参列表只有默认实参不同。默认实参并没有改变形参的个数(而且参数的类型也都相同)。无论实参是由用户还是由编译器提供的,这个函数都带有两个实参。

Record lookup(Phone);Record lookup(const Phone);

最后一对的区别仅在于是否将形参定义为 const。这种差异并不影响传递至函数的对象;第二个函数声明被视为第一个的重复声明。其原因在于实参传递的方式。复制形参时并不考虑形参是否为 const——函数操纵的只是副本。函数的无法修改实参。结果,既可将 const 对象传递给 const 形参,也可传递给非 const 形参,这两种形参并无本质区别。

值得注意的是,形参与 const 形参的等价性仅适用于非引用形参。有 const 引用形参的函数与有非 const 引用形参的函数是不同的。类似地,如果函数带有指向 const 类型的指针形参,则与带有指向相同类型的非 const 对象的指针形参的函数不相同。//正如《关于函数形参的一些讨论》所讨论到的,void fcn(const int i) { } 和void fcn(int i) { }会被视为重定义。 因为当你给fcn传递参数时,会拷贝传递的参数生成一个副本,而fcn是修改的那个副本而非原对象,所以这里的const没有限制,视void fcn(const int i) { } 和void fcn(int i) { }为相同。而对于const引用和非const引用,如void f(cosnt int &r)和void f(int &r),如果f函数直接操作的是你给f传递的对象本身,所以如果传递的对象是const,就不能用void f(int &r),只能用void f(cosnt int &r),所以void f(cosnt int &r)和void f(int &r)是不同的。

  • 重载与作用域

如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应在同一个作用域中声明。

作为例子,考虑下面的程序:

void print(const string &);void print(double);   // overloads the print functionvoid fooBar(int ival){void print(int);   // new scope: hides previous instances of printprint("Value: ");  // error: print(const string &) is hiddenprint(ival); // ok: print(int) is visibleprint(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 型。因此,该调用合法。

另一种情况是,在与其他 print 函数相同的作用域中声明 print(int),这样,它就成为 print 函数的另一个重载版本。此时,所有的调用将以不同的方式解释:

void print(const string &);void print(double); // overloads print functionvoid print(int);    // another overloaded instancevoid fooBar2(int ival){print("Value: "); // ok: calls print(const string &)print(ival);      // ok: print(int)print(3.14);      // ok: calls print (double)}

现在,编译器在检索名字 print 时,将找到这个名字的三个函数。每一个调用都将选择与其传递的实参相匹配的 print 版本。

  • 重载的确定

函数重载确定,即函数匹配是将函数调用与重载函数集合中的一个函数相关联的过程。通过自动提取函数调用中实际使用的实参与重载集合中各个函数提供的形参做比较,编译器实现该调用与函数的匹配。

考虑下面的这组函数和函数调用:

void f();void f(int);void f(int, int);void f(double, double = 3.14);f(5.6);  // calls void f(double, double)

第一步,确定候选函数。函数重载确定的第一步是确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数。候选函数是与被调函数同名的函数,并且在调用点上,它的声明可见。在这个例子中,有四个名为 f 的候选函数。

第二步,选择可行函数。从候选函数中选择一个或多个函数,它们能够用该调用中指定的实参来调用。因此,选出来的函数称为可行函数。可行函数必须满足两个条件:第一,函数的形参个数与该调用的实参个数相同;第二,每一个实参的类型必须与对应形参的类型匹配,或者可被隐式转换为对应的形参类型

如果函数具有默认实参,则调用该函数时,所用的实参可能比实际需要的少。默认实参也是实参,在函数匹配过程中,它的处理方式与其他实参一样。

对于函数调用 f(5.6),可首先排除两个实参个数不匹配的候选函数。没有形参的 f 函数和有两个 int 型形参的 f 函数对于这个函数调用来说都不可行。例中的调用只有一个实参,而这些函数分别带有零个和两个形参。

另一方面,有两个 double 型参数的 f 函数可能是可行的。调用带有默认实参的函数时可忽略这个实参。编译器自动将默认实参的值提供给被忽略的实参。因此,某个调用拥有的实参可能比显式给出的多。

根据实参个数选出潜在的可行函数后,必须检查实参的类型是否与对应的形参类型匹配。与任意函数调用一样,实参必须与它的形参匹配,它们的类型要么精确匹配,要么实参类型能够转换为形参类型。在这个例子中,余下的两个函数都是是可行的。

f(int) 是一个可行函数,因为通过隐式转换可将函数调用中的 double 型实参转换为该函数唯一的 int 型形参。f(double, double) 也是一个可行函数,因为该函数为其第二个形参提供了默认实参,而且第一个形参是 double 类型,与实参类型精确匹配。如果没有找到可行函数,则该调用错误。

第三步,寻找最佳匹配。函数重载确定的第三步是确定与函数调用中使用的实际参数匹配最佳的可行函数。这个过程考虑函数调用中的每一个实参,选择对应形参与之最匹配的一个或多个可行函数。这里所谓“最佳”,其原则是实参类型与形参类型越接近则匹配越佳。因此,实参类型与形参类型之间的精确类型匹配比需要转换的匹配好。

  • 调用的二义性

如果函数调用使用了两个或两个以上的显式实参,则函数匹配会更加复杂。假设有两样的名为 f 的函数,分析下面的函数调用:

     f(42, 2.56);

可行函数将以同样的方式选出。编译器将选出形参个数和类型都与实参匹配的函数。在本例中,可行函数是 f(int, int)f(double, double)。接下来,编译器通过依次检查每一个实参来决定哪个或哪些函数匹配最佳。如果有且仅有一个函数满足下列条件,则匹配成功:

其每个实参的匹配都不劣于其他可行函数需要的匹配。

至少有一个实参的匹配优于其他可行函数提供的匹配。

如果在检查了所有实参后,仍找不到唯一最佳匹配函数,则该调用错误。编译器将提示该调用具有二义性。

在本例子的调用中,首先分析第一个实参,发现函数 f(int, int) 匹配精确。如果使之与第二个函数匹配,就必须将 int 型实参 42 转换为 double 型的值。通过内置转换的匹配“劣于”精确匹配。所以,如果只考虑这个形参,带有两个 int 型形参的函数比带有两个 double 型形参的函数匹配更佳。

但是,当分析第二个实参时,有两个 double 型形参的函数为实参 2.56 提供了精确匹配。而调用两个 int 型形参的 f 函数版本则需要把 2.56double 型转换为 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)

  • 需要类型提升或转换的匹配

必须注意的一个重点即便是较小的整型也会提升为 int 型而不会直接提升为short。假设有两个函数,一个的形参为 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 型的转换高。再举一个具体的例子,考虑:

extern void manip(long);extern void manip(float);manip(3.14);  // error: ambiguous call

字面值常量 3.14 的类型为 double。这种类型既可转为 long 型也可转为 float 型。由于两者都是可行的标准转换,因此该调用具有二义性。没有哪个标准转换比其他标准转换具有更高的优先级。

  • 枚举类型

枚举类型的对象只能用同一枚举类型的另一个对象或一个枚举成员进行初始化。整数对象即使具有与枚举元素相同的值也不能用于调用期望获得枚举类型实参的函数。

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;}

虽然无法将整型值传递给枚举类型的形参,但可以将枚举值传递给整型形参。此时,枚举值被提升为 int 型或更大的整型。具体的提升类型取决于枚举成员的值。如果是重载函数,枚举值提升后的类型将决定调用哪个函数:

void newf(unsigned char);void newf(int);unsigned char uc = 129;newf(VIRTUAL); // calls newf(int)newf(uc);      // calls newf(unsigned char)

枚举类型 Tokens 只有两个枚举成员,最大的值为 129。这个值可以用 unsigned char 类型表示,很多编译器会将这个枚举类型存储为 unsigned char 类型。然而,枚举成员 VIRTUAL 却并不是 unsigned char 类型。就算枚举成员的值能存储在 unsigned char 类型中,枚举成员和枚举类型的值也不会提升为 unsigned char 类型。

在使用有枚举类型形参的重载函数时,请记住:由于不同枚举类型的枚举常量值不相同,在函数重载确定过程中,不同的枚举类型会具有完全不同的行为。其枚举成员决定了它们提升的类型,而所提升的类型依赖于机器。

  • const形参

仅当形参是引用或指针时,形参是否为 const 才有影响。可基于函数的引用形参是指向 const 对象还是指向非 const 对象,实现函数重载。将引用形参定义为 const 来重载函数是合法的,因为编译器可以根据实参是否为 const 确定调用哪一个函数

Record lookup(Account&);Record lookup(const Account&); // new functionconst 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 并没有带来区别。当形参以副本传递时,不能基于形参是否为 const 来实现重载。

转载于:https://www.cnblogs.com/predator-wang/p/5197971.html

关于重载函数的一些学习相关推荐

  1. gmock学习02---编写自己的Matcher与如何让编译器识别被mock的重载函数

    本文目的 gmock框架中自带Matcher只针对c++内置类型,如string,int,float等等,有时候无法满足项目要求,所以需要编写自己的Matcher对象.本文介绍如何使用gmock提供的 ...

  2. c 语言重载参数类型不同重载和,C++基础学习之函数重载的简单介绍

    前言 我们在平时写代码中会用到几个函数但是他们的实现功能相同,但是有些细节却不同.例如:交换两个数的值其中包括(int, float,char,double)这些个类型.在C语言中我们是利用不同的函数 ...

  3. 在学习EasyX过程中,遇到“没有与参数列表匹配的重载函数loadimage”,修改解决方案字符集属性无效后的解决方法

    在学习EasyX过程中,遇到"没有与参数列表匹配的重载函数loadimage"的问题,在网上查询后,很多人给出的方法是--修改解决方案字符集属性. 解决"E0304&qu ...

  4. C++ 学习 ::【基础篇:17】:C++ 类与对象:运算符重载介绍、运算符重载函数(类内与类外区别)写法及简单设计实现

    本系列 C++ 相关文章 仅为笔者学习笔记记录,用自己的理解记录学习!C++ 学习系列将分为三个阶段:基础篇.STL 篇.高阶数据结构与算法篇,相关重点内容如下: 基础篇:类与对象(涉及C++的三大特 ...

  5. c++类指针赋值表达式必须是可修改的左值_C++学习刷题8--复制构造函数和赋值运算符重载函数...

    一.前言 本部分为C++语言刷题系列中的第8节,主要讲解这几个知识点:复制构造函数和赋值运算符重载函数.欢迎大家提出意见.指出错误或提供更好的题目! 二.知识点讲解 知识点1:复制构造函数 1.当依据 ...

  6. c++ 复制构造函数_C++学习刷题8--复制构造函数和赋值运算符重载函数

    一.前言 本部分为C++语言刷题系列中的第8节,主要讲解这几个知识点:复制构造函数和赋值运算符重载函数.欢迎大家提出意见.指出错误或提供更好的题目! 二.知识点讲解 知识点1:复制构造函数 1.当依据 ...

  7. C++学习笔记(十二):重载函数

    1. 什么是重载函数 假设同一作用域内的几个函数名字同样但形參列表不同.那么这些函数就称之为--重载函数. 比如: void print( const char *cp); void print(co ...

  8. 编写自己的Matcher与如何让编译器识别被mock的重载函数

    本文目的 gmock框架中自带Matcher只针对c++内置类型,如string,int,float等等,有时候无法满足项目要求,所以需要编写自己的Matcher对象.本文介绍如何使用gmock提供的 ...

  9. C/C++基础语法复习(三):C++重载函数,多态,虚函数

    1.重载运算符和重载函数: C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载. 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它 ...

最新文章

  1. 3G时代需要“移动云计算专业”
  2. java笔试之字符串反转
  3. 数学--数论--HDU 6128 Inverse of sum (公式推导论)
  4. linux的less命令如何退出,Linux命令详解之less命令
  5. 让你的ASP.NET Core应用程序更安全
  6. mysql计算订单总金额_mysql统计当天消费总额
  7. WIN10环境JAVA的JDK环境变量设置教程
  8. 洛谷P3376-网络流
  9. Mac电脑上哪个解压缩软件好用?MAC上好用的解压缩软件分享
  10. 一路向往有光亮的地方-2015年终总结
  11. mac --- wifi无法获取ip地址
  12. android app报告,知乎APP用户体验报告
  13. H3C 802.11n的频宽模式
  14. AWS云计算题目总结
  15. 文学-谚语-英文谚语:英文谚语
  16. JNI入门学程之HelloWorld篇
  17. Android 性能优化之内存泄漏,使用MATLeakCanary解决问题
  18. ssl vpn 与 ipsec vpn 区别
  19. Debug_C++:关于逻辑或在合法性检测中的问题
  20. JDK的安装和环境变量配置-Win10系统

热门文章

  1. 快速读书的方法(对于理工科的可能适用)
  2. 危机永远回来,只是什么时候的问题
  3. 啊,我南非时间上午10点看成了。。
  4. 今天突然想到一个问题:地球在转动吗,由东西向西跳与由西向东跳哪个更远...
  5. Resources与StreamingAssets文件夹的区别
  6. windows 如何使用4GB(开启3GB和PAE)
  7. Intellij IDEA 神器那些让人爱不释手的小技巧
  8. MyBatis子查询
  9. CocoaPods 错误解决 Attempt to read non existent folder
  10. hdfs web_ui深入讲解、服务启动日志分析、NN SNN关系