在 C++ 中使用 continue 关键字的另一种方法
Posted
技术标签:
【中文标题】在 C++ 中使用 continue 关键字的另一种方法【英文标题】:Another way to use continue keyword in C++ 【发布时间】:2010-09-29 07:29:19 【问题描述】:最近我们发现了一个使用 continue 注释掉代码行的“好方法”:
for(int i=0; i<MAX_NUM; i++)
....
.... //--> about 30 lines of code
continue;
....//--> there is about 30 lines of code after continue
....
我问前一个开发人员为什么将 continue 关键字放在密集循环中,这让我摸不着头脑。很可能是他/她觉得放置“继续”关键字比删除所有不需要的代码更容易......
通过查看以下场景,它引发了我另一个问题:
场景 A:
for(int i=0; i<MAX_NUM; i++)
....
if(bFlag)
continue;
....//--> there is about 100 lines of code after continue
....
场景 B:
for(int i=0; i<MAX_NUM; i++)
....
if(!bFlag)
....//--> there is about 100 lines of code after continue
....
你觉得哪个最好?为什么? break 关键字呢?
【问题讨论】:
使用 continue 而不是注释掉不需要的代码是邪恶的! 现在你知道他被“提升”为经理的原因了。 我能想到的让未使用的代码无法访问的唯一半好处是,如果它只是暂时未使用,并且您想确保它仍然可以编译。例如,检查它是否更新了它调用的函数签名的任何更改。如果你把它注释掉,那就不会了。但这是一个非常微不足道的好处——你没有测试代码,所以你只是半信半疑地认为它仍然有效。由于您在重新启用它时必须对其进行测试,因此您也可以修复它以进行编译。而且你把它这样放得越久,它就越有可能被巧妙地破坏。 你们让我意识到了使用 continue 关键字的正确方法。以前从来没有考虑过。感谢大家的建议! 【参考方案1】:在这种情况下使用continue
可以大大减少嵌套并且通常使代码更具可读性。
例如:
for(...)
if( condition1 )
Object* pointer = getObject();
if( pointer != 0 )
ObjectProperty* property = pointer->GetProperty();
if( property != 0 )
///blahblahblah...
变成了
for(...)
if( !condition1 )
continue;
Object* pointer = getObject();
if( pointer == 0 )
continue;
ObjectProperty* property = pointer->GetProperty();
if( property == 0 )
continue;
///blahblahblah...
你看 - 代码变成线性而不是嵌套。
您可能还会发现this closely related question 的答案很有帮助。
【讨论】:
很好......我喜欢你的例子,因为使用 continue 的好处。这类似于优先切换多个 if()。 除了在大多数情况下,如果你有很多 if 语句,函数指针的查找表会比 a switch 更清晰,更易于阅读和维护。 Clang 编码准则也支持“提前返回”风格以减少缩进:提前返回、大量使用 continue/break 等... 很好的例子——很难反驳这种用法。 +1。如果不小心的话,我不能同意这一点:它只有在“if->continue”子句简单且接近循环头部(或控制范围)的情况下才有效。那是在过滤“在这里做任何事情是否合理?”的情况下。案例。想象一下,在您的示例中,只有一个条件代码部分很大,超出了屏幕页面左右。没有人期望继续散布不明显的地方,因为它们是调试的魔鬼。如果您使用中断/继续/返回来构建复杂的流程,那么使用您的代码的人会讨厌您。是的,一旦你进入调试阶段,那个人就是你!【参考方案2】:对于您的第一个问题,这可能是一种跳过代码而不将其注释掉或删除的方式。我不建议这样做。如果您不希望您的代码被执行,请不要在其前面加上 continue/break/return,因为这会在您/其他人查看代码时引起混乱,并可能被视为错误。
至于您的第二个问题,它们在性能方面基本相同(取决于汇编输出),并且很大程度上取决于设计。这取决于您希望代码读者将其“翻译”成英文的方式,就像大多数人在回读代码时所做的那样。
因此,第一个示例可能是“Do blah, blah, blah. If (expression), continue on the next iteration。” 而第二个可能是“Do blah, blah, blah. If (expression), do blah, blah, blah"
因此,使用 if 语句的 continue 可能会削弱其后代码的重要性。
在我看来,如果可以的话,我更喜欢 continue,因为它会减少嵌套。
【讨论】:
【参考方案3】:我讨厌注释掉未使用的代码。我所做的是,
我将它们完全删除,然后签入版本控制。
源代码控制发明后,谁还需要注释掉未使用的代码?
【讨论】:
在大多数情况下我会同意这一点,但要有所保留......如果您不定期关注您的 SCM,您可能会错过一些不那么未使用的消失代码,然后花一些时间弄清楚并意识到您需要检查早期版本。我通常做的是首先用任务标签(TODO、TOREVIEW、TODELETE)、名称标签(在不查看 SCM 的情况下跟踪作者)和时间戳(相同的原因)评论事物,并附上评论事物的理由出去。然后下次我浏览这段代码时,如果在会议上没有提出任何关于它的内容,我会删除它。 @haylem,许多现代 IDE 可以检测未使用的代码。尽管在 C++ 中它可能不如例如可靠。在 Java 中,它仍然是一种辅助。此外,当您看到此类代码时,为什么不立即询问一位(或多位)开发人员以确认可以安全删除它?最后(但并非最不重要),通过在每次提交后自动运行一组良好的单元/集成测试,您可以更加大胆:-) 我显然试图询问我的开发人员,但它是在 5 多年前由离开公司的人实施的,在某些情况下我们甚至不知道它是谁,这并不罕见是因为 SCM 之间的代码库迁移出错了。否则,我和你的原则是保持快速和干净。【参考方案4】:continue
的“评论”使用与 goto 一样滥用:-)。放置#if 0/#endif
或/*...*/
非常容易,然后许多编辑器会对注释代码进行颜色编码,因此很明显它没有被使用。 (我有时喜欢#ifdef USE_OLD_VERSION_WITH_LINEAR_SEARCH
,所以我知道那里还剩下什么,因为对我来说很明显,如果我真的希望有人在编译期间定义它,我永远不会有这么愚蠢的宏名称......我猜我会如果我在那种状态下共享代码,则必须向团队解释这一点。)其他答案指出源代码控制系统允许您简单地删除注释代码,虽然这是我在提交之前的做法 - 通常有一个“工作”阶段,其中你想要它以最大限度地方便交叉引用、复制粘贴等。
对于场景:实际上,除非您的项目具有需要适应的一致方法,否则您使用哪一种并不重要,因此我建议使用在这种情况下看起来更具可读性/表现力的任何一种。在较长的代码块中,单个 continue 可能不太明显,因此不太直观,而其中一组 - 或许多分散在循环中 - 更难错过。过度嵌套的代码也会变得丑陋。 因此,如果不确定,请选择其中一个,如果替代方案开始看起来很有吸引力,请更改它。
它们也向读者传达了微妙不同的信息:continue
的意思是“嘿,排除所有这些情况,然后查看下面的代码”,而 if 块意味着您必须“推送”一个上下文但仍然有当您尝试了解循环内部的其余部分时,所有这些都在您的脑海中(在这里,只找到 if 紧跟在循环终止之后,因此所有的精神努力都被浪费了。针对这一点,继续语句往往会触发精神检查确保在下一次循环迭代之前所有必要的步骤都已完成——这一切都与接下来的任何内容一样有效,如果有人说在循环底部添加了额外的增量或调试语句,那么他们必须知道有他们可能还想处理的 continue 语句。
您甚至可以根据测试的简单程度来决定使用哪个测试,就像一些程序员会使用早期返回语句来处理异常错误情况,但会使用“结果”变量和结构化编程来处理预期的流程。一切都会变得一团糟——编程必须至少和问题一样复杂——你的工作就是让它比这更混乱/更复杂。
为了提高工作效率,重要的是要记住“不要为小事出汗”,但在 IT 中,学习小事可能是一种痛苦:-)。
除此之外:您可能会发现对结构化编程的优缺点进行一些背景阅读很有用,其中涉及单个入口/出口点、goto 等。
【讨论】:
【参考方案5】:我同意其他回答者的观点,即第一次使用 continue
是不好的。 应该删除未使用的代码(如果您以后仍然需要它,您总是可以从您的 SCM 中找到它 - 您确实使用了 SCM,对吗?:-)
第二,一些答案强调可读性,但我错过了一个重要的事情:IMO 的第一步应该是将这 100 行代码提取到一个或多个单独的方法中。之后,循环变得更短更简单,执行流程变得明显。如果我可以将代码提取到单个方法中,我个人更喜欢if
:
for(int i=0; i<MAX_NUM; i++)
....
if(!bFlag)
doIntricateCalculation(...);
但是continue
对我来说几乎同样适用。事实上,如果这100行代码中有多个continue
s / return
s / break
s,是不可能将其提取到一个方法中的,那么重构可能会导致一系列continue
s 和方法调用:
for(int i=0; i<MAX_NUM; i++)
....
if(bFlag)
continue;
SomeClass* someObject = doIntricateCalculation(...);
if(!someObject)
continue;
SomeOtherClass* otherObject = doAnotherIntricateCalculation(someObject);
if(!otherObject)
continue;
// blah blah
【讨论】:
+0.5 建议重构并提取到另一种方法,+0.5 建议使用 !bFlag 子句继续使用。 +1 建议使用函数。函数必须简短、可读、易懂且名称清晰。 @Stephane:像那些提议的函数通常只从一个地方调用,短名称是低优先级。 @Péter:那些 if/continues 仍然很丑:-)...如果只有 C++ 允许在 if 中声明多个变量,而不仅仅是一个,那么我们可以使用if ((SomeClass* someObject = ...) and (SomeOtherClass* otherObject = ...)) ...
或等效的 if (!(SomeClass* someObject = ...) or !(...)) continue;
。照原样,可以考虑离域SomeClass* someObject; SomeOtherClass* otherObject;
然后if (!(someObject = ...) or !(otherObject = ...))
。各种口味。
@Tony,不是函数名,而是函数 body 应该很短 :-) 对我来说,可读性是总体上最关心的问题。大多数情况下,只从一个地方调用一个函数应该不是问题——除非你能证明它会导致性能问题。然后你还有inline
。
@Stephane / @Péter:我很抱歉......完全误读了 Stephane 的评论。是的...短函数很好,短标识符不一定。在提到该函数可能只从一个地方调用时,我只是认为必须键入更长的函数标识符不是问题。我认为我们都同意...我只是没有意识到之前 :-<.>【参考方案6】:
continue
在高复杂度的 for 循环中很有用。使用它来注释掉循环的剩余代码,即使是临时调试也是不好的做法,因为人们往往会忘记......
【讨论】:
【参考方案7】:首先考虑可读性,这将使您的代码更易于维护。使用continue
语句对用户来说很清楚:在这种情况下,我无法/想要对这个元素做任何其他事情,忘记它并尝试下一个。另一方面,if
只是告诉下一个代码块不适用于不满足条件的代码块,但如果块足够大,您可能不知道实际上是否还有其他代码这将适用于这个特定的元素。
出于这个特殊原因,我倾向于使用continue
而不是 if。它更明确地说明了意图。
【讨论】:
我实际上不同意这一点(大部分时间)。虽然 continue 语句很清楚,但 if 会导致程序流程中断,使其更难遵循,而排除 if 语句更容易在心理上处理。它会增加一些级别的范围,但正如 Péter Török 所提到的,那么您很可能想要提取代码的犯罪部分。以上是关于在 C++ 中使用 continue 关键字的另一种方法的主要内容,如果未能解决你的问题,请参考以下文章
如何使用算法将一种类型的列表映射到现代 C++ 中的另一种类型的列表
[C++多线程]1.3-多线程控制的另一种姿势-条件变量(condition_variable), 信号量(semaphore)
[C++多线程]1.3-多线程控制的另一种姿势-条件变量(condition_variable), 信号量(semaphore)