当前位置: 首页 > 科技观察

C-C++为什么要设计一个Do...While?

时间:2023-03-16 19:55:52 科技观察

最初作为do...while出现,更像是循环控制流的语法糖。因为不管是while还是for循环,首先要判断是否满足进入循环体的条件。条件满足后,才能进入循环,执行循环体中的操作。而且有时候,我们第一次执行逻辑不需要满足循环条件。这时候可以使用do...while。比如前几天LeetCode每日一题869.重新排序得到2的次方,刚好遇到这样一个场景:给定一个正整数N,我们把数字以任意顺序(包括原来的顺序)重新排序,注意它的前导数字不能为零。如果通过上述方法可以得到2的幂,则返回true;否则,返回假。https://leetcode-cn.com/problems/reordered-power-of-2/如果懒得解题,可以直接用STL的排列相关函数next_permutation来答:classSolution{public:boolreorderedPowerOf2(intn){autocheck=[](intn){return(n&(n-1))==0;};strings=to_string(n);intlen=s.size();sort(s.begin(),s。结尾());do{if(s[0]=='0'){continue;}if(check(stoi(s))){returntrue;}}while(next_permutation(s.begin(),s.end()));返回假;}};在这道题中,我们对字符串sort()之后,就变成了字典的升序,然后每次调用next_permutation()修改字符串s,就变成了里面字母的下一次排列。当没有下一个排列时(字符串已按字典顺序反转),返回false。不是你第一次进来的时候。while(next_permutaion(s.begin(),s.end()){if(s[0]=='0'){continue;}if(check(stoi(s))){returntrue;}}因为这个会导致排序完成的s(升序)不参与校验的计算,导致遗漏。如果你做不到...而你只能这样写:sort(s.begin(),s.end());if(s[0]!='0'&&check(stoi(s))){返回真;}while(next_permutation(s.begin(),s.end())){if(s[0]=='0'){continue;}if(check(stoi(s))){returntrue;}}在执行while之前做一个校验计算,然后进入while。当然逻辑上没有问题,只是造成代码冗余。当然,这是do...while最初的用法,后来程序员集思广益,利用do...while的特点,发明了do...while(0)独特的特殊使用场景。do...while(0)with宏函数的定义C和C++有宏的概念,而Java没有,所以这个子句对Java程序员没有用。在C/C++中,有时我们可能会使用宏来定义“函数”。我们都知道它的本质还是一个宏,而不是一个函数。所以其实代码文本的暴力替换还是在编译预处理阶段进行的!而如果插入了你定义的宏函数中的代码,附近有括号或者分号,有时往往不能如愿编译运行。do...while(0)构造的代码块不会受到花括号、分号等的影响,无论你把你的宏函数放在哪里都不会出错。比如Redis源码中就有很多这样的用法。下面这段话来自zmalloc的源码:#defineupdate_zmalloc_stat_alloc(__n)do{\size_t_n=(__n);\if(_n&(sizeof(long)-1))_n+=sizeof(long)-(_n&(sizeof(long)-1));\if(zmalloc_thread_safe){\update_zmalloc_stat_add(_n);\}else{\used_memory+=_n;\}\}while(0)do...while(0)中断顺序执行的逻辑本项适用于C、C++、Java等有do...while用法的语言。由于在Java中int和bool不能转换,所以在Java中是:do{}while(false);言归正传,关于这个用法,其实我在上一篇文章的第7条中也有介绍过。简化C++代码的方法(一)总结一下,一个函数(或方法)中的一段时序逻辑依次经过1、2、3三个步骤,然后是其他逻辑(如4、5)。其中1,如果失败,不执行2,2,如果失败,不执行3,就是逻辑中断后直接跳转到4和5。很容易想到的实现思路有3种:将步骤1、2、3抽象成函数。每次判断函数的返回值,只有成功才调用下一个函数。好的。没关系。但是如果类似的逻辑很多,就会分成很多个函数,每个函数只有几行代码。啰嗦了使用例外。如果是Java语言,你应该很习惯用异常来实现这个逻辑,把时序逻辑封在trycatch块中。每一步失败直接抛出异常。OK,C++也能写出类似的代码。但是,在C++中使用异常存在很多隐患,不如Java安全。许多工程规范都尽力避免抛出异常。另外,抛出异常也不是没有代价的,这里只是逻辑中断,逻辑上不算“异常”。抛异常和捕获异常的方式必然会影响代码的可读性。当然我们要严格禁止goto。直接跳过这个选项。其实还有第四种方案:dowhile(0)do{//step1...if(step1fails){break;}//step2...if(step2fails){break;}//Step3...if(step3fails){break;}}while(0);//Step4...//Step5...这其实适用于其他使用dowhile的语言,不仅仅是C++。当然,在C++11之后,很多人建议使用立即执行的lambda会更好,表现力会更强:[...](...){//通过捕获或传递参数在上下文中输入一些变量,//replacewith...,这意味着省略...不是语法的一部分!//Step1...if(step1failed){return;}//step2...if(step2failed){return;}//step3...if(step3failed){return;}}();//比普通的lambda表达式多了一个括号,意思是立即执行这个匿名的,定义处立即执行lambda,也叫IIFE(ImmediatelyInvokedFunctionExpression),译为:ImmediatelyInvokedFunctionExpression。IIFE是Javascript中的一个概念。看到国外也有人把C++中lambda表达式的使用称为IIFE,我想这可能不是C++的官方说法。不管怎样,其实IIFE的风格相比do...while(0)并没有减少多少代码量,还需要额外的传参或者捕获。支持者认为这里的return中断逻辑优于do...while(0)的break表达式中断。这……见仁见智了。本文转载自微信公众号《编程史》,可通过以下二维码关注。转载本文请联系编程新闻公众号。