Проверить петли сверху или снизу? (в то время как vs. do while)

Когда я принимал CS в колледже (в середине 80-х), одна из идей, которые постоянно повторялись, заключалась в том, чтобы всегда писать циклы, которые проверяются наверху (хотя...), а не внизу (do... while ) цикла. Эти понятия часто подтверждались ссылками на исследования, в которых показано, что циклы, которые были проверены наверху, статистически гораздо более достоверны, чем их сравнительные результаты.

В результате я почти всегда пишу петли, которые проверяют вверху. Я не делаю этого, если он вводит дополнительную сложность в коде, но этот случай кажется редким. Я замечаю, что некоторые программисты склонны почти исключительно писать петли, которые проверяют внизу. Когда я вижу конструкции вроде:

if (condition)
{
    do
    {
       ...
    } while (same condition);
}

или инверсный (if внутри while), это заставляет меня задаться вопросом, действительно ли они написали его так или добавили оператор if, когда они поняли, что цикл не обрабатывает нулевой регистр.

Я сделал несколько поисковых запросов, но не смог найти литературу по этому вопросу. Как вы, ребята (и gals), пишете свои циклы?

Ответ 1

Я всегда придерживаюсь правила, что если он должен начинаться с нуля или более раз, сначала тестируйте, если он должен запускаться один или несколько раз, проверьте в конце. Я не вижу логической причины использовать код, указанный в вашем примере. Это только добавляет сложности.

Ответ 2

Использовать while, когда вы хотите проверить условие до первой итерации цикла.

Используйте циклы do-while, когда вы хотите проверить условие после запуска первой итерации цикла.

Например, если вы обнаружите, что делаете что-то вроде любого из этих фрагментов:

func();
while (condition) {
   func();
}

//or:

while (true){
    func();
    if (!condition) break;
}

Вы должны переписать его как:

do{
    func();
} while(condition);

Ответ 3

Разница заключается в том, что цикл do выполняет "сделать что-то" один раз и затем проверяет условие, чтобы увидеть, следует ли ему повторять "сделать что-то", в то время как цикл while проверяет условие перед тем, как что-либо сделать

Ответ 4

Помогает ли избежать do/while сделать мой код более удобочитаемым?

Нет.

Если имеет смысл использовать цикл do/while, тогда сделайте это. Если вам нужно выполнить тело цикла один раз перед тестированием условия, то цикл do/while, вероятно, является самой простой реализацией.

Ответ 5

Первый может вообще не выполняться, если условие ложно. Другой будет выполняться хотя бы один раз, а затем проверить конидирование.

Ответ 6

Для удобства чтения, кажется разумным проверить вверху. Важным является тот факт, что это цикл. человек, читающий код, должен знать условия цикла, прежде чем пытаться понять тело цикла.

Ответ 7

Здесь хороший реальный пример, который я недавно встретил. Предположим, что у вас есть несколько задач обработки (например, элементы обработки в массиве), и вы хотите разделить работу между одним потоком на текущий ядро ​​процессора. Должно быть хотя бы одно ядро ​​для запуска текущего кода! Таким образом, вы можете использовать do... while что-то вроде:

do {
    get_tasks_for_core();
    launch_thread();
} while (cores_remaining());

Это почти небрежно, но, возможно, стоит подумать о преимуществах производительности: его можно было бы записать как стандартный цикл while, но это всегда делало бы ненужное начальное сравнение, которое всегда оценивало бы true - и на одном -core, ветки предположения do-while более предсказуемо (всегда false, по сравнению с переменным true/false для стандартного while).

Ответ 8

Первый тест выполняется перед выполнением, поэтому ваш код никогда не будет вводить код под ним. Второй будет выполнять код внутри перед тестированием условия.

Ответ 9

Цикл while сначала проверяет "условие"; если он ложный, он никогда не "что-то сделает". Но do... while цикл сначала "сделает что-то", а затем проверит "условие".

Ответ 10

Yaa..истинно.. делаю, пока будет работать по крайней мере один раз. Это единственное различие. Больше ничего не обсуждать на этом

Ответ 11

Да, как и для использования вместо while, или foreach, а не для повышения удобочитаемости. Тем не менее, некоторые обстоятельства нужно делать, и я согласен, что было бы глупо принуждать эти ситуации к циклу while.

Ответ 12

Более полезно подумать с точки зрения общего использования. Подавляющее большинство циклов while работают вполне естественно с while, даже если их можно заставить работать с do...while, поэтому в основном вы должны использовать его, когда разница не имеет значения. Поэтому я использовал бы do...while для редких сценариев, где он заметно улучшил читаемость.

Ответ 13

Варианты использования разные для этих двух. Это не вопрос "лучшей практики".

Если вы хотите, чтобы цикл выполнялся на основе условия, кроме использования для или , а

Если вы хотите что-то сделать, независимо от состояния, а затем продолжайте делать это на основе оценки состояния. do..while

Ответ 14

A while() проверяет условие перед каждым исполнением тела цикла и do... while() проверяет условие после каждого выполнения цикла.

Таким образом, ** do... while() ** s всегда будет выполнять тело цикла хотя бы один раз.

Функционально while() эквивалентно

startOfLoop:
    if (!condition)
        goto endOfLoop;

    //loop body goes here

    goto startOfLoop;
endOfLoop:

и a do... while() эквивалентно

startOfLoop:

    //loop body

    //goes here
    if (condition)
        goto startOfLoop;

Обратите внимание, что реализация, вероятно, более эффективна, чем эта. Однако do... while() действительно включает в себя меньшее сравнение, чем некоторое время(), поэтому он немного быстрее. Используйте do... while(), если:

  • вы знаете, что условие всегда будет истинным в первый раз, или
  • вы хотите, чтобы цикл выполнялся один раз, даже если для этого условия было ложным.

Ответ 15

Вот перевод:

do { y; } while(x); 

То же, что и

{ y; } while(x) { y; }

Обратите внимание, что дополнительный набор фигурных скобок для случая имеет определения переменных в y. Их объем должен храниться локально, как в случае с петлей. Таким образом, цикл do-while просто выполняет свое тело хотя бы один раз. Кроме того, две петли идентичны. Поэтому, если мы применим это правило к вашему коду

do {
    // do something
} while (condition is true);

Соответствующий цикл while для вашего do-loop выглядит как

{
    // do something
}
while (condition is true) {
    // do something
}

Да, вы видите, что соответствующее время для вашего цикла do отличается от вашего времени:)

Ответ 16

Я, как правило, предпочитаю делать, пока петель, сам. Если условие будет всегда быть истинным в начале цикла, я предпочитаю проверить его в конце. На мой взгляд, весь смысл условий тестирования (кроме утверждений) заключается в том, что никто не знает результата теста. Если я вижу цикл while с тестом условия наверху, мой наклон должен учитывать случай, когда цикл выполняет нулевое время. Если это никогда не произойдет, почему бы не сделать код таким образом, чтобы это четко отображалось?

Ответ 17

Это на самом деле означало разные вещи. В C вы можете использовать конструкцию do - while для достижения обоих сценариев (выполняется хотя бы один раз и выполняется, пока она истинна). Но PASCAL имеет repeat - до и в то время как для каждого сценария, и если я правильно помню, ADA имеет еще одну конструкцию, которая позволяет вам выйти из середины, но, конечно, это не то, что вы спрашиваете. Мой ответ на ваш вопрос: мне нравится моя петля с тестированием сверху.

Ответ 18

Для тех, кто не может думать о причине, чтобы иметь один или более циклов времени:

try {
    someOperation();
} catch (Exception e) {
    do {
        if (e instanceof ExceptionIHandleInAWierdWay) {
            HandleWierdException((ExceptionIHandleInAWierdWay)e);
        }
    } while ((e = e.getInnerException())!= null);
}

То же самое можно использовать для любой иерархической структуры.

в классе Node:

public Node findSelfOrParentWithText(string text) {
    Node node = this;
    do {
        if(node.containsText(text)) {
            break;
        }
    } while((node = node.getParent()) != null);
    return node;
}

Ответ 19

Оба соглашения правильны, если вы знаете, как правильно писать код:)

Обычно использование второго соглашения (do {} while()) позволяет избежать дублирования оператора вне цикла. Рассмотрим следующий (более упрощенный) пример:

a++;
while (a < n) {
  a++;
}

можно писать более кратко, используя

do {
  a++;
} while (a < n)

Конечно, этот конкретный пример может быть написан еще более кратким образом, как (предполагая синтаксис Си)

while (++a < n) {}

Но я думаю, вы можете видеть здесь пункт.

Ответ 20

Как отмечено Piemasons, разница заключается в том, выполняется ли цикл один раз перед выполнением теста или если сначала выполняется тест, чтобы тело цикла никогда не выполнялось.

Основной вопрос, который имеет смысл для вашего приложения.

Чтобы сделать два простых примера:

  • Скажите, что вы перебираете элементы массива. Если массив не имеет элементов, вы не хотите обрабатывать номер один из нуля. Поэтому вы должны использовать WHILE.

  • Вы хотите отобразить сообщение, принять ответ, а если ответ недействителен, спросите еще раз, пока не получите действительный ответ. Поэтому вы всегда хотите спросить один раз. Вы не можете проверить, действителен ли ответ, пока вы не получите ответ, поэтому вам нужно пройти тело цикла один раз, прежде чем вы сможете проверить условие. Вы должны использовать DO/WHILE.

Ответ 21

Я пишу у меня в основном эксклюзивное тестирование наверху. Это меньше кода, поэтому, по крайней мере, для меня меньше шансов на что-то поднять (например, копирование в скопированном состоянии делает два места, которые вы всегда должны обновлять)

Ответ 22

На самом деле это зависит от ситуации, когда вы хотите протестировать вверху, другие, когда вы хотите протестировать внизу, и все еще другие, когда вы хотите протестировать посередине.

Однако приведенный пример кажется абсурдным. Если вы собираетесь тестировать вверху, не используйте инструкцию if и test внизу, просто используйте оператор while, для чего он создан.

Ответ 23

Сначала вы должны подумать о тестировании как части кода цикла. Если тест логически принадлежит в начале обработки цикла, то это тест на вершину цикла. Если тест логически принадлежит в конце цикла (т.е. Он решает, должен ли цикл продолжать работать), то это, вероятно, тест на основании цикла.

Вам нужно будет сделать что-то фантастическое, если тест логически принадлежит им в середине.: -)

Ответ 24

Я думаю, что некоторые люди тестируют внизу, потому что вы можете сохранить один или несколько машинных циклов, выполнив это 30 лет назад.

Ответ 25

Чтобы правильно написать код, нужно в основном выполнить умственное, возможно, неофициальное доказательство правильности.

Чтобы доказать правильность цикла, стандартным способом является выбор инварианта цикла и индуктивное доказательство. Но пропустите сложные слова: то, что вы делаете, неофициально, - это то, что верно для каждой итерации цикла, и что, когда цикл завершен, то, что вы хотели сделать, теперь верно. Инвариант цикла является ложным в конце, чтобы цикл завершился.

Если условия цикла довольно легко сопоставляются с инвариантом, а инвариант находится в верхней части цикла, и один указывает, что инвариант истинен на следующей итерации цикла, работая через код цикла, тогда легко понять, что петля верна.

Однако, если инвариант находится в нижней части цикла, то, если у вас есть утверждение непосредственно перед циклом (хорошая практика), тогда это становится более трудным, потому что вы должны по существу заключить, что должен быть этим инвариантом, и что любой код, который был запущен до цикла, делает цикл инвариантным true (поскольку нет предустановки цикла, код будет выполняться в цикле). Просто становится труднее доказать правильность, даже если это неофициальное доказательство вашей головы.

Ответ 26

На самом деле это не ответ, а повторение того, что сказал один из моих преподавателей, и это заинтересовало меня в то время.

Два типа цикла while..do и do..while фактически являются экземплярами третьего более общего цикла, который имеет тест где-то посередине.

begin loop
  <Code block A>
  loop condition
  <Code block B>
end loop

Блок кода A выполняется хотя бы один раз, а B выполняется ноль или более раз, но не выполняется на последней (неудачной) итерации. цикл while - это когда кодовый блок a пуст, а do... тогда, когда кодовый блок b пуст. Но если вы пишете компилятор, вам может быть интересно обобщить оба случая на такой цикл.

Ответ 27

while( someConditionMayBeFalse ){

// this will never run...

}


// then the alternative

do{

// this will run once even if the condition is false

while( someConditionMayBeFalse );

Разница очевидна и позволяет вам запускать код, а затем оценивать результат, чтобы увидеть, нужно ли вам "делать это снова", а другой метод while позволяет вам иметь блок script игнорируется, если условное не выполняется.

Ответ 28

В типичном классе дискретных структур в информатике это простое доказательство того, что между ними существует сопоставление эквивалентности.

Стилистически, я предпочитаю while (easy-expr) {}, когда easy-expr известен спереди и готов к работе, и цикл не имеет много повторяющихся служебных/инициализационных операций. Я предпочитаю делать {} while (несколько-less-easy-expr); когда есть более повторяющиеся накладные расходы, и условие может быть не так просто установить заблаговременно. Если я пишу бесконечный цикл, я всегда использую while (true) {}. Я не могу объяснить, почему, но мне просто не нравится писать для (;;) {}.

Ответ 29

Я бы сказал, что это плохая практика, чтобы писать if..do.. while loop, по той простой причине, что это увеличивает размер кода и вызывает дублирование кода. Дублирование кода подвержено ошибкам, и его следует избегать, так как любое изменение одной части должно выполняться и в дубликате, что не всегда так. Кроме того, больший код означает более сложное время в кэше процессора. Наконец, он обрабатывает нулевые случаи и решает головные боли.

Только тогда, когда первый цикл принципиально отличается, нужно использовать... тогда, скажем, если в цикле выполняется код, который заставляет вас проходить условие цикла (например, инициализация). В противном случае, если он определен, что цикл никогда не будет падать на первую итерацию, тогда да, do..while подходит.

Ответ 30

Из моих ограниченных знаний о генерации кода я думаю, что может быть хорошей идеей написать нижние тестовые циклы, поскольку они позволяют компилятору лучше выполнять оптимизацию циклов. Для нижних тестовых циклов гарантируется, что цикл выполняется хотя бы один раз. Это означает, что цикл-инвариантный код "доминирует" на выходе node. И, таким образом, его можно безопасно перемещать непосредственно перед запуском цикла.