Bjarne Stroustrup(C ++创建者)曾说过他避免使用“do / while”循环,而更喜欢用“while”循环来编写代码。 [见下面的引用。]
自从听到这个以来,我发现这是真的。你的想法是什么?有没有一个例子,“do / while”比使用“while”更干净,更容易理解?
回答一些答案:是的,我理解“do / while”和“while”之间的技术差异。这是一个关于可读性和构造涉及循环的代码的更深层次的问题。
让我问另一种方式:假设你被禁止使用“do / while” - 是否有一个现实的例子,这会让你别无选择,只能使用“while”编写不洁的代码?
来自“The C ++ Programming Language”,6.3.3:
根据我的经验,do-statement是错误和混乱的根源。原因是它的主体总是在评估条件之前执行一次。然而,为了使身体正常工作,非常像条件的东西必须在第一次通过时才能保持。比我想象的更频繁,我发现在程序第一次编写和测试时,或者在修改之前的代码之后,条件不能按预期保持。 我也更喜欢“我可以看到它的前方”。因此,我倾向于避免做声明。 -Bjarne
答案 0 :(得分:86)
是的我同意while循环可以重写为while循环,但我不同意总是使用while循环更好。总是至少运行一次,这是一个非常有用的属性(最典型的例子是输入检查(从键盘))
#include <stdio.h>
int main() {
char c;
do {
printf("enter a number");
scanf("%c", &c);
} while (c < '0' || c > '9');
}
这当然可以重写为while循环,但这通常被视为一种更优雅的解决方案。
答案 1 :(得分:38)
do-while是一个带有后置条件的循环。在循环体至少执行一次的情况下需要它。这对于在可以合理评估循环条件之前需要一些动作的代码是必要的。使用while循环,您必须从两个站点调用初始化代码,而您只能从一个站点调用它。
另一个例子是当你要开始第一次迭代时已经有一个有效的对象,所以你不想在第一次迭代开始之前执行任何操作(包括循环条件评估)。一个例子是FindFirstFile / FindNextFile Win32函数:你调用FindFirstFile,它会向第一个文件返回一个错误或一个搜索句柄,然后你调用FindNextFile直到它返回一个错误。
伪代码:
Handle handle;
Params params;
if( ( handle = FindFirstFile( params ) ) != Error ) {
do {
process( params ); //process found file
} while( ( handle = FindNextFile( params ) ) != Error ) );
}
答案 2 :(得分:17)
do { ... } while (0)
是使宏表现良好的重要构造。
即使它在实际代码中不重要(我不一定同意),但对于修复预处理器的一些缺陷也很重要。
编辑:我遇到了一个情况,在我自己的代码中,今天do / while更加清洁。我正在对配对的LL/SC指令进行跨平台抽象。这些需要在循环中使用,如下所示:
do
{
oldvalue = LL (address);
newvalue = oldvalue + 1;
} while (!SC (address, newvalue, oldvalue));
(专家可能会发现旧的值在SC实现中未被使用,但它包含在内以便可以使用CAS模拟这种抽象。)
LL和SC是一个很好的例子,其中do / while比同等形式更清晰:
oldvalue = LL (address);
newvalue = oldvalue + 1;
while (!SC (address, newvalue, oldvalue))
{
oldvalue = LL (address);
newvalue = oldvalue + 1;
}
出于这个原因,我对Google Go的opted to remove the do-while construct感到非常失望。
答案 3 :(得分:7)
当你想“做”某事“直到”满足条件时,这是有用的。
它可以像这样在while循环中捏造:
while(true)
{
// .... code .....
if(condition_satisfied)
break;
}
答案 4 :(得分:6)
在我们的编码惯例中
所以我们几乎从来没有do {} while(xx)
这是因为:
int main() {
char c;
do {
printf("enter a number");
scanf("%c", &c);
} while (c < '0' || c > '9');
}
改写于:
int main() {
char c(0);
while (c < '0' || c > '9'); {
printf("enter a number");
scanf("%c", &c);
}
}
和
Handle handle;
Params params;
if( ( handle = FindFirstFile( params ) ) != Error ) {
do {
process( params ); //process found file
} while( ( handle = FindNextFile( params ) ) != Error ) );
}
改写于:
Params params(xxx);
Handle handle = FindFirstFile( params );
while( handle!=Error ) {
process( params ); //process found file
handle = FindNextFile( params );
}
答案 5 :(得分:6)
以下常见习语对我来说似乎非常简单:
do {
preliminary_work();
value = get_value();
} while (not_valid(value));
避免do
的重写似乎是:
value = make_invalid_value();
while (not_valid(value)) {
preliminary_work();
value = get_value();
}
第一行用于确保第一次测试始终评估为true 。换句话说,第一次测试总是多余的。如果没有这个多余的测试,也可以省略初始分配。此代码给人的印象是它会自己打架。
在像这样的情况下,do
构造是一个非常有用的选项。
答案 6 :(得分:6)
(假设你知道两者之间的区别)
在检查条件并运行while循环之前,Do / While适用于引导/预初始化代码。
答案 7 :(得分:5)
关于可读性的全部内容
更易读的代码可以减少代码维护和更好协作的麻烦
到目前为止,其他考虑因素(例如优化)在大多数情况下并不那么重要
我会详细说明,因为我在这里发表评论:
如果您有使用do { ... } while()
的代码段 A ,并且它比while() {...}
等效 B 更具可读性,那么我会投票给< EM> A 的。如果您更喜欢 B ,因为您看到“预先”循环条件,并且您认为它更具可读性(因此可维护等) - 然后继续前进,使用 B 。
我的观点是:使用对您的眼睛(以及您的同事)更具可读性的代码。当然,选择是主观的。
答案 8 :(得分:3)
在我看来,这只是个人选择。
大多数情况下,您可以找到一种方法将do ... while循环重写为while循环;但不一定总是。另外,有时候编写一个do while循环以适应你所处的上下文可能更具逻辑性。
如果你看一下TimW的回复,它说明了一切。第二个是Handle,在我看来特别麻烦。
答案 9 :(得分:3)
这是我所见过的最干净的选择。这是推荐用于Python的习惯用法,它没有do-while循环。
有一点需要注意的是,你不能在continue
中拥有<setup code>
,因为它会跳过休息状态,但是没有一个显示do-while好处的例子需要继续条件。
while (true) {
<setup code>
if (!condition) break;
<loop body>
}
这里它适用于上面的do-while循环的一些最好的例子。
while (true); {
printf("enter a number");
scanf("%c", &c);
if (!(c < '0' || c > '9')) break;
}
下一个例子是结构比do-while更可读的情况,因为条件保持在顶部附近,因为//get data
通常很短但//process data
部分可能很长。 / p>
while (true); {
// get data
if (data == null) break;
// process data
// process it some more
// have a lot of cases etc.
// wow, we're almost done.
// oops, just one more thing.
}
答案 10 :(得分:2)
回答来自未知(谷歌)的问题/评论以及Dan Olson的回答:
“do {...} while(0)是使宏运行良好的重要构造。”
#define M do { doOneThing(); doAnother(); } while (0)
...
if (query) M;
...
你看到没有do { ... } while(0)
会发生什么吗?它将始终执行doAnother()。
答案 11 :(得分:2)
我几乎没有因为以下因素而使用它们:
即使循环检查后置条件,您仍需要在循环中检查此后置条件,以便不处理后置条件。
获取示例伪代码:
do {
// get data
// process data
} while (data != null);
理论上听起来很简单,但在现实世界的情况下,它可能看起来像这样:
do {
// get data
if (data != null) {
// process data
}
} while (data != null);
额外的“如果”检查是不值得的IMO。我发现很少有实例做一个do-while而不是while循环。 YMMV。
答案 12 :(得分:2)
阅读Structured Program Theorem。 do {} while()总是可以重写为while()do {}。序列,选择和迭代都是需要的。
因为循环体中包含的内容总是可以封装到例程中,所以必须使用while()的肮脏需要永远不会比
更糟糕LoopBody()
while(cond) {
LoopBody()
}
答案 13 :(得分:2)
do-while循环总是可以重写为while循环。
是否仅使用while循环,while,do-while和for循环(或其任何组合)在很大程度上取决于您对美学的品味以及您正在进行的项目的惯例。
就个人而言,我更喜欢while循环,因为它简化了关于循环不变量IMHO的推理。
是否存在需要do-while循环的情况:而不是
do
{
loopBody();
} while (condition());
你总是可以
loopBody();
while(condition())
{
loopBody();
}
所以,不,如果由于某些原因你不能,你永远不需要使用do-while。 (当然这个例子违反了DRY,但它只是一个概念验证。根据我的经验,通常有一种方法可以将do-while循环转换为while循环,而不是在任何具体的用例中违反DRY。)< / p>
“在罗马时,像罗马人一样。”
BTW:你要找的报价可能是这个([1],6.3.3节的最后一段):
根据我的经验,do-statement是错误和混乱的根源。原因是它的主体总是在测试条件之前执行一次。然而,为了身体的正确运作,最终条件的类似条件必须在第一次运行中保持。比我预期的更多,我发现这些条件并非如此。当我从头开始编写有问题的程序然后测试它以及更改代码之后,就是这种情况。此外,我更喜欢条件“预先,我可以看到它”。因此,我倾向于避免做声明。
(注意:这是我对德语版的翻译。如果您碰巧拥有英文版,请随意编辑报价以符合他原来的措辞。不幸的是,Addison-Wesley讨厌谷歌。)
[1] B. Stroustrup: C ++编程语言。第3版。 Addison-Wessley,Reading,1997。
答案 14 :(得分:2)
首先,我同意do-while
的可读性低于while
。
但令我惊讶的是,经过这么多答案,没有人考虑过为什么do-while
甚至存在于语言中。原因是效率。
假设我们有一个带有do-while
条件检查的N
循环,其中条件的结果取决于循环体。然后,如果我们用while
循环替换它,我们会得到N+1
条件检查,其中额外检查是没有意义的。如果循环条件只包含一个整数值的检查,那就没什么大不了的了,但我们可以说
something_t* x = NULL;
while( very_slowly_check_if_something_is_done(x) )
{
set_something(x);
}
然后循环第一圈的函数调用是多余的:我们已经知道x
尚未设置为任何东西。为什么要执行一些毫无意义的开销代码?
在对实时嵌入式系统进行编码时,我经常使用do-while,其中条件内的代码相对较慢(检查来自某些慢速硬件外设的响应)。
答案 15 :(得分:1)
考虑这样的事情:
int SumOfString(char* s)
{
int res = 0;
do
{
res += *s;
++s;
} while (*s != '\0');
}
恰好'\ 0'为0,但我希望你明白这一点。
答案 16 :(得分:1)
/ 我的问题 严格来说是在C中的实现。由于重复使用 while 关键字,经常让人们吵架,因为它看起来像是一个错误。
如果 只为 保留,而 循环且 执行 / ,而 已更改为 执行 / 直到< / em> 或 重复 / 直到 ,我不认为循环(这是当然,方便和编码某些循环的“正确”方法会造成很多麻烦。
I've ranted before about this in regards to JavaScript,这也继承了C的这个令人遗憾的选择。
答案 17 :(得分:1)
好吧也许这可以追溯到几步,但是在
的情况下do
{
output("enter a number");
int x = getInput();
//do stuff with input
}while(x != 0);
虽然不一定可以使用
,但这是可能的int x;
while(x = getInput())
{
//do stuff with input
}
现在,如果你想使用0以外的数字来退出循环
while((x = getInput()) != 4)
{
//do stuff with input
}
但同样,可读性也有所下降,更不用说在条件中使用赋值语句被认为是不好的做法,我只是想指出有更简洁的方法来执行它而不是分配“保留”值,表示循环是初始运行。
答案 18 :(得分:0)
我喜欢DavidBožjak的榜样。然而,为了扮演魔鬼的拥护者,我觉得你总是可以将你想要运行的代码至少分解为一个单独的函数,从而实现最可读的解决方案。例如:
int main() {
char c;
do {
printf("enter a number");
scanf("%c", &c);
} while (c < '0' || c > '9');
}
可能会成为这个:
int main() {
char c = askForCharacter();
while (c < '0' || c > '9') {
c = askForCharacter();
}
}
char askForCharacter() {
char c;
printf("enter a number");
scanf("%c", &c);
return c;
}
(原谅任何不正确的语法;我不是C程序员)