Menu

Цикл for в C#: синтаксис, обратный отсчёт, вложенные циклы и ловушки

Как работает цикл for в C#: инициализатор, условие и итератор, обратный отсчёт и шаг, несколько переменных цикла, обход массива по индексу, вложенные циклы, for(;;), ошибки на единицу и удаление элементов во время обхода.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

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

Вывод:

Lap 1 of 5
Lap 2 of 5
Lap 3 of 5
Lap 4 of 5
Lap 5 of 5

Как выполняются три части

for (initializer; condition; iterator) выполняется в фиксированном порядке:

  1. Инициализатор (int i = 1) выполняется один раз, раньше всего остального.
  2. Проверяется условие (i <= 5). Если оно ложно, цикл заканчивается и тело больше не выполняется.
  3. Выполняется тело.
  4. Выполняется итератор (i++), затем управление возвращается к шагу 2.

Поскольку условие проверяется перед каждой итерацией, цикл for может выполниться ноль раз: for (int i = 10; i < 5; i++) ни разу не входит в тело. Цикл выше ведёт себя как этот цикл while:

int i = 1;
while (i <= 5)
{
    Console.WriteLine($"Lap {i} of 5");
    i++;
}

Эти две формы различаются одной деталью: continue внутри тела for всё равно выполняет итератор, а в этой версии с while он перепрыгнул бы через i++, поэтому цикл снова проверял бы то же i и мог бы крутиться вечно. Переменная, объявленная в инициализаторе, существует только внутри цикла. Если её итоговое значение нужно после цикла, объявите её до цикла: int i; for (i = 0; ...; i++) { }.

Обратный отсчёт и шаг

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

Вывод:

3... 2... 1... liftoff
 00 15 30 45
 1 10 100 1000

Убедитесь, что итератор приближает момент, когда условие станет ложным. for (int i = 0; i < 10; i--) уходит от 10 и останавливается только тогда, когда i переходит через границу после двух миллиардов итераций.

Обход массива по индексу

У массивов есть Length, у списков Count, а допустимые индексы идут от 0 до значения на единицу меньше. Индекс даёт то, чего нет у foreach: доступ к позиции, к соседям и возможность записывать обратно.

Вывод:

#1: 17.99
#2: 4.50
#3: 38.25
#4: 7.43
item 1 vs item 0: down
item 2 vs item 1: up
item 3 vs item 2: down

Последний цикл начинается с 1, потому что сравнивает каждый элемент с предыдущим. Создание, сортировка и копирование самих массивов описаны на странице о массивах.

Несколько переменных цикла

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

Вывод:

racecar is a palindrome: True
9, 1, 8, 3

В одном объявлении обе переменные должны иметь один тип: int left = 0, right = 10 работает, а int i = 0, string s = "" нет.

Вложенные циклы

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

Вывод:

   1   2   3   4
   2   4   6   8
   3   6   9  12
   4   8  12  16

Внутреннее тело выполняется 16 раз (4 × 4). Вложенность быстро умножает работу: два вложенных цикла по 10 000 элементов каждый дают 100 миллионов итераций. break во внутреннем цикле выходит только из внутреннего цикла; как выйти из обоих, показано на странице о break и continue.

Бесконечные циклы: for(;;)

Все три части заголовка необязательны. Без условия цикл выполняется, пока что-то внутри него не выйдет:

Вывод:

Month 1: 70 left
Month 2: 40 left
Month 3: 10 left
Overdrawn in month 4: -20

for (;;) и while (true) компилируются в одно и то же; выбирайте тот вариант, который принят в вашем коде.

Ошибки на единицу

Самая частая ошибка в for это условие, которое заходит на шаг дальше:

Вывод:

Ada
Grace
Linus
IndexOutOfRangeException at index 3

У массива из 3 элементов индексы 0, 1 и 2, поэтому правильное условие i < names.Length. Зеркальная ошибка это обратный отсчёт с i > 0, который никогда не посещает индекс 0. Для List<T> та же ошибка вместо этого выбрасывает ArgumentOutOfRangeException.

Удаление элементов во время обхода

Удаление из List<T> внутри прямого цикла сдвигает все последующие элементы на одну позицию вниз, поэтому цикл пропускает элемент, стоящий сразу после каждого удалённого:

Вывод:

forward:  5, 0, 3, 7
backward: 5, 3, 7

Прямой цикл оставил 0: после удаления индекса 1 второй ноль переместился на индекс 1, а i++ уже перешёл к индексу 2. Обратный обход этого избегает, потому что удаления сдвигают только уже посещённые элементы. В одну строку то же самое делает stock.RemoveAll(x => x == 0). Цикл foreach вообще не разрешает удаление: он выбрасывает InvalidOperationException.

Счётчики с плавающей точкой

Избегайте счётчиков цикла типа double, которые проверяются на равенство. У 0.1 нет точного двоичного представления, поэтому десятикратное прибавление не даёт ровно 1.0:

for (double x = 0; x != 1.0; x += 0.1)   // never ends: x skips past 1.0
{
}

Вместо этого считайте целым числом и вычисляйте значение из него: for (int i = 0; i <= 10; i++) { double x = i / 10.0; }.

for или foreach

Что нужноЧто использовать
Прочитать все элементы по порядкуforeach
Индекс или соседи (i - 1, i + 1)for
Присвоить значение элементу массиваfor
Шагать через 2, идти в обратном порядкеfor
Удалять из List во время обходаfor, в обратном порядке
Коллекция без индексатора (HashSet, Dictionary, результаты LINQ)foreach
Повторить что-то N раз без коллекцииfor

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

Часто задаваемые вопросы

Какой синтаксис у цикла for в C#?

for (initializer; condition; iterator) { body }, например for (int i = 0; i < 5; i++) { ... }. Инициализатор выполняется один раз, условие проверяется перед каждой итерацией, а итератор выполняется после каждой итерации. Цикл заканчивается, как только условие впервые оказывается ложным.

Как пройти цикл в обратном порядке в C#?

Начните с последнего индекса и уменьшайте его: for (int i = items.Length - 1; i >= 0; i--). Обратите внимание на >= 0, а не > 0, иначе первый элемент будет пропущен. Обход в обратном порядке также безопасный способ удалять элементы из List во время перебора.

Когда использовать for вместо foreach в C#?

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

Как написать бесконечный цикл в C#?

for (;;) { ... } или while (true) { ... }. Все три части заголовка for необязательны, а отсутствующее условие считается истинным. Выходите из цикла через break или return, когда выполнено условие выхода.

Почему в цикле for возникает IndexOutOfRangeException?

Условие позволяет индексу дойти до длины: i <= array.Length обращается к array[array.Length], на один элемент дальше последнего. Допустимые индексы идут от 0 до Length - 1, поэтому условие должно быть i < array.Length (или i < list.Count для List, который вместо этого выбрасывает ArgumentOutOfRangeException).

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ