我们不能修改一个已经存在的默认值:

string screen(sz,sz,char);//错误:重复声明
//但是可以按照如下形式添加默认实参:
stringscreen(sz=24,sz=80,char);//正确:添加默认实参
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

通常,应该在函数声明中指定默认实参,并将该声明放在合适的头文件中。

默认实参初始值

局部变量不能作为默认实参除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参:

//wd、def和ht的声明必须出现在函数之外
sz wd = 80;
char def=' ';
sz ht();
string screen(sz=ht(),sz=wd,char=def);
string window=screen();//调用screen(ht(,80,' ')
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

用作默认实参的名字在函数声明所在的作用域内解析,而这些名字的求值过程发生在函数调用时:

void f2()
{
    def = '*';//改变默认实参的值
    sz wd=100;//隐藏了外层定义的wd,但是没有改变默认值
    window=screen();//调用screen(ht(),80,'*')
)
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

我们在函数f2内部改变了def的值,所以对screen的调用将会传递这个更新过的值。另一方面,虽然我们的函数还声明了一个局部变量用于隐藏外层的wd,但是该局部变量与传递给screen的默认实参没有任何关系。

内联函数和constexpr函数

把规模较小的操作定义成函数有很多好处,主要包括:

然而,使用shorterString函数也存在一个潜在的缺点:调用函数一般比求等价表达式的值要慢一些。在大多数机器上,一次函数调用其实包含着一系列工作:调用前要先保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向一个新的位置继续执行。

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

将函数指定为内联函数(inline),通常就是将它在每个调用点上“内联地“展开。假设我们把shorterString函数定义成内联函数,则如下调用

cout<<shorterString(s1,S2)<<endl;
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

将在编译过程中展开成类似于下面的形式

cout<<(s1.size()<s2.size()?s1:s2)<<endl;
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

从而消除了shorterstring函数的运行时开销。
在shorterString函数的返回类型前面加上关键字inline,这样就可以将它声明成内联函数了:

//内联版本:寻找两个string对象中较短的那个
inline const string&
shorterString(conststring&sl,conststring&52)
{
    return s1.size()<=s2.size()?s1:s2;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

内联说明只是向编译器发出的一个请求,编评器可以选择忽略这个请求。

一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数。很多编译器都不支持内联递归函数,而且一个75行的函数也不大可能在调用点内联地展开。

constexpr函数

constexpr函数(constexpr function)是指能用于常量表达式的函数。定义constexpr函数的方法与其他函数类似,不过要遵循几项约定:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句:

constexpr int new_sz(){return 42}
constexpr int foo=new_sz();//正确:foo是一个常量表达式
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

我们把new_sz定义成无参数的constexpr函数。因为编译器能在程序编译时验证new_sz函数返回的是常量表达式,所以可以用new_sz函数初始化constexpr类型的变量foo。

执行该初始化任务时,编译器把对constexpr函数的调用替换成其结果值。为了能在编详过程中随时展开,constexpr函数被隐式地指定为内联函数。

constexpr函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。例如,constexpr函数中可以有宇语句、类型别名以及using声明。

我们允许constexpr函数的返回值并非一个常量:

//如果arg是常量表达式,则scale(arg)也是常量表达式
constexpr size_t scale(size_t cnt){return new_sz()*cnt;}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

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

int arr[scale(2)];// 正确:scale(2)是常量表达式
int i = 2; //i不是常量表达式
int a2[scale(i)];//错误:scale(i)不是常量表达式
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

如上例所示,当我们给scale函数传入一个形如字面值2的常量表达式时,它的返回类型也是常量表达式。此时,编译器用相应的结果值替换对scale函数的调用。

如果我们用一个非常量表达式调用scale函数,比如int类型的对象;,则返回值是一个非常量表达式。当把scale函数用在需要常量表达式的上下文中时,由编译器负责检查函数的结果是否符合要求。如果结果恰好不是常量表达式,编译器将发出错误信息。

constexpr函数不一定返回常量表达式。

把内联函数和constexpr函数放在头文件内

和其他函数不一样,内联函数和constexpr函数可以在程序中多次定义。毕竟,编详器要想展开函数仅有函数声明是不够的,还需要函数的定义。不过,对于多个给定的内联函数或者constexpr函数来说,它的多个定义必须完全一致。基于这个原因,内联函数和constexpr函数通常定义在头文件中。

调试帮助

C++程序员有时会用到一种类似于头文件保护的技术,以便有选择地执行调试代码。基本思想是,程序可以包含一些用于调试的代码,但是这些代码只在开发程序时使用。当应用程序编写完成准备发布时,要先屏蔽掉调试代码。这种方法用到两项预处理功能assert和NDEBUG。

assert预处理宏

assert是一种预处理宏(preprocessor marco)。所谓预处理宏其实是一个预处理变量,它的行为有点类似于内联函数。assert宏使用一个表达式作为它的条件:

assert(expr);
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

首先对expr求值,如果表达式为假(即0),assert输出信息并终止程序的执行。如果表达式为真(即非0),assert什么也不做。

assert宏定义在cassert头文件中。如我们所知,预处理名字由预处理器而非编译器管理,因此我们可以直接使用预处理名字而无须提供using声明。也就是说,我们应该使用assert而不是std::assert,也不需要为assert提供using声明。

和预处理变量一样,宏名字在程序内必须唯一。含有cassert头文件的程序不能再定义名为assert的变量、函数或者其他实体。在实际编程过程中,即使我们没有包含

cassert头文件,也最好不要为了其他目的使用assert。很多头文件都包含了cassert,这就意味着即使你没有直接包含cassert,它也很有可能通过其他途径包含在你的程序中。

assert宏常用于检查“不能发生“的条件。例如,一个对输入文本进行操作的程序可能要求所有给定单词的长度都大于某个阑值。此时,程序可以包含一条如下所示的语句:

assert(word.size()>threshold);
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

NDEBUG预处理变量

assert的行为依赖于一个名为NDEBUG的预处理变量的状态.如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。

我们可以使用一个#define语句定义NDEBUG,从而关闭调试状态。同时,很多编译器都提供了一个命令行选项使我们可以定义预处理变量:

$CC -DNDEBUG main.C # use /D with the Microsoft compiler
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

这条命令的作用等价于在main.c文件的一开始写#define NDEBUG。

定义NDEBUG能避免检查各种条件所需的运行时开销,当然此时根本就不会执行运行时检查。因此,assert应该仪用于验证那些确实不可能发生的事情。我们可以把assert当成调试程序的一种辅助手段,但是不能用它替代真正的运行时逻辑检查,也不能替代程序本身应该包含的错误检查。

除了用于assert外,也可以使用NDEBUG编写自己的条件调试代码。如果NDEBUG未定义,将执行#ifndef和#endif之间的代码;如果定义了NDEBUG,这些代码将被忽略掉:

void print(const int ia[],size_t size)
{
#ifndef NDEBUG
// __func_“是编译器定义的一个局部静态变量,用于存放函数的名字
cerr << __func__ <<  "array size is"<< size << endl;
#endif
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

在这段代码中,我们使用变量__func__ 输出当前调试的函数的名字。编诙器为每个函数都定义了__func__,它是const char的一个静态数组,用于存放函数的名字。除了C++编诙器定义的__func__之外,预处理器还定义了另外4个对于程序调试很有用的名字:

__FILE__ //存放文件名的字符串字面值。
__LINE__ //存放当前行号的整型字面值。
__TIME__ //存放文件编译时间的字符串字面值。
__DATE__ //存放文件编译日期的字符串字面值。
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

可以使用这些常量在错误消息中提供更多信息:

if(word.size()<threshold)
cerr << "Error" <<__FILE__
    <<": in function" << __func__
    <<"at line" << __LINE__<<endl
<< "    Compiled on " <<__DATE__
<<"at "<<__TIME__<<endl
<<" Word read as " << word
<<":Length too short" <<endl;
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

如果我们给程序提供了一个长度小于threshold的string对象,将得到下面的错误消息:

error:wdebug.cc:in function main at 1ine 27
Compiled on Jul 11 2012 at 20:50:03
Word read was "foo": length too short
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/m0_56279421/article/details/145662975","extend1":"pc","ab":"new"}">> id="blogExtensionBox" style="width:400px;margin:auto;margin-top:12px" class="blog-extension-box"> class="blog_extension blog_extension_type1" id="blog_extension"> class="blog_extension_card" data-report-click="{"spm":"1001.2101.3001.6470"}" data-report-view="{"spm":"1001.2101.3001.6470"}"> class="blog_extension_card_left"> class="blog_extension_card_cont"> class="blog_extension_card_cont_l"> devlife01 class="blog_extension_card_cont_r"> 微信公众号 A股龙虎榜
注:本文转载自blog.csdn.net的c-c-developer的文章"https://blog.csdn.net/m0_56279421/article/details/145662975"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接

评论记录:

未查询到任何数据!