C++ Primer第六章⑤

C++ Primer

第六章 函数

特殊用途语言特性

这次介绍三种函数相关的语言特性,都挺有用的。

默认实参

假如,我们现在要用一个string表示一个窗口,一般情况下,我们是希望窗口的高、宽是默认的

//窗口函数的声明如下,带默认实参
string screen(int height = 24, int w = 80);

在上面的函数,我们为每一个形参都提供了默认实参。我们可以为一个或多个形参定义默认值,注意,一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。

int f(int a = 24, int b); //这样的声明就是错误的
int f(int a, int b = 24); //这样就可以
使用默认实参调用函数

如果我们想使用默认实参,只要在调用函数的时候省略该实参就可以了,例如调用上面的screen函数:

string window;
window = screen(); //等价于screen(24, 80)

当然,我们也可以不使用默认实参来调用,只要指定实参的值就行:

window = screen(55); //等价于screen(55, 80)
window = screen(55,35); //等价于screen(55, 35)

当然,你不能跳过一个默认实参去调用,什么意思呢?

window = screen( , 60); //错误:只能省略尾部的实参

所以啊,当设计含有默认实参的函数时,重要的一项任务是合理设置形参的顺序,尽量让不怎么使用默认值的形参出现在前面,而让那些经常使用默认值的形参出现在后面。

默认实参声明

可以多次声明同一个函数(跟我们之前介绍的一样),但是,在给定的作用域中一个形参只能被赋予一次默认实参:

string screen(int height, int w = 24);
string screen(int height = 24, int w = 24);
//错误:重复默认实参w,即便是同一个值也不行哦
string screen(int height = 48, int w); 
//正确,看上去height有默认实参,而它右边的w没有,但是
//w在第一行那个函数中已经有了啊
默认实参初始值

下面我会写一些看着有些神奇的代码,让你猜输出什么,你只要记住这句话就不难了:用作默认实参的名字在函数声明所在的作用域内解析

int a = 1;
int b = 2;
void f(int m = a, int n = b)
{
    cout << m << "," << n << endl;
}
void callf()
{
    a = 5;
    int b = 6;
    f();
}
int main()
{
    callf();
    return 0;
}

答案是:5,2 自己想想吧,别忘了上面那句要记住的话。

内联函数和constexpr函数

现在我们可能觉得调用函数很方便,让某个函数实现一个功能,我们再去调用它就很爽,不用管里面的逻辑,但它也有缺点:调用函数比直接在主函数里面写逻辑要慢,因为调用前要保存寄存器,在返回时恢复;可能需要拷贝实参;控制权转换。

内联函数可以避免函数调用的开销

将函数指定为内联函数,其实就是把函数调用的东西省略,直接把逻辑代码贴上去,例如我们之前写过一个比较字符串大小的函数:

cout << shortStr(s1, s2) << endl;
//如果把这个函数写成内联函数,那这个调用等价于
cout << (s1.size() < s2.size() ? s1 : s2) << endl;
//这样就消除了shortStr函数的运行时开销

其实就是加个关键字inline:

inline const string &shortStr(const string &s1, const string &s2)
{
    return s1.size() < s2.size() ? s1 : s2;
}

那有人就会说,为什么不把所有的函数都定义成内联函数呢?这个我也不知道,可能是函数逻辑太多,在主函数中展开的话会有问题吧(知道的同学求赐教啊) 一般来说,内联机制用于优化规模较小,流程直接,频繁调用的函数

constexpr函数

constexpr函数是指能用于常量表达式的函数,定义它要遵守一些规定:函数的返回值类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句:

constexpr int new_sz() {return 42;} //constexpr函数定义
constexpr int f = new_size(); //正确的调用

执行f的初始化时,编译器会直接把constexpr函数的调用替换成其结果值,就是42;为了能在编译过程中随时展开,constexpr函数直接被隐式地指定为内联函数。 其实啊,我们也允许constexpr函数的返回值不是一个常量(破规定好多):

constexpr int scale(int cnt){return new_sz() * cnt;}

当scale的实参cnt是常量表达式时,返回值new_sz() * cnt也是常量表达式;反之则不然:

int arr[scale(2)]; //对的
int i = 2;
int a2[scale(i)]; //错了

所以啊,constexpr函数不一定返回常量表达式 plus:和其他函数不同,内联函数和constexpr函数可以在程序中多次定义,因为你要展开的话肯定要定义的,声明是不够的,但是定义得合法,上面已经说过怎么合法了,所以我们把内联函数和constexpr函数都放在头文件里。

调试帮助

程序可以包含一些代码,只是用于调试的,发布的时候要屏蔽,这里介绍两个:assert和NDEBUG。

assert预处理宏
assert(expr);

首先对expr求值,如果为假(0),assert输出信息并终止程序的执行;如果为真,assert什么也不做。 我觉得是很好用的,相当于可以帮你检查你所要检查的任何表达式的真假,还不会出大的错,用起来很放心。

NDEBUG预处理变量

因为类似assert的运行时检查依赖于NDEBUG的预处理变量状态,所以在不用检查的时候,可以定义NDEBUG来避免检查各种条件所需的运行时开销,因为此时根本就不会检查,你的assert什么的都没用。 由此可见,C++赋予了程序员极大的权力与自由,同时也有些复杂。想使用它的同学自己去看用法吧,我一般不用就不介绍了。

#C++工程师#
全部评论
int a = 1; int b = 2; void f(int m = a, int n = b)//这个a,b是前面定义的全局a,b { cout << m << "," << n << endl; } void callf() { a = 5;//将全局a的值变为5 int b = 6;//重新定义了一个局部变量b,并赋值为6 f();//函数调用的,调用的是全局变量a,b,和上面的b没关系 } 这么理解的是对的吧
点赞 回复 分享
发布于 2017-01-18 17:18
为什么不把所有的函数都定义成内联函数呢 (1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。 (2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
点赞 回复 分享
发布于 2019-08-17 22:16

相关推荐

小红书 后端开发 总包n+8w+期权
点赞 评论 收藏
分享
评论
7
收藏
分享
牛客网
牛客企业服务