我听说一些“休息”并不是坏习惯。这个呢?

发布于 2024-09-11 19:42:30 字数 1050 浏览 10 评论 0原文

我经常听说在 Java 中使用 break 被认为是不好的做法,但在阅读了 Stack Overflow 上的一些帖子后,我发现情况并非如此。许多人说在某些情况下这是可以接受的。

在这种情况下,我有点困惑什么是/不是不好的做法。

对于 Project Euler:问题 7,我构建了下面的代码。挑战是找到第 10001 个素数。

int index = 2, count = 1, numPrime = 1;

while (true) {
    index++;

    if (isPrime(index)) {
        count = index;
        numPrime++;
    }

    if (numPrime >= 10001)
        break;
}

System.out.println(count);

这会返回正确的答案(21 毫秒内),但我是否忽略了一个严重的警告?创建一个没有中断的 while 循环是 100% 可能的,但我发现这更容易理解。

我使用 break; 的方式是不好的做法吗?我知道总有办法使用它,但它真的有那么糟糕吗?

非常感谢

Justian

编辑

这是我的 isPrime() 代码。当我这样做的时候,我不妨优化一下。

public static boolean isPrime(long num) {  
    if (num == 2)
        return true;

    if (num % 2 == 0 || num <= 0)
        return false;

    for (long i = 3; i * i <= num; i += 2)
        if (num % i == 0)
            return false;

    return true;
}

I have often heard that using breaks in Java is considered bad practice, but after reading some threads on Stack Overflow, I've seen otherwise. Many say that it is acceptable in certain cases.

I'm a little confused as to what is/isn't bad practice in this case.

For Project Euler: Problem 7, I've constructed the code below. The challenge was to find the 10001st prime.

int index = 2, count = 1, numPrime = 1;

while (true) {
    index++;

    if (isPrime(index)) {
        count = index;
        numPrime++;
    }

    if (numPrime >= 10001)
        break;
}

System.out.println(count);

This returns the correct answer (in 21ms), but am I overlooking a serious warning? It's 100% possible to create a while loop without a break, but I find that this is a little easier to follow.

Is the way I use the break; bad practice? I know that there's always a way around using one, but is it really that terrible here?

Many thanks

Justian

EDIT

Here's my isPrime() code. I might as well optimize this while I'm at it.

public static boolean isPrime(long num) {  
    if (num == 2)
        return true;

    if (num % 2 == 0 || num <= 0)
        return false;

    for (long i = 3; i * i <= num; i += 2)
        if (num % i == 0)
            return false;

    return true;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

风筝在阴天搁浅。 2024-09-18 19:42:31

在这种情况下,在我看来,更改 while 条件会更容易:

while (numPrime < 10001) {

结束时,通常就是这种情况。

if (condition)
{
    break;
}

while(true) 循环以... 尽管您需要检查循环体中的其他内容是否执行继续

或者,您可以稍微重组它:

int current = 1;
for (int i = 0; i < 10001; i++)
{
    current++;
    while (!isPrime(current))
    {
        current++;
    }
}

然后 current 将是最后的答案。

当您尝试执行特定次数的某件事时,我通常更喜欢 for 循环而不是 while 循环。在这种情况下,“某事”是“找到下一个素数”。

我觉得编程中的一些教条太过分了——包括“方法的一个出口点”和“不要使用break”。尽可能编写可读性强的代码。如果您查看某些代码并感觉其中发生的情况并不那么明显,请尝试找出其他构造它的方法。有时这是改变循环的情况;有时是提取方法;有时它会反转一些逻辑(首先处理负分支,可能提前退出,然后处理正常情况)。

In this case, it looks to me like it would be easier just to change the while condition:

while (numPrime < 10001) {

That's usually the case when a while(true) loop ends with

if (condition)
{
    break;
}

... although you need to check whether anything else in the body of the loop performs a continue.

Alternatively, you could restructure it slightly:

int current = 1;
for (int i = 0; i < 10001; i++)
{
    current++;
    while (!isPrime(current))
    {
        current++;
    }
}

Then current will be the answer at the end.

I generally prefer a for loop over a while loop, when you're trying to do something a particular number of times. In this case the "something" is "find the next prime".

There are various bits of dogma in programming that I feel are taken too far - including "one exit point to a method" and "don't use break." Write code as readably as you can. If you look at some code and feel it's not blindingly obvious what's going on, try to work out other ways of structuring it. Sometimes that's a case of changing a loop; sometimes it's extracting a method; sometimes it's inverting some logic (deal with a negative branch first, possibly exiting early, and then handle the normal case).

ゃ人海孤独症 2024-09-18 19:42:31

如果你可以不休息地离开,那就去做吧。

 while (numPrime < 10001) ...

if you can get away without a break, then do it.

do

 while (numPrime < 10001) ...
玉环 2024-09-18 19:42:31

有几个条件才需要使用中断。一种是当您需要执行 N.5 个循环时——即,您将执行循环多次,但最后一次总是在循环体中间的某个位置执行。在这种情况下,您可以避免使用中断,但这样做通常会混淆代码或导致重复。例如:

while (true) {
    first part;
    if (finished) 
       break;
    second part;
}

可以变成类似:

first part;
while (!finished) {
    second part;
    first part;
}

或:

while (!finished) {
    first part;
    if (!finished)
        second part;
}

这些都不一定是重大改进。中断有意义的另一种情况是简单地处理错误之类的事情。例如,如果您已传递 N 个文件来处理,那么如果其中一个文件无法打开,则退出循环可能是有意义的。尽管如此,当它完全合理时,显然最好在循环条件中明确声明退出循环的条件。

There are a couple of conditions when it makes sense to use a break. One is when you need to execute N.5 loops -- i.e., you'll execute the loop some number of times, but the last time through you'll always execute somewhere in the middle of the loop body. You can avoid using a break in this case, but doing so often obfuscates the code or results in duplication. For example:

while (true) {
    first part;
    if (finished) 
       break;
    second part;
}

can be turned into something like:

first part;
while (!finished) {
    second part;
    first part;
}

or:

while (!finished) {
    first part;
    if (!finished)
        second part;
}

Neither of these is necessarily a major improvement. Another circumstance under which a break can make sense is simply dealing with something like an error. For example, if you've been passed N files to process, it may make sense to break out of the loop if one of them fails to open. Nonetheless, when it's at all reasonable, it's clearly better to have the condition under which you'll exit from the loop explicitly stated in the loop's condition.

灯角 2024-09-18 19:42:31

这就是 do/while 的发明目的:

do {
//...
} while(numPrime < 10001);

我发现 while(true) 位是不好的做法,这当然会导致 break

That is what do/while was invented for:

do {
//...
} while(numPrime < 10001);

It's the while(true) bit that I find bad practice, which of course leads to break.

椵侞 2024-09-18 19:42:31

与之前所说的没有太大不同,但从可读性、透明逻辑的角度来看,我建议

long i,primes=0;
for (i=2;primes<10001;i++) {
    if (isPrime(i))
        primes++;
}

i 是答案。

Not hugely different than anything that has been said before, but from readablity, transparent logic stand point I would recommend

long i,primes=0;
for (i=2;primes<10001;i++) {
    if (isPrime(i))
        primes++;
}

i is the answer.

情愿 2024-09-18 19:42:31

如果只有一种条件可以退出循环,并且在检查条件之前不需要运行大量代码,并且在检查条件之后不需要运行大量代码,将条件放入循环中。

我确实认为“do{}while(1);”肯定有用途。环形。其中:

  1. 在循环继续的情况下,主要条件需要在检查之前和之后都有足够的代码运行,将代码放入条件本身充其量是很尴尬的。
  2. 该循环具有多个退出条件,并且逻辑上最可能位于循环顶部或底部的退出条件将需要执行特殊代码,而这些代码不应针对其他退出条件执行。

有些人喜欢使用标志来区分退出条件。我倾向于将此类标志视为避免使用实际上最能体现程序操作的编码结构的努力。

如果不太关心速度,可以避免使用任何形式的 goto、过早退出 return,或者为此使用多个 while--并且有一个简单的条件。只需将整个程序编写为状态机:

void do_whatever(void)
{
  int current_state=1;
  do
  {
    next_state = 0;
    if (current_state == 1)
    {
      do_some_stuff();
      next_state = 2;
    }
    if (current_state == 2)
    {
      do_some_more_stuff();
      next_state = 3;
    }
    ...
    current_state = next_state;
  } while(current_state);
}

有时这样的编码很有用(特别是如果“while”可以从 do_whatever() 例程中拉出到一个循环中,该循环“同时”运行多个类似编码的例程)。而且从来没有必要使用“goto”之类的东西。但就可读性而言,结构化编程结构要好得多。

在我看来,使用标志退出循环,然后根据退出原因选择几段代码之一来执行,就是用非结构化代码替换结构化代码。如果在循环中我编写,

  if (index >= numItems)
  {
    createNewItem(index);
    break;
  }

则立即(并且本地)清楚地表明我创建新项目的原因是因为我的索引超出了项目数量,并且我不需要多余地测试条件。相反,如果我循环直到找到某些东西或用完所有项目,那么我要么必须在循环后冗余地测试条件,要么向每个循环迭代添加标志测试。

If there is only one condition under which a loop can exit, and there isn't a whole lot of code that needs to run before checking the condition, and there isn't a whole lot of code that needs to run after checking the condition, put the condition in the loop.

I do think there are certainly uses for a "do{}while(1);" loop. Among them:

  1. In the loop-continue case, the primary condition needs to have enough code run both before and after it is checked that putting the code in the condition itself would be awkward at best.
  2. The loop has multiple exit conditions, and the exit condition which would most logically sit at the top or bottom of the loop would require special code to execute which should not execute for other exit conditions.

Some people like to use flags to distinguish exit conditions. I tend to view such flags as an effort to avoid the coding structures which in fact best embody the action of the program.

If one isn't too concerned about speed, one can avoid ever using any form of goto, premature-exit return, or for that matter using more than one while--and that with a simple condition. Simply write one's entire program as a state machine:

void do_whatever(void)
{
  int current_state=1;
  do
  {
    next_state = 0;
    if (current_state == 1)
    {
      do_some_stuff();
      next_state = 2;
    }
    if (current_state == 2)
    {
      do_some_more_stuff();
      next_state = 3;
    }
    ...
    current_state = next_state;
  } while(current_state);
}

There are times such coding is useful (particularly if the "while" can be pulled out of the do_whatever() routine into a loop which runs several similarly-coded routines "simultaneously"). And it's never necessary to use anything like a "goto". But for readability, structured programming constructs are much nicer.

To my mind, to use a flag to exit a loop and then select one of several pieces of code to execute based upon the exit cause is to replace structured code with unstructured code. If within a loop I write

  if (index >= numItems)
  {
    createNewItem(index);
    break;
  }

it's immediately (and locally) clear that the reason I'm creating a new item is because my index exceeded the number of items, and there's no need for me to redundantly test a condition. If instead I loop until I've found something or run out of items, then I'll either have to redundantly test a condition after the loop or add a flag test to every loop iteration.

动次打次papapa 2024-09-18 19:42:31

正如其他人所指出的,您给出的示例是一个糟糕的示例,因为您可以轻松地将测试移至 WHILE 中。

我使用中断的情况是当我有一个循环时,我必须在知道这是最后一次之前进行一些处理。例如:

while (true)
{
  String inLine=inStream.readLine();
  if (inLine.startsWith("End"))
    break;
  ... process line ...
}

这个例子只是稍微做作:我经常遇到这样的问题:从文件或其他源读取一些数据,以某种方式解析这些数据,然后才知道我已经到达了我感兴趣的内容的末尾现在正在处理。

当然,您可以编写一个函数来读取该行并进行必要的解析,例如:

while (!endOfInterestingData(inStream))
{
  ... process ...
}

但是您可能会遇到这样的问题:函数和循环体都需要访问读取的数据,并且函数必须返回一个布尔值来控制循环处理,因此它无法返回数据,因此使数据可用于循环体的唯一方法是让函数将其扔到某个可相互访问的字段中,然后该字段会掩盖循环获取其数据的位置数据来自.

As others have noted, the example you gave is a bad one because you could easily move the test into the WHILE.

The cases where I use a break are when I have a loop where I must do some processing before I know that this is the last time through. For example:

while (true)
{
  String inLine=inStream.readLine();
  if (inLine.startsWith("End"))
    break;
  ... process line ...
}

This example is only slightly contrived: I often run into problems where I read some data from a file or other source, parse this data in some way, and only then know that I have reached the end of what I am interested in processing right now.

You could, of course, wrte a function that reads the line and does the parsing necessary, like:

while (!endOfInterestingData(inStream))
{
  ... process ...
}

But then you may have the problem that both the function and the body of the loop need access to the data read, and the function must return a boolean to control loop processing so it can't return the data, so the only way to make the data available to the loop body is to have the function throw it into some mutually-accessible field, which then obscures where the loop gets its data from.

夏九 2024-09-18 19:42:31

这个解决方案非常快:

#include <stdio.h>

unsigned long isqrt(unsigned long n) {
    // http://snippets.dzone.com/posts/show/2715
    unsigned long a;
    for (a = 0; n >= (2*a)+1; n -= (2*a++) + 1);
    return a;
}

void nPrimes(const long N, long *const primes)
{
    unsigned long n, count, i, root;

    primes[0] = 2;
    count = 1;

    for (n = 3; count < N; n+=2) {

        root = isqrt(n);

        for (i = 0; primes[i] <= root; i++) {
            if ((n % primes[i]) == 0) goto notPrime;
        }
/*      printf("Prime #%lu is: %lu\n", count+1, n);*/
        primes[count++] = n;

        notPrime: ;
    }
}

int main (int argc, char **argv)
{
    long N;

    if (argc > 1) {
        N = atoi(argv[1]);
    } else {
        N = 10001;
    }

    long primes[N];

    nPrimes(N, primes);

    printf("Prime #%lu is: %lu\n", N, primes[N-1]);
}

注意:

  • isqrt(n) 是floor(sqrt(n))。这避免了浮点运算,无论如何我们都不需要浮点运算。算法来自 http://snippets.dzone.com/posts/show/2715
  • nPrimes 保存素数列表。这允许您仅测试素数,从而消除了绝大多数昂贵的取模操作。
  • 即使素数也只测试到 iqrt(n)。
  • 这是一个丑陋的 goto,但 C 没有命名的 continue,并且标志变量完全是浪费时间。
  • primes 数组用 2 初始化,并且只检查 3 中的奇数。消除 3 的倍数同样是可行的,但复杂性并不简单。
  • 通过保留素数平方表可以消除 isqrt 调用。这几乎肯定是矫枉过正,但如果你愿意的话也可以。
  • 在我的新机器上,这个过程眨眼间就可以完成。

This solution is pretty fast:

#include <stdio.h>

unsigned long isqrt(unsigned long n) {
    // http://snippets.dzone.com/posts/show/2715
    unsigned long a;
    for (a = 0; n >= (2*a)+1; n -= (2*a++) + 1);
    return a;
}

void nPrimes(const long N, long *const primes)
{
    unsigned long n, count, i, root;

    primes[0] = 2;
    count = 1;

    for (n = 3; count < N; n+=2) {

        root = isqrt(n);

        for (i = 0; primes[i] <= root; i++) {
            if ((n % primes[i]) == 0) goto notPrime;
        }
/*      printf("Prime #%lu is: %lu\n", count+1, n);*/
        primes[count++] = n;

        notPrime: ;
    }
}

int main (int argc, char **argv)
{
    long N;

    if (argc > 1) {
        N = atoi(argv[1]);
    } else {
        N = 10001;
    }

    long primes[N];

    nPrimes(N, primes);

    printf("Prime #%lu is: %lu\n", N, primes[N-1]);
}

Notes:

  • isqrt(n) is floor(sqrt(n)). This avoids floating-point operations, which we don't need anyway. Algorithm from http://snippets.dzone.com/posts/show/2715.
  • nPrimes keeps a list of the primes. This allows you to test only prime divisors, which cuts out the vast majority of those costly mod operations.
  • Even the primes are only tested up to isqrt(n).
  • That's an ugly goto, but C doesn't have named continues, and a flag variable would have been a complete waste of time.
  • The primes array is initialized with 2, and it only checks odd numbers from 3. Eliminating multiples of 3 is similarly doable, but not of trivial complexity.
  • The isqrt call could be eliminated by keeping a table of squares of primes. This is almost certainly overkill, but you can if you want to.
  • On my not-new machine, this runs in the blink of an eye.
终难遇 2024-09-18 19:42:30

我不确定一般来说休息是一种不好的做法,但我认为这是一种不好的做法。

这有点愚蠢,因为没有必要。您的 while (true) ... break 完全等同于:

while (numPrime < 10001) {
   ...
}

但不太直观。

坚持使用规范的方式来表示事物意味着读者的计算开销更少,使您的代码更容易理解,从而更容易维护并最终更加健壮。

编辑(回应评论):你是对的,总有一种方法可以使用它,但规则(如果你想要一个)相对简单:写下最易读的内容。在您发布的情况下,这不是最具可读性的,因为我给出的替代方案是表示这一点的规范方式。在某些情况下,例如循环遍历集合直到找到匹配的特定候选者,使用 break 可能是表示此模式的最合适方式。

尝试提出关于何时使用 break 以及何时提取变量并使用条件语句的硬性规则是很困难的(我认为这是徒劳的)。通常很容易判断什么是最简单的选择,但并非总是如此。这是经验真正重要的例子之一 - 你自己阅读和编写的好/坏代码越多,你的个人好例子和坏例子库就越大,你就越容易判断什么是“最好的” “* 表示给定概念的方式是。

*当然这是主观的!

I'm not sure that breaks in general are bad practice, but I think that this one is.

It's a bit silly because it's unnecessary. Your while (true) ... break is entirely equivalent to:

while (numPrime < 10001) {
   ...
}

but much less intuitive.

Sticking with the canonical way to represent things means less computational overhead for your readers, makes your code easier to understand, thus easier to maintain and ultimately more robust.

Edit (in response to comment): You're right that there's always a way around using one, but the rule (if you want one) is relatively simple: write whatever is most readable. In the case you posted, this is not the most readable as the alternative I gave is the canonical way to represent this. In some cases, for example looping though a collection until you find a specific candidate that matches, using break probably is the most fitting way to represent this pattern.

It would be difficult (and I woudl argue, futile) to try to come up with hard-and-fast rules about when to use break and when to pull out variables and use conditionals instead. Often it's easy to tell what's the simplest option, but not always. This is one of those examples where experience really counts - the more good/bad code you've read and wrote yourself, the bigger your personal library of good and bad examples, and the easier it will be for you to tell what the "best"* way to represent a given concept is.

*Of course it's subjective!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文