Есть ли необходимость в цикле "do {...} while ()"? - PullRequest
66 голосов
/ 15 июня 2009

Бьярн Страуструп (Cjarne Stroustrup) (создатель C ++) однажды сказал, что он избегает циклов "do / while" и предпочитает вместо этого писать код в терминах цикла "while". [См. Цитату ниже.]

Услышав это, я обнаружил, что это правда. о чем ты думаешь? Есть ли пример, когда «do / while» намного чище и проще для понимания, чем если бы вы использовали вместо него «while»?

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

Позвольте мне спросить по-другому: предположим, что вам запрещено использовать «do / while» - есть ли реалистичный пример, в котором у вас не было бы выбора, кроме как писать нечистый код с использованием «while»?

Из "Языка программирования C ++", 6.3.3:

По моему опыту, оператор do является источником ошибок и путаницы. Причина в том, что его тело всегда выполняется один раз, прежде чем условие будет оценено. Однако, чтобы тело работало правильно, что-то очень похожее на состояние должно сохраняться даже в первый раз. Чаще, чем я мог предположить, я обнаружил, что условие не выполняется должным образом ни тогда, когда программа была впервые написана и протестирована, ни позже, после изменения кода, предшествующего ей. Я также предпочитаю условие "там, где я его вижу". Следовательно, я стараюсь избегать дел-заявлений. -Bjarne

Ответы [ 19 ]

87 голосов
/ 15 июня 2009

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

#include <stdio.h>

int main() {
    char c;

    do {
        printf("enter a number");
        scanf("%c", &c);

    } while (c < '0' ||  c > '9'); 
}

Это, конечно, можно переписать в цикл while, но обычно это рассматривается как более элегантное решение.

38 голосов
/ 15 июня 2009

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

Другой пример - когда у вас уже есть действительный объект, когда должна быть запущена первая итерация, поэтому вы не хотите ничего выполнять (включая оценку состояния цикла) перед началом первой итерации. Примером могут служить функции Win32 FindFirstFile / FindNextFile: вы вызываете FindFirstFile, который либо возвращает ошибку или дескриптор поиска в первый файл, затем вызываете FindNextFile до тех пор, пока он не вернет ошибку.

псевдокод:

Handle handle;
Params params;
if( ( handle = FindFirstFile( params ) ) != Error ) {
   do {
      process( params ); //process found file
   } while( ( handle = FindNextFile( params ) ) != Error ) );
}
17 голосов
/ 15 июня 2009

do { ... } while (0) - важная конструкция для обеспечения хорошего поведения макросов.

Даже если это неважно в реальном коде (с которым я не обязательно согласен), это важно для исправления некоторых недостатков препроцессора.

Edit: я столкнулся с ситуацией, когда do / while было намного чище сегодня в моем собственном коде. Я делал кроссплатформенную абстракцию парных LL / SC инструкций. Их нужно использовать в цикле, например так:

do
{
  oldvalue = LL (address);
  newvalue = oldvalue + 1;
} while (!SC (address, newvalue, oldvalue));

(Эксперты могут понять, что 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 решил удалить конструкцию do-while .

7 голосов
/ 15 июня 2009

Это полезно, когда вы хотите «что-то сделать», пока «условие не будет выполнено.

Это может быть сделано в цикле, например:

while(true)
{

    // .... code .....

    if(condition_satisfied) 
        break;
}
6 голосов
/ 15 июня 2009

В наших соглашениях по кодированию

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

Так что у нас почти никогда нет 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 );
}
6 голосов
/ 15 июня 2009

(при условии, что вы знаете разницу между ними)

Do / While подходит для начальной загрузки / предварительной инициализации кода до проверки вашего состояния и запуска цикла while.

6 голосов
/ 19 декабря 2009

Следующая общая идиома кажется мне очень простой:

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 является очень полезной опцией.

5 голосов
/ 15 июня 2009

Это все о удобочитаемости .
Более читаемый код приводит к уменьшению головной боли при обслуживании кода и лучшему сотрудничеству.
Другие соображения (такие как оптимизация), безусловно, менее важны в большинстве случаев.
Я уточню, так как я получил комментарий здесь:
Если у вас есть фрагмент кода A , который использует do { ... } while(), и он более читабелен, чем его while() {...} эквивалент B , то я бы проголосовал за A, Если вы предпочитаете B , поскольку вы видите условие цикла "впереди", и думаете, что оно более читабельно (и, следовательно, поддерживается и т. Д.), - тогда продолжайте, используйте B . * * тысяча двадцать один Моя точка зрения: используйте код, который будет более читабельным для ваших глаз (и для ваших коллег). Выбор, конечно, субъективен.

3 голосов
/ 13 ноября 2013

Это самая чистая альтернатива do-while, которую я видел. Это идиома, рекомендуемая для Python, который не имеет цикла do-while.

Одно предостережение в том, что вы не можете иметь continue в <setup code>, так как это могло бы перескочить условие разрыва, но ни один из примеров, которые показывают преимущества выполнения, не требует продолжения перед условием. 1005 *

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

Этот следующий пример - случай, когда структура более читаема, чем задание, поскольку условие сохраняется вблизи вершины, поскольку //get data обычно короткое, а часть //process data может быть длинной.

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.
} 
3 голосов
/ 15 июня 2009

На мой взгляд, это только личный выбор.

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

Если вы посмотрите выше, ответ от TimW говорит сам за себя. Второй с Handle, особенно более грязный, на мой взгляд.

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