2008-10-22 2 views
21

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

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

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

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

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

+0

«статистически более вероятно, будет правильно» Забудьте статистику. Доказательство условия сверху цикла является простым. Доказательство условия нижнего цикла намного сложнее. – 2008-10-22 00:35:00

+0

Вопрос: http://stackoverflow.com/questions/224138/infinite-loops-top-or-bottom – CesarB 2008-10-22 01:16:44

+4

У меня был профессор CS около 1997 года, провозглашающий, что ваши два выбора в программировании были C или Pascal, и что вы должны выбрать Pascal, потому что он не позволял вам увеличивать счетчики циклов внутри цикла. Возможно, он был прав, но мальчик был неправ. – MusiGenesis 2008-10-22 02:23:20

ответ

1

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

73

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

7

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

2

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

3

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

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

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

1

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

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

1

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

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

2

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

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; 
} 
20

Разница в том, что делать цикл выполняется «сделать что-то» один раз, а затем проверяет состояние, чтобы увидеть, если он должен повторить «сделать что-то» в то время как цикл в то время как проверяет состояние перед выполнением чего-либо

12

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

4

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

4

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

3

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

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

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

startOfLoop: 
    if (!condition) 
     goto endOfLoop; 

    //loop body goes here 

    goto startOfLoop; 
endOfLoop: 

и делать ... в то время как() эквивалентно

startOfLoop: 

    //loop body 

    //goes here 
    if (condition) 
     goto startOfLoop; 

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

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

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

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

3

Вот перевод:

do { y; } while(x); 

То же

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

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

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

Соответствующее в то время как петля для вашего сделай петли выглядит

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

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

2

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

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

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

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

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

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

while (++a < n) {} 

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

1

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

1

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

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

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

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

1

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

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

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

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

0

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

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

0

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

54

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

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

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

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

//or: 

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

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

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

Yaa..its правда .. делать время будет работать по крайней мере один раз. Это единственное различие. Ничто другое не обсуждалось на этом

6

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

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

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

2

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

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

принять два простых примера:

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

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

17

ли избежать do/while действительно поможет сделать мой код более читабельным?

No.

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

4

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

4

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

3

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

0

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

1
while(someConditionMayBeFalse){ 

// this will never run... 

} 


// then the alternative 

do{ 

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

while(someConditionMayBeFalse); 

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

Смежные вопросы