【深度C++】之“函数重载”

0. 什么是函数重载

如果同一作用域内的几个函数名字相同形参列表不同,我们称之为重载(overloaded) 函数。

分析上面的定义:

  1. 同一作用域内,强调函数重载的范围。若两个不相干的文件、或两个不相干的类具有同名函数,那么不构成重载。
  2. 名字相同但形参列表不同,这是重载的充分条件。若同一作用域内两个函数的返回值不同,不构成重载。

发明函数重载,只是为了减轻我们在定义函数时起名字的负担。例如:

void print(const char *p);
void print(const int *beg, const int *end);
void print(const int ia[], size_t size);

上面的函数构成了函数重载,不论何时我们想输出一些内容,只要调用print就行了。

注意:main函数不能重载。

关于重载,我们需要了解:

  1. 声明&定义重载函数
  2. 调用重载函数
  3. 成员函数的重载
  4. 运算符重载

1. 声明&定义重载函数

声明&定义重载函数要从两方面入手:

  1. 逻辑上是否可以构成重载
  2. C++语言层面上如何定义重载

1.1 逻辑层面

尽管函数重载可以减轻我们为函数起名字的负担,但是最好只重载那些确实非常相似的函数。例如:

void move_home();  // 移动光标到本行开头
void move_abs(int, int);
void move_rel(int, int, const string &dir);

似乎可以把他们统一为:

void move();
void move(int, int);
void move(int, int, const string &dir);

但是使用起来,丢失了名字中本来的信息。使用的时候,也明显感觉非重载的版本让人读起来更顺畅:

myScreen.moveHome();
myScreen.move();

1.2 定义重载

经过逻辑层面的分析,若可以使用重载函数减轻命名负担,则可以按照以下步骤定义重载:

  1. 确定函数的名字
  2. 确定每个函数的参数,确保不一致

确定函数的名字很简单,确保函数的参数不一致有些困难。

1.2.1 参数数量不一致
1.2.1.1 含有默认实参

某些函数有这样一种形参,在函数的很多次调用中都被赋予一个相同的值,此时我们把这个反复出现的值成为函数的默认实参(default argument) 。含有默认实参的函数,可以包含该实参,也可以省略该实参。

typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');

调用时,省略想要使用的默认实参即可。

screen(30);  // 等价于screen(30, 80, ' ');

若定义了类似于如下的重载函数:

typedef string::size_type sz;
string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');
string screen(sz ht = 24, sz wid = 80);

虽然参数列表不一样,编译重载函数也不会出错,但是在调用时不恰当,如只传递两个参数screen(30, 90),会出现二义性错误

1.2.1.2 含有可变形参

使用initializer_list定义的可变形参,可以定义一组数量未知、但是全部实参类型相同的可变形参。

void error_msg(initializer_list<string>);

initializer_list实际上是标准库的一个类型,因此定义含有该类参数的重载函数,和其他函数没什么区别。

还有一种定义可变形参的方法,可变参数模板,请参考【C++深陷】之“可变参数”。

1.2.1.2 含有省略符形参

省略符形参是为了便于C++程序访问某些特殊的C代码而设置的,这些代码使用了名为varargs的C标准库功能。通常,省略符形参不应用于其他目的。

省略符形参只能出现在形参列表的最后一个位置:

// 两种形式
void func(params, ...);
void func(...);

含有省略符形参的函数,若省略符前面的参数和其他函数参数一样,虽然编译重载函数不会出错,但是在调用的时若没有给省略符形参传递参数,会出现二义性错误

1.2.2 参数类型不一致

以下讨论的前提是参数数量一致。

1.2.2.1 省略形参名

声明函数时可以省略形参名,因此以下两种声明是等价的,不构成重载:

void print(int);
void print(int a);  // 和上面声明等价
1.2.2.2 顶层const与底层const

顶层const对参数区分无影响,例如下面的声明,都是重复声明:

void print(int);
void print(const int);  // 和上面的声明是等价的

void print(int *);
void print(int * const);  // 这是顶层const

但是指针和引用,使用底层const是可以区分的:

void print(int &);
void print(const int &);

void print(int *);
void print(const int *);
1.2.2.3 类型别名

我们可以使用typedefusing声明一个类型的别名,若使用别名定义了一系列重载函数,字面上看不出区别,但实际上是一种函数:

typedef Phone Telno;
Record lookup(const Phone &);
Record lookup(const Telno &);  // 和上面的定义等价
1.2.2.4 数组与指针

C++中的数组通常会被转换成指向首元素的指针,因此如下示例的3个函数,是等价的:

void print(const int *);
void print(const int []);
void print(const int [10]);
1.2.3 返回值不是重载条件

如定义,我们再次强调,两个函数名相同、参数列表相同,但是返回值不同的函数,无法构成重载。

2. 调用重载函数

调用一组重载函数,是函数匹配做的事情。

函数匹配,也称作重载确定,它是一个过程,主要任务是找到一个与实参最佳匹配的函数。

若重载的函数参数差别很大,可以很容易区分,但是遇到如下面这种情况:两个重载的函数参数数量相同,且相对位置的参数可以互相类型转换,这就麻烦了。

函数匹配的结果,无非是确定了一个最优调用方案无匹配二义性错误

关于函数匹配的内容,请参考【深度C++】之“函数匹配”

3. 成员函数的重载

成员函数重载条件与普通的函数一样,只需要注意构成重载的函数只在一个类内。因为成员函数有一个隐式的this指针作为第一个参数。

4. 运算符重载

运算符重载可以给类类型重新定义该运算符的含义。明智地使用运算符重载能令我们的程序更易于编写和阅读。

与定义函数重载一样,第一步要从逻辑层面保证运算符的本身的逻辑与内置类型差不多。

第二步,根据每个运算符的规则,定义属于每个类类型自己的运算符重载函数,详细参考【C++深陷】之“运算符重载”。

5. 总结

函数重载(overload)同一作用域内的几个函数名字相同形参列表不同的函数。

只有返回值不一样,不能构成重载。

函数重载只是为了减轻我们在定义函数时起名字的负担,帮助我们的程序更易于编写和阅读。

逻辑层面保证需要重载技术,然后再根据C++语言的规则定义重载函数。要注意确保函数名一致,参数数量、参数类型不一致。常见的问题如默认实参、const、数组等情况。

调用重载的函数,要遵循C++的函数匹配规则,确定一个最优调用方案,或者是出现无匹配二义性错误

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值