默认实参
某些函数有这样一种形参,在函数的很多次调用中它们都被赋予一个相同的值,此时,我们把这个反复出现的值称为函数的默认实参。调用含有默认实参的函数时, 可以包含该实参,也可以省略该实参。
默认实参声明
对于函数的声明来说,通常的习惯是将其放在头文件中,并且一个函数只声明一次,但是多次声明一个函数也是合法的。有一点需要注意,在给定的作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前那些没有默认值的形参添加默认实参,而且该形参右侧的所有形参必须都有默认值:
string screen(sz, sz, char = ' ');
string screen(sz, sz, char = '*'); //错误,重复声明
string screen(s24, sz=80, char); //正确,添加默认实参
默认实参初始值
局部变量不能作为默认实参,除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参:
sz wd = 80;
char def = ' ';
sz ht();
string sceen(sz = ht(), sz = wd, char = def);
string window =screen(); //调用screen(ht(), 80, ' ')
用作默认实参的名字在函数声明所在的作用域内解析,而这些名字的求值过程发生在函数调用时:
void func()
{
def = "*"; //改变默认实参的值
sz wd = 100; //隐藏了外层定义的wd,但是没有改变默认实参的值
window = screen(); //调用screen(ht(), 80, '*');
}
我们在函数func内部改变了def的值,所以对screen的调用将会传递这个更新过的值。另一方面,虽然我们的函数还声明了一个局部变量用于隐藏外层的wd,但是该局部变量与传递给screen的默认实参没有任何关系。
内联函数
在大多数机器上一次函数调用包含着一些列工作:调用前保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向新的位置继续执行等。内联函数可避免函数调用的开销,在编译的过程中会把函数展开。
内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求。一般来说,内联机制用于优化规模较小,流程直接,频繁调用的函数。很多编译器都不支持内联递归函数,而且一个75行的函数也不大可能在调用点内联展开。
constexpr函数
constexpr函数是指能用于常量表达式的函数,函数返回类型及所有形参类型都是字面值类型,而且函数体中必须有且只要一条return语句:
constexpr int new_sz () { return 42; }
constexpr int foo = new_sz();
我们把new_sz定义成无参数的constexpr函数。因为编译器能在程序编译时验证函数返回的是常量表达式,所以可以用new_sz函数初始化constexpr类型的变量foo。
执行该任务时,编译器把对constexpr函数的调用替换成其结果值。为了能在编译时随时展开,constexpr函数被隐式的指定为内联函数。
constexpr函数体内也可以包含其他语句,只要这些语句在运行时不指向任何操作。constexpr函数中可以有空语句,类型别名以及using声明。
我们允许constexpr函数的返回值并非一个常量:
constexpr size_t scale(size_t cnt) { return new_sz() * cnt; ]
当scale的实参cnt是常量表达式时,它的返回值也是常量表达式;反之则不然:
int arr[scale(2)]; //正确,数组初始化个数需要常量表达式,scale(2)是常量表达式
int i = 2;
int arr2[scale(i)]; //错误,scale(i)不是常量表达式