第04章 C++语言专题(一.06)函数重载

声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。

摘要

本文主要包括:重载函数的定义、调用和作用域,以及寻找最佳函数匹配的具体说明。


重载函数(overloaded functions):在同一作用域内,几个函数有相同的名字,不同的形参列表(不同的形参数量或形参类型)。

1 定义重载函数

// 创建几个不同的函数,分别根据账户号、电话、名字信息查找记录
Record lookup(const Account &); // find by Account
Record lookup(const Phone &);   // find by Phone
Record lookup(const Name &);    // find by Name

// 编译器根据实参的类型确定应该调用哪一个函数
Account acct;
Phone phone;
Record r1 = lookup(acct);  // call version that takes an Account
Record r2 = lookup(phone); // call version that takes a Phone

// 不允许两个函数除了返回类型外其他所有的要素都相同
Record lookup(const Account &);
bool lookup(const Account &); // error: only the return type is different

1.1 相同的形参

有时候两个形参列表看起来不一样,但实际上是相同的:

  • 省略 vs 不省略形参的名字
  • 类型 vs 类型别名
// 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

1.2 顶层 const 和重载

顶层 const 作用于对象本身,当用实参初始化形参时,会忽略掉顶层 const。所以,一个拥有顶层 const 的形参无法和另一个没有顶层 const 的形参进行区分。

// 在这两组函数声明中,每一组的第二个声明和第一个声明是等价的
Record lookup(Phone);
Record lookup(const Phone); // redeclares Record lookup(Phone)

Record lookup(Phone *);
Record lookup(Phone *const); // redeclares Record lookup(Phone*)

1.3 底层 const 和重载

如果形参是某种类型的指针或引用,则通过区分其指向的是 const 对象还是 非const 对象可以实现函数重载。

在调用过程中,编译器可以通过实参是否是常量来推断应该调用哪个函数

  • 只能把 const 对象(或指向 const 的指针)传递给 const 形参;
  • 可以把 非const 对象(或指向 非const 的指针)传递给所有形参,但会优先选用const 形参。
// functions taking const and nonconst references or pointers have different parameters
// declarations for four independent, overloaded functions
Record lookup(Account &);       // function that takes a reference to Account
Record lookup(const Account &); // new function that takes a const reference

Record lookup(Account *);       // new function, takes a pointer to Account
Record lookup(const Account *); // new function, takes a pointer to const

关于顶层 const 和底层 const(指针:本身是一个对象,又可以指向另外一个对象)

顶层 const (top-level const)

  • 表示指针本身是常量;
  • 扩展表示任意的对象是常量。

底层 const (low-level const)

  • 表示指针所指的对象是常量;
  • 扩展表示引用等复合类型的基本类型部分是常量。

指针类型:既可以是顶层 const,又可以是底层 const

2 const_cast 和重载

最常应用 const_cast 的情境是在重载函数中:

// return a reference to the shorter of two strings
const string &shorterString(const string &s1, const string &s2) {
  return s1.size() <= s2.size() ? s1 : s2;
}

string &shorterString(string &s1, string &s2) {
  auto &r = shorterString(const_cast<const string &>(s1), const_cast<const string &>(s2));
  // 安全地将 const string & 类型的对象 r 转回其原本的 string & 类型
  return const_cast<string &>(r);
}

3 调用重载的函数

函数匹配(function matching,也叫做重载确定 overloaded resolution):把函数调用与一组重载函数中的某一个关联起来。

在调用重载函数时,有三种可能的结果:

  • 编译器找到一个与实参最佳匹配(best match)的函数,并生成调用该函数的代码;
  • 编译器找不到任何一个函数与调用的实参匹配,此时发出无匹配(no match)的错误信息;
  • 编译器找到多个函数可以匹配,但是没有一个明显的最佳选择,此时发出二义性调用(ambiguous call)的错误信息。

函数匹配的步骤包括:

  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)

3.1 确定候选函数

候选函数(candidate function),具备两个特征:

  1. 与被调用的函数同名;
  2. 其函数声明在调用点可见。

在示例代码中,对于函数调用 f(5.6),有 4 个名为 f 的候选函数。

3.2 确定可行函数

可行函数(viable function),具备两个特征:

  1. 其形参数量与本次调用提供的实参数量相等;
  2. 每个实参的类型与对应的形参类型相同,或者能转换成形参的类型。

如果没找到可行函数,编译器将报告无匹配函数(no match)的错误。

在示例代码中,对于函数调用 f(5.6),有 2 个可行函数,f(int)f(double, double = 3.14)

3.3 寻找最佳匹配

为了确定最佳匹配,编译器将实参类型到形参类型的转换划分成几个等级,由高到低排序如下:

  1. 精确匹配,包括:
    • 实参类型和形参类型相同;
    • 实参从数组类型或函数类型转换成对应的指针类型;
    • 向实参添加顶层 const 或者从实参中删除顶层 const
  2. 通过 const 转换实现的匹配;
  3. 通过类型提升实现的匹配;
  4. 通过算术类型转换、指针转换实现的匹配;
  5. 通过类类型转换实现的匹配。

类类型转换参考后面章节:C++ 语言专题(一.07)操作重载与类型转换 => 9 重载、类型转换与运算符
其它类型转换回顾:C++ 语言专题(一.02)指针与引用 => 3.1 隐式转换

在示例代码中,对于函数调用 f(5.6),实参 5.6 的类型是 double,与可行函数 f(double, double) 的形参类型精确匹配,因此,编译器解析成对 f(double, double = 3.14) 的调用。

3.3.1 需要类型提升的匹配

小整型一般都会提升到 int 类型或更大的整数类型。

void ff(int);
void ff(short);
ff('a');   // char promotes to int; calls f(int)

3.3.2 需要算数类型转换的匹配

所有算术类型转换的级别都一样。

void manip(long);
void manip(float);
manip(3.14); // 3.14 is a double; error: ambiguous call

3.3.3 函数匹配和 const 实参

如果重载函数的区别在于,它们的引用类型的形参是否引用了 const,或者指针类型的形参是否指向了 const,则当调用发生时,编译器通过实参是否是 const 来决定选择哪个函数。

Record lookup(Account &);       // function that takes a reference to Account
Record lookup(const Account &); // new function that takes a const reference
const Account a;
Account b;

// 可行函数只有 lookup(const Account&)
lookup(a); // calls lookup(const Account&)

// 两个函数都是可行函数
// 接受非常量形参的函数与 b 精确匹配,匹配程度最高
// 用非常量对象初始化常量引用需要类型转换,即通过 const 转换实现的匹配,匹配程度相对低
lookup(b); // calls lookup(Account&)

3.4 含有多个形参的函数匹配

当实参的数量有两个或更多时,编译器依次检查每个实参以确定哪个函数是最佳匹配。如果有且仅有一个函数满足下列条件,则匹配成功:

  1. 每个实参的匹配程度,都不劣于其他可行函数 与相应实参的匹配(需要的匹配?);
  2. 至少有一个实参的匹配程度,优于其他可行函数 与此实参的匹配(提供的匹配?)。

如果没有任何一个函数脱颖而出,编译器将报告二义性调用(ambiguous call)的错误。

分析函数调用:f(42, 2.56);
在示例代码中,可行函数包括:f(int, int)f(double, double)
对于第 1 个实参 42f(int, int) 精确匹配;
对于第 2 个实参 2.56f(double, double) 精确匹配。
因为每个可行函数各自在一个实参上实现了更好地匹配,从整体上无法判断孰优孰劣,所以编译器最终将因为这个调用具有二义性而报告错误。

4 重载与作用域

重载对作用域的一般性质并没有改变:

  • 如果在内层作用域中声明名字,那么它将隐藏外层作用域中声明的同名实体;
  • 在不同的作用域中无法重载函数名。
string read();
void print(const string &);
void print(double); // overloads the print function

void fooBar(int ival)
{
  bool read = false; // new scope: hides the outer declaration of read
  string s = read(); // error: read is a bool variable, not a function

  // bad practice: usually it's a bad idea to declare functions at local scope
  void print(int); // new scope: hides previous instances of print

  // 在调用 print 函数时,编译器首先寻找对该函数名的声明,找到的是接受 int 值的局部声明
  // 一旦找到了所需的名字,编译器就会忽略掉外层作用域中的同名实体
  // 剩下的工作就是检查函数调用是否有效(在 C++ 语言中,名字查找发生在类型检查之前)
  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
}

参考

  1. [美] Stanley B.Lippman著.C++ Primer 中文版(第5版).电子工业出版社.2013.
  2. [美] Stephen Prata著.C++ Primer Plus(第6版)中文版.人民邮电出版社.2012.

宁静以致远,感谢 Vico 老师。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值