假设我有这样的伪代码:
bool conditionA = executeStepA();
if (conditionA){
bool conditionB = executeStepB();
if (conditionB){
bool conditionC = executeStepC();
if (conditionC){
...
}
}
}
executeThisFunctionInAnyCase();
函数executeStepX当且仅当前一个成功时执行。
在任何情况下,executeThisFunctionInAnyCase函数都应该在最后被调用。
我在编程方面是一个新手,所以很抱歉提出一个非常基本的问题:有没有一种方法(例如在C/ c++中)以代码易读性为代价,避免长if链产生那种“金字塔式代码”?
我知道如果我们可以跳过executeThisFunctionInAnyCase函数调用,代码可以简化为:
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
但是约束是executeThisFunctionInAnyCase函数调用。
break语句可以以某种方式使用吗?
在这种情况下,老式的C程序员使用goto。这是goto的一种用法,实际上是Linux样式指南鼓励的,它被称为集中函数exit:
int foo() {
int result = /*some error code*/;
if(!executeStepA()) goto cleanup;
if(!executeStepB()) goto cleanup;
if(!executeStepC()) goto cleanup;
result = 0;
cleanup:
executeThisFunctionInAnyCase();
return result;
}
有些人使用goto的方法是将body包装成一个循环并将其断开,但实际上这两种方法做的是同一件事。如果你只在executeStepA()成功时才需要一些其他的清理,那么goto方法会更好:
int foo() {
int result = /*some error code*/;
if(!executeStepA()) goto cleanupPart;
if(!executeStepB()) goto cleanup;
if(!executeStepC()) goto cleanup;
result = 0;
cleanup:
innerCleanup();
cleanupPart:
executeThisFunctionInAnyCase();
return result;
}
使用循环方法,在这种情况下,您将得到两级循环。
另一种解决方案是通过宏hack定义习语。
#define block for(int block = 0; !block; block++)
现在,“block”可以用break退出,与for(;;)和while()循环的方式相同。例子:
int main(void) {
block {
if (conditionA) {
// Do stuff A...
break;
}
if (conditionB) {
// Do stuff B...
break;
}
if (conditionC) {
// Do stuff C...
break;
}
else {
// Do default stuff...
}
} /* End of "block" statement */
/* ---> The "break" sentences jump here */
return 0;
}
尽管使用了“for(;;)”结构,但“block”语句只执行了一次。
这些“块”可以用断句退出。
因此,if else if else if…避免使用句子。
最多,最后一个else可以挂在“块”的末尾,以处理“默认”情况。
该技术旨在避免典型的和丑陋的做{…} while(0)方法。
在宏块中,它定义了一个同样名为block的变量,该变量以这样一种方式定义,即恰好执行了一次for迭代。根据宏的替换规则,宏块定义中的标识符块不会被递归替换,因此block成为程序员无法访问的标识符,但在内部可以很好地控制for(;;)循环的“隐藏”。
此外:这些“块”可以嵌套,因为隐藏变量int块将有不同的作用域。
关于当前的代码示例,本质上是第二个问题,
[...block of code...]
bool conditionA = executeStepA();
if (conditionA){
[...block of code...]
bool conditionB = executeStepB();
if (conditionB){
[...block of code...]
bool conditionC = executeStepC();
if (conditionC){
...other checks again...
}
}
}
executeThisFunctionInAnyCase();
除了将函数结果存储在变量中之外,这是典型的C代码。
如果布尔函数导致信号失败,那么c++的方法是使用异常,并将其编码为
struct Finals{ ~Finals() { executeThisFunctionInAnyCase(); } };
Finals finals;
// [...block of code...]
executeStepA();
// [...block of code...]
executeStepB();
// [...block of code...]
executeStepC();
//...other checks again...
然而,根据实际问题的不同,细节可能会有很大差异。
当我需要这样的通用最终操作时,我通常使用通用范围保护类,而不是当场定义一个自定义结构。作用域保护是由Petru Marginean为c++ 98发明的,然后使用临时生命周期扩展技巧。在c++ 11中,一般的范围保护类可以基于提供lambda表达式的客户端代码简单地实现。
在问题的最后,你建议了一个很好的C方法来做到这一点,即使用break语句:
for( ;; ) // As a block one can 'break' out of.
{
// [...block of code...]
if( !executeStepA() ) { break; }
// [...block of code...]
if( !executeStepB() ) { break; }
// [...block of code...]
if( !executeStepC() ) { break; }
//...other checks again...
break;
}
executeThisFunctionInAnyCase();
或者,对于C,将代码块中的代码重构为一个单独的函数,并使用return而不是break。因为它支持嵌套循环或开关,所以这更清楚也更通用。然而,你问的是休息。
与基于异常的c++方法相比,这种方法依赖于程序员记得检查每个函数的结果,并做正确的事情,这两者在c++中都是自动化的。