Подтвердить что ты не робот

Должен ли я ожидать, что счетчик в цикле `for` изменится внутри его тела?

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

for( int y = 4; y < 12; y++ ) {
    // blah
    if( var < othervar ) {
        y++;
    }
    // blah
}

На основании большинства кода, написанных и прочитанных другими, следует ли ожидать этого?

4b9b3361

Ответ 1

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

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

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

for(something)
  if (somethingElse)
   y++

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

Но помимо этого могут возникнуть ситуации, когда "что-то" похоже на ваш пример; но для этих случаев - почему бы не использовать цикл while?

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

 while (y < whatever) {
   ...
   y = determineY(y, who, knows);
 }

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

Ответ 2

Я прошу отличиться с приветственным ответом выше. Нет ничего плохого в манипулировании переменной управления контуром внутри тела цикла. Например, вот классический пример очистки карты:

for (auto it = map.begin(), e = map.end(); it != e; ) {
    if (it->second == 10)
        it = map.erase(it);
    else
        ++it;
}

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

for (size_t i = 0; i < s_len; ++i) {
    if (s[i] == '\\') {
       ++i;
       continue;
    }
    process_symbol(s[i]);
}

Ответ 3

Вместо этого используйте цикл while.

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

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

Для человека, однако, цикл for имеет конкретное подразумеваемое значение: Запускайте этот цикл фиксированное число раз. Если вы обезьяна с индексом цикла, то это нарушает импликацию. Это нечестно в некотором смысле, и это важно, потому что следующий человек, чтобы прочитать код, должен либо потратить дополнительные усилия, чтобы понять цикл, либо не сможет этого сделать, и поэтому не сможет понять.

Если вы хотите обезьяну с индексом цикла, используйте цикл while. Особенно в C/С++/родственных языках, цикл for точно такой же мощный, как цикл while, поэтому вы никогда не теряете никакой силы или выразительности. Любой цикл for может быть преобразован в цикл while и наоборот. Однако следующий человек, который его читает, не будет зависеть от того, что вы не обезьяны с индексом цикла. Создание цикла while вместо цикла for является предупреждением о том, что этот вид цикла может быть более сложным, и в вашем случае он на самом деле более сложный.

Ответ 4

Если вы увеличиваете внутри цикла, обязательно прокомментируйте его. Канонический пример (основанный на эффекте С++ для Скотта Мейера) приведен в Q & A Как удалить с карты во время ее итерации? (копирование стенографического кода)

for (auto it = m.cbegin(); it != m.cend() /* not hoisted */; /* no increment */)
{
  if (must_delete)
  {
    m.erase(it++);    // or "it = m.erase(it)" since C++11
  }
  else
  {
    ++it;
  }
}

Здесь как непостоянный характер итератора end(), так и инкремент внутри цикла удивляют, поэтому их необходимо документировать. Примечание: подъем петли здесь все-таки возможно, поэтому, вероятно, это должно быть сделано для ясности кода.

Ответ 5

Для чего это стоит, вот что должно сказать по этому поводу основные принципы С++:

http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-loop-counter

ES.86: Избегайте изменения переменных управления контурами внутри тела raw for-loops

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

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

Ответ 6

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

Насколько я понимаю, вопрос:

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

Ответ на этот вопрос ясен:

да

Когда вы работаете с другим кодом - независимо от того, выполняете ли вы обзор, исправляете ошибку, добавляете новую функцию - вы должны ожидать худшего.

Ожидается все, что действительно в пределах языка.

Не делайте никаких предположений о том, что код находится в соответствии с любой хорошей практикой.

Ответ 7

Лучше писать как цикл while

y = 4;
while(y < 12)
{
   /* body */
   if(condition)
     y++;
   y++;
}

Иногда вы можете выделить логику цикла из тела

 while(y < 12)
 {
    /* body */
    y += condition ? 2 : 1;
 }

Я бы допустил метод for(), если и только если вы редко пропускаете элемент, как escape-последовательности в цитируемой строке.