Цикл 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) выполняется в фиксированном порядке:
- Инициализатор (
int i = 1) выполняется один раз, раньше всего остального. - Проверяется условие (
i <= 5). Если оно ложно, цикл заканчивается и тело больше не выполняется. - Выполняется тело.
- Выполняется итератор (
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).