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

Есть ли техническая причина использовать> (<) вместо!= При увеличении на 1 в цикле 'for'?

Я почти никогда не вижу цикл for следующим образом:

for (int i = 0; 5 != i; ++i)
{}

Есть ли техническая причина использовать > или < вместо != при увеличении на 1 в цикле for? Или это скорее соглашение?

4b9b3361

Ответ 1

while (time != 6:30pm) {
    Work();
}

Это 6:31 вечера... Черт, теперь мой следующий шанс вернуться домой завтра!:)

Это показывает, что более сильное ограничение смягчает риски и, вероятно, более интуитивно понятно.

Ответ 2

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

< или > являются более сильными ограничениями, чем !=, и в большинстве случаев выполняют те же самые цели (я бы даже сказал во всех практических случаях).

Существует дублированный вопрос здесь; и один интересный ответ.

Ответ 3

Уже есть ответы, но есть и другой случай, когда разница имеет значение. Если у вас такой цикл:

for (int i = a; i < b; i++){}

этот цикл будет работать как ожидалось для любых значений a и b, а если вы использовали !=, вам нужно будет проверить a <= b перед запуском цикла.

Итак, снова это сводится к: В большинстве случаев нет разницы, но в некоторых случаях небольшая разница имеет большое значение. Если вы хотите, чтобы все ваши циклы выглядели одинаково (а не для каждого цикла, чтобы рассмотреть, какая версия лучше), просто используйте <, а не !=.

В некоторых особых случаях вам может понадобиться !=, а не <, но это действительно особые случаи, и тогда имеет смысл, что они выглядят иначе, чем обычные циклы.

Когда я впервые ответил на этот вопрос, я рассмотрел только простые старые циклы с помощью счетчика int. Когда итераторы входят в игру, ситуация другая. Циклы с итераторами обычно выглядят так:

for (Iterator it = v.begin(); it != v.end(); ++it);

Причина заключается в том, что * это гораздо более слабое ограничение для реализации оператора !=. Почти любые два объекта могут быть проверены на неравенство, но не для всех типов a < имеет смысл. Особенно, когда элементы контейнера не заказываются, итераторы не могут его реализовать. Таким образом, более общий способ записи такого цикла - !=.

Однако моя точка сверху остается в силе: могут быть случаи с счетчиком int, где вы должны использовать !=. Они редки, и когда они происходят, вы хотите, чтобы их легко идентифицировать. Таким образом, обычно пишется большинство int -контурных петель с помощью <.

* = поставляется без гарантии. Пожалуйста, кто-то исправит меня, если я ошибаюсь;)

Ответ 4

У вас может быть что-то вроде

for(int i = 0; i<5; ++i){
    ...
    if(...) i++;
    ...
}

Если ваша переменная цикла записана внутренним кодом, i!=5 может не нарушить этот цикл. Это безопаснее проверять на неравенство.

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

Ответ 5

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

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

Ответ 6

Я бы сказал, что выражение типа

for ( int i = 0 ; i < 100 ; ++i )
{
  ...
}

более выразителен в отношении намерения, чем

for ( int i = 0 ; i != 100 ; ++i )
{
  ...
}

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

Ответ 7

Итераторы - важный случай, когда вы чаще всего используете обозначение !=:

for(auto it = vector.begin(); it != vector.end(); ++it) {
 // do stuff
}

Предоставлено: на практике я бы написал то же самое, что и на range-for:

for(auto & item : vector) {
 // do stuff
}

но точка остается: каждый обычно сравнивает итераторы с помощью == или !=.

Ответ 8

Условие цикла является принудительным инвариантом цикла.

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

for (int i = 0; i != 5; ++i)
{
  // ?
}

в этом случае вы знаете в начале итерации цикла, что i не равно 5.

for (int i = 0; i < 5; ++i)
{
  // ?
}

в этом случае вы знаете в начале итерации цикла, что i меньше 5.

Во-вторых, гораздо больше информации, чем первая, нет? Теперь, программист намерен (почти наверняка) то же самое, но если вы ищете ошибки, уверенность в чтении строки кода - это хорошо. И второй принудительно вводит этот инвариант, что означает, что некоторые ошибки, которые укусят вас в первом случае, просто не могут произойти (или не вызывать повреждение памяти, скажем) во втором случае.

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

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

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

В С++ вы можете написать тип indexes, чтобы:

for( const int i : indexes(0, 5) )
{
  // ?
}

делает то же самое, что и одно из двух вышеперечисленных циклов for, вплоть до компилятора, оптимизирующего его до одного и того же кода. Здесь, однако, вы знаете, что i нельзя манипулировать в теле цикла, поскольку он объявлен const, без кода, искажающего память.

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

Ответ 9

Может случиться так, что переменная я установлена ​​в какое-то большое значение, и если вы просто используете оператор! =, вы окажетесь в бесконечном цикле.

Ответ 10

Как уже говорилось в Ian Newson, вы не можете надежно перекрыть плавающую переменную и выйти с помощью !=. Например,

for (double x=0; x!=1; x+=0.1) {}

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

(Обратите внимание, что это в основном поведение undefined, если вы получите 0.9999... как последнее принятое число, которое нарушает менее чем предположение или ndash или уже выходит с 1.0000000000000001.)

Ответ 11

Как вы можете видеть из других многочисленных ответов, есть причины использовать < вместо! =, что поможет в случаях кросс, начальных условиях, непреднамеренной модификации счетчика циклов и т.д.

Честно говоря, я не думаю, что вы можете подчеркнуть важность конвенции достаточно. В этом примере для других программистов будет достаточно просто посмотреть, что вы пытаетесь сделать, но это вызовет двойной прием. Одна из заданий во время программирования делает его максимально читабельным и знакомым для всех, поэтому неизбежно, когда кто-то должен обновлять/изменять свой код, не требуется много усилий, чтобы выяснить, что вы делали в разных блоках кода, Если бы я увидел, что кто-то использует !=, я бы предположил, что была причина, по которой они использовали его вместо <, и если бы это был большой цикл, я бы просмотрел все это, пытаясь понять, что вы сделали, что сделало это необходимо... и это потраченное впустую время.

Ответ 12

Я использую прилагательное "технический" для обозначения языкового поведения/причуд и побочных эффектов компилятора, таких как производительность сгенерированного кода.

С этой целью ответ: no (*). (*) - "обратитесь к руководству вашего процессора". Если вы работаете с RISC или FPGA с кратным корпусом, вам может потребоваться проверить, какие команды сгенерированы и какие они стоят. Но если вы используете практически любую обычную современную архитектуру, то нет существенной разницы в уровне процессора в стоимости между lt, eq, ne и gt.

Если вы используете кромку края, вы можете обнаружить, что != требует трех операций (cmp, not, beq) против двух (cmp, blt xtr myo). Опять же, RTM в этом случае.

По большей части причины являются защитными/упрочняющими, особенно при работе с указателями или сложными циклами. Рассмотрим

// highly contrived example
size_t count_chars(char c, const char* str, size_t len) {
    size_t count = 0;
    bool quoted = false;
    const char* p = str;
    while (p != str + len) {
        if (*p == '"') {
            quote = !quote;
            ++p;
        }
        if (*(p++) == c && !quoted)
            ++count;
    }
    return count;
}

Менее надуманным примером будет то, что вы используете возвращаемые значения для выполнения приращений, принимая данные от пользователя:

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i != len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;
        std::cin >> step;
        i += step; // here for emphasis, it could go in the for(;;)
    }
}

Попробуйте это и введите значения 1, 2, 10, 999.

Вы можете предотвратить это:

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i != len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;
        std::cin >> step;
        if (step + i > len)
            std::cout << "too much.\n";
        else
            i += step;
    }
}

Но вы, вероятно, хотели, чтобы

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i < len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;
        std::cin >> step;
        i += step;
    }
}

Существует также что-то вроде условного смещения к <, потому что упорядочение в стандартных контейнерах часто зависит от operator<, например хеширование в нескольких контейнерах STL определяет равенство, говоря

if (lhs < rhs) // T.operator <
    lessthan
else if (rhs < lhs) // T.operator < again
    greaterthan
else
    equal

Если lhs и rhs - пользовательский класс, записывающий этот код как

if (lhs < rhs) // requires T.operator<
    lessthan
else if (lhs > rhs) // requires T.operator>
    greaterthan
else
    equal

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

Ответ 13

Существует несколько способов написания любого кода (обычно), в этом случае просто два способа (три, если вы считаете <= и > =).

В этом случае люди предпочитают > и < чтобы убедиться, что даже если что-то неожиданное произойдет в цикле (например, ошибка), он не будет бесконечно циклически (BAD). Рассмотрим, например, следующий код.

for (int i = 1; i != 3; i++) {
    //More Code
    i = 5; //OOPS! MISTAKE!
    //More Code
}

Если мы использовали (i < 3), мы были бы в безопасности от бесконечного цикла, потому что он поставил большее ограничение.

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

Надеюсь, это помогло!

Ответ 14

Да; OpenMP не распараллеливает циклы с условием !=.

Ответ 15

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

С другой стороны, многие ответы здесь утверждают, что использование формы < помогает избежать ошибок. Я бы сказал, что во многих случаях это помогает скрывать ошибки. Если индекс цикла должен достигнуть конечного значения, и вместо этого он фактически выходит за его пределы, тогда происходит что-то, что вы не ожидали, что может вызвать сбой (или быть побочным эффектом другой ошибки). < скорее всего задержит обнаружение ошибки. !=, скорее всего, приведет к остановке, зависанию или даже сбою, что поможет вам обнаружить ошибку раньше. Чем раньше будет найдена ошибка, тем дешевле ее исправить.

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

Например, если вы переходите через обычную строку C, обычно чаще всего записывается:

for (char *p = foo; *p != '\0'; ++p) {
  // do something with *p
}

чем

int length = strlen(foo);
for (int i = 0; i < length; ++i) {
  // do something with foo[i]
}

Во-первых, если строка очень длинная, вторая форма будет медленнее, потому что strlen - это другой проход через строку.

С С++ std::string вы должны использовать цикл, основанный на диапазоне, стандартный алгоритм или итераторы, даже если длина доступна. Если вы используете итераторы, соглашение должно использовать !=, а не <, как в:

for (auto it = foo.begin(); it != foo.end(); ++it) { ... }

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

Ответ 16

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

(1) Немного избыточности. На естественном языке мы обычно предоставляем больше информации, чем это необходимо, так же, как код с исправлением ошибок. Здесь дополнительная информация заключается в том, что переменная цикла i (см., Как я использовал избыточность здесь? Если вы не знали, что означает "переменная цикла", или если вы забыли имя переменной, после прочтения "переменной цикла i" у вас есть полная информация) менее 5 во время цикла, а не только от 5. Избыточность улучшает читаемость.

(2) Конвенция. Языки имеют определенные стандартные способы выражения определенных ситуаций. Если вы не будете следовать установленному способу сказать что-то, вы все равно поймете, но усилия для получателя вашего сообщения больше, потому что определенные оптимизации не будут работать. Пример:

Не говорите вокруг горячего пюре. Просто осветите трудность!

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

Не бить вокруг куста. Просто объясните проблему!

Это верно даже в том случае, если синонимы, используемые в первой версии, оказались лучше, чем обычные слова в английской идиоме. Подобные силы действуют, когда программисты читают код. Именно поэтому 5 != i и 5 > i - это странные способы его размещения, если вы не работаете в среде, в которой стандартно менять более обычные i != 5 и i < 5 таким образом. Такие сообщества диалектов существуют, вероятно, потому, что согласованность облегчает запоминание записи 5 == i вместо естественного, но подверженного ошибкам i == 5.

Ответ 17

Одна из причин не использовать эту конструкцию - это числа с плавающей запятой. != - очень опасное сравнение для использования с поплавками, поскольку оно редко оценивается как истинное, даже если числа выглядят одинаково. < или > устраняет этот риск.

Ответ 18

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

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

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

for (unsigned i = 42; i != -1; --i)
  ...

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

Ответ 19

Помимо примеров, в которых переменная цикла (непреднамеренная) изменяется внутри тела, существуют другие способы использования операторов меньшего или большего размера:

  • Отрицание делает код труднее понять
  • < или > - это только один char, но != два

Ответ 20

В дополнение к различным людям, которые упомянули, что это смягчает риск, он также уменьшает количество перегрузок функций, необходимых для взаимодействия с различными стандартными библиотечными компонентами. В качестве примера, если вы хотите, чтобы ваш тип сохранялся в std::set или использовался как ключ для std::map или использовался с некоторыми алгоритмами поиска и сортировки, стандартная библиотека обычно использует std::less для сравнения объектов так как большинству алгоритмов нужен только строгий слабый порядок. Таким образом, становится хорошей привычкой использовать сравнения < вместо сравнения != (где это имеет смысл, конечно).

Ответ 21

Нет проблем с точки зрения синтаксиса, но логика этого выражения 5!=i не является звуковой.

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

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