文章目录
6.4 函数重载
如果 同一作用域内 的几个函数 名字相同,但 形参列表不同,则称之为 重载函数。
【重载与作用域】
如果我们在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。也就是说,如果编译器在当前作用域中找到了所需的名字,那它就会忽略掉外层作用域中的同名实体。例如:
string read();
void print(const string &);
void print(double); // 重载print函数
void func(int i) {
bool read = true; // 新作用域:隐藏了外层的read
string s = read(); // 错误:read是个布尔值,而非函数
void print(int); // 新作用域:隐藏了之前的print
print("Value is "); // 错误:参数应该是int
print(i); // 正确
print(3.14); // 正确:3.14可以转换为int
}
6.5 特殊用途语言特性
C++ 中有3种函数相关的语言特性,分别是 默认实参、内联函数 和 constexpr函数。
6.5.1 默认实参
某些函数有这样一种形参,在函数的多次调用中它们都被赋予同一个值,此时我们把这个反复出现的值称为默认实参。调用含有默认实参的函数时,可以包含该实参,也可以省略该实参。
我们可以为一个或多个形参定义默认值,但要注意的是,一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。如果我们想使用默认实参,只要在调用函数的时候省略该实参就可以了。函数调用时 实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参(靠右侧位置)。
例子:
// screen函数为它的所有形参提供了默认实参
// 我们可以用0、1、2或3个实参来调用该函数
string screen(int h = 1, int w = 4, char back = ' ');
string window;
window = screen(); // 等价于screen(1, 4, ' ')
window = screen(2); // 等价于screen(2, 4, ' ')
window = screen(2, 6); // 等价于screen(2, 6, ' ')
window = screen(2, 6, '*'); // 等价于screen(2, 6, '*')
window = screen(, , '&'); // 错误:只能省略尾部实参
【默认实参声明】
对于函数的声明来说,通常的习惯是将其放在 头文件 中,并且一个函数只声明一次,但多次声明同一个函数也是合法的,但要注意的是,在给定作用域中一个形参只能被赋予一次默认实参(也就是说函数的后续声明只能为之前那些没有默认值的形参添加默认实参,而且该形参右侧的所有形参必须有默认值)。例如:
// a和b没有默认值
string screen(int, int, char = 'c');
// 错误:重复声明,不能修改一个已经存在的默认值
string screen(int , int , char = 'd');
// 正确:添加默认实参
string screen(int = 1, int = 3, char)
例子:
void func(int = 9);
void func(int i) {
cout << i << endl;
}
int main()
{
func(); // 输出:9
return 0;
}
6.5.2 内联函数
调用函数 一般比 求等价表达式 要 慢 一些,因为一次函数调用其实包含一系列工作:
(1)调用前要先保存寄存器,并在返回时恢复;
(2)可能需要拷贝实参;
(3)程序转向一个新的位置开始执行。
【内联函数可避免函数调用的开销】
将函数指定为 内联函数(inline),通常就是将它在每个调用点上”内联地“展开。如果想将一个函数定义为内联函数,只需要 在函数的返回类型前加上关键字 inline。
例如,现在有一个函数func:
const string &func(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}
假设将这个函数定义为内联函数:
inline const string &func(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}
那么,如下调用
cout << func(s1, s2) << endl;
将在编译过程中展开成类似于下面的形式:
cout << (s1.size() <= s2.size() ? s1 : s2) << endl;
从而消除了 func 函数的运行时开销。
【注意】一般来说,内联机制用于优化规模较小、流程直接、调用频繁的函数。
6.5.3 调试帮助
【assert预处理宏】
assert 是一种 预处理宏。所谓预处理宏,其实就是一个预处理变量,它由预处理器而非编译器管理。
assert 宏定义在 cassert头文件 中,它使用一个表达式作为它的条件:
#include <cassert> // 需要包含该头文件
assert(expr);
// 首先对表达式expr求值
// 如果表达式为假(即0),assert会输出信息并终止程序的执行
// 如果表达式为真(即1),assert什么也不做
assert 宏常用于检查”不能发生“的条件。例如,一个对输入文本进行操作的程序,要求所给单词的长度不能小于某个阈值,则可以包含该语句:
assert(word.size() > threshold);
【预处理器定义的几个名字】
C++编译器定义了多个对于程序调试很有用的名字:
__func__ // 存放当前函数的名字
__FILE__ // 存放文件名的字符串字面值
__TIME__ // 存放文件编译时间的字符串字面值
__DATE__ // 存放文件编译日期的字符串字面值
我们可以利用这些常量在错误消息中提供更多有用的信息。例如:
int main()
{
string word = "hellow";
if(word.size() < 10) {
cerr << "Error:" << __FILE__
<< " : in function " << __func__
<< " at line " << __LINE__ << endl
<< "Compiled on " << __DATE__
<< " at " << __TIME__ << endl;
}
return 0;
}
// 输出
/*
main.cpp : in function main at line 15
Compiled on Mar 23 2019 at 15:57:33
*/