我已经用Java编程好几年了,但我最近才回到学校获得一个正式学位。我很惊讶地发现,在我的上一个作业中,我因为使用下面这样的循环而失分。
do{
//get some input.
//if the input meets my conditions, break;
//Otherwise ask again.
} while(true)
现在对于我的测试,我只是扫描一些控制台输入,但我被告知这种循环是不鼓励的,因为使用break类似于goto,我们只是不这样做。
我完全理解goto和它的Java兄弟break:label的陷阱,而且我很明智地不使用它们。我也意识到,一个更完整的程序会提供一些其他的逃避方式,比如结束程序,但我的教授并没有提到这一点,所以……
do-while(正确)怎么了?
我不会说这很糟糕——但同样地,我通常至少会寻找另一种选择。
如果这是我写的第一件事,我几乎总是至少试着把它重构成更清晰的东西。有时这是没有办法的(或者另一种选择是使用bool变量,它除了指示循环的结束之外没有任何意义,比break语句更不清楚),但至少值得一试。
作为一个使用break比使用flag更清楚的例子,考虑:
while (true)
{
doStuffNeededAtStartOfLoop();
int input = getSomeInput();
if (testCondition(input))
{
break;
}
actOnInput(input);
}
现在让我们强制它使用一个标志:
boolean running = true;
while (running)
{
doStuffNeededAtStartOfLoop();
int input = getSomeInput();
if (testCondition(input))
{
running = false;
}
else
{
actOnInput(input);
}
}
我认为后者读起来更复杂:它有一个额外的else块,actOnInput更缩进,如果您试图弄清楚当testCondition返回true时发生了什么,您需要仔细检查块的其余部分,以检查else块之后是否有什么东西会发生,无论running是否被设置为false。
break语句更清楚地传达了意图,并让块的其余部分继续做它需要做的事情,而不必担心前面的条件。
请注意,这与人们对方法中多个return语句的争论完全相同。例如,如果我可以在前几行内计算出一个方法的结果(例如,因为一些输入是空的,或者是空的,或者是零),我发现直接返回答案比用一个变量来存储结果,然后是一整个代码块,最后是一个返回语句更清楚。
通常用于读取输入的Java约定是:
import java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String strLine;
while ((strLine = br.readLine()) != null) {
// do something with the line
}
通常c++读取输入的约定是:
#include <iostream>
#include <string>
std::string data;
while(std::readline(std::cin, data)) {
// do something with the line
}
在C语言中,它是
#include <stdio.h>
char* buffer = NULL;
size_t buffer_size;
size_t size_read;
while( (size_read = getline(&buffer, &buffer_size, stdin)) != -1 ){
// do something with the line
}
free(buffer);
或者如果你确信你知道你的文件中最长的文本行有多长,你可以这样做
#include <stdio.h>
char buffer[BUF_SIZE];
while (fgets(buffer, BUF_SIZE, stdin)) {
//do something with the line
}
如果您正在测试用户是否输入了退出命令,那么很容易扩展这3个循环结构中的任何一个。我用Java写一下
import java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = br.readLine()) != null && !line.equals("quit") ) {
// do something with the line
}
因此,虽然在某些情况下break或goto是合理的,但如果您所做的只是一行一行地从文件或控制台读取,那么您不应该需要while (true)循环来完成它——您的编程语言已经为您提供了使用input命令作为循环条件的适当习惯用法。
Douglas Crockford说过,他多么希望JavaScript包含一个循环结构:
loop
{
...code...
}
我也不认为Java会因为拥有循环结构而变得更糟糕。
while(true)循环本身并没有什么错,但是老师们倾向于不鼓励它们。从教学的角度来看,很容易让学生创造出无穷无尽的循环,而不理解为什么循环没有被转义。
但是他们很少提到的是,所有的循环机制都可以用while(true)循环复制。
while( a() )
{
fn();
}
和
loop
{
if ( !a() ) break;
fn();
}
and
do
{
fn();
} while( a() );
等于:
loop
{
fn();
if ( !a() ) break;
}
and
for ( a(); b(); c() )
{
fn();
}
等于:
a();
loop
{
if ( !b() ) break;
fn();
c();
}
只要你能以一种工作的方式设置你的循环,你选择使用的结构是不重要的。如果它恰好适合for循环,则使用for循环。
最后一点:保持循环的简单性。如果在每次迭代中都需要执行许多功能,那么就将其放入一个函数中。在你让它工作之后,你总是可以优化它。
在break语句中使用while(正确)没有大问题,但是有些人可能会认为它会略微降低代码的可读性。试着给变量起个有意义的名字,在合适的位置求值表达式。
对于你的例子,这样做似乎更清楚:
do {
input = get_input();
valid = check_input_validity(input);
} while(! valid)
如果do while循环变长,尤其如此——您确切地知道在哪里检查是否发生了额外的迭代。在抽象级别上,所有变量/函数都有适当的名称。while(true)语句的作用是告诉你,处理不在你认为的地方。
也许你想在第二次循环时得到不同的输出。类似的
input = get_input();
while(input_is_not_valid(input)) {
disp_msg_invalid_input();
input = get_input();
}
这样我就更容易理解了
do {
input = get_input();
if (input_is_valid(input)) {
break;
}
disp_msg_invalid_input();
} while(true);
同样,通过一个简单的例子,两者都很容易理解;但是如果循环变得非常大或嵌套很深(这意味着您可能已经重构过了),那么第一种样式可能会更清晰一些。
早在1967年,Edgar Dijkstra在一份行业杂志上写了一篇文章,讲述了为什么应该从高级语言中删除goto以提高代码质量。一种叫做“结构化编程”的编程范式由此而来,尽管当然不是每个人都同意goto自动意味着糟糕的代码。
结构化编程的关键本质上是代码的结构应该决定它的流,而不是在任何可能的情况下使用goto或break或continue来决定流。类似地,在该范例中也不鼓励有多个循环或函数的入口和出口点。
显然,这不是唯一的编程范式,但通常它可以很容易地应用到其他范式,如面向对象编程(比如Java)。
你的老师可能已经被教导过,并且正试图教导你的班级,我们最好通过确保我们的代码是结构化的,并遵循结构化编程的隐含规则来避免“意大利面条代码”。
While there is nothing inherently "wrong" with an implementation that uses break, some consider it significantly easier to read code where the condition for the loop is explicitly specified within the while() condition, and eliminates some possibilities of being overly tricky. There are definitely pitfalls to using a while(true) condition that seem to pop up frequently in code by novice programmers, such as the risk of accidentally creating an infinite loop, or making code that is hard to read or unnecessarily confusing.
具有讽刺意味的是,异常处理是一个与结构化编程有偏差的领域,并且随着您对Java编程的深入了解,这种偏差肯定会出现。
也有可能你的老师希望你展示你使用特定循环结构或语法的能力,而你写的代码在功能上是等价的,你可能没有展示你应该在那一课中学习的特定技能。
这是你的枪,你的子弹和你的脚…
说它不好是因为你在自找麻烦。你或本页上的任何其他海报都不会有简短的while循环的例子。
麻烦将在未来某个非常随机的时间开始。可能是其他程序员造成的。可能是安装软件的人。可能是最终用户。
为什么?我必须找出为什么700K的LOC应用程序会逐渐开始消耗100%的CPU时间,直到每个CPU都饱和。这是一个神奇的while (true)循环。这件事又大又恶心,但归结起来就是:
x = read_value_from_database()
while (true)
if (x == 1)
...
break;
else if (x ==2)
...
break;
and lots more else if conditions
}
没有最后的其他分支。如果值不匹配If条件,循环将一直运行到时间结束。
当然,程序员指责最终用户没有选择程序员期望的值。(然后我在代码中删除了while(true)的所有实例。)
恕我直言,使用while(true)这样的结构并不是很好的防御性编程。它会回来困扰你的。
(但我确实记得,如果我们不评论每一行,教授就会扣分,即使是i++;)