Вложенные циклы
Бегун готовится к соревнованиям, и у тренера план на несколько дней: каждый день пробежать по три круга. В блокноте бегун отмечает пары «день — круг»: день 1, круг 1; день 1, круг 2; и так далее. Когда круги дня закончились, наступает следующий день, и счёт кругов начинается заново.
Такой план — цикл внутри цикла. Внешний цикл перебирает дни, а вложенный цикл — круги внутри одного дня:
for (int day = 1; day <= 2; day++)
{
for (int lap = 1; lap <= 3; lap++)
{
Console.WriteLine(day + " " + lap);
}
}
// 1 1
// 1 2
// 1 3
// 2 1
// 2 2
// 2 3Выражение day + " " + lap склеивает число, пробел и второе число в одну строку. Давайте разберём, что происходит:
- Внешний цикл начинает с
day = 1. - Внутренний цикл проходит
lapот1до3, и программа печатает1 1,1 2,1 3. - Внутренний цикл закончился, и внешний выполняет свой шаг:
dayстановится равной2. - Внутренний цикл начинается заново, с
lap = 1, и печатает2 1,2 2,2 3. - Шаг внешнего цикла даёт
day = 3, условиеday <= 2ложно, и оба цикла позади.
Внутренний цикл проходит целиком на каждой итерации внешнего. Поэтому его тело выполняется 2 * 3, то есть шесть раз: число итераций внешнего цикла, умноженное на число итераций внутреннего.
На собеседовании это свойство обсуждают, когда говорят о скорости программы. Если оба цикла проходят по n значений, тело выполнится n * n раз: при n = 1000 это уже миллион итераций.
Внутренний цикл зависит от внешнего
Условие внутреннего цикла может использовать переменную внешнего. Так рисуют треугольник из звёздочек, где в строке с номером row ровно row звёздочек:
for (int row = 1; row <= 4; row++)
{
for (int k = 1; k <= row; k++)
{
Console.Write("*");
}
Console.WriteLine();
}
// *
// **
// ***
// ****Разберём по шагам:
- Внешний цикл перебирает строки:
rowот1до4. - Внутренний цикл печатает звёздочки через
Console.Write, и все они остаются в одной строке. Условиеk <= rowдаёт число звёздочек, равное номеру строки. Console.WriteLine()с пустыми скобками стоит во внешнем цикле, после внутреннего, и переводит строку.
Так решают задачи, где нужно перебрать все пары значений: таблицу умножения, все сочетания двух чисел.
break во вложенном цикле
break завершает только ближайший цикл, тот, в котором стоит. Если он внутри вложенного цикла, внешний цикл выполнит остаток своего тела после внутреннего и перейдёт к следующей итерации.
Здесь аналогия с тренировками перестаёт работать. Тренер может отменить весь план разом, а у break такой силы нет: из внутреннего цикла он отменяет только сегодняшние круги, и следующий день всё равно наступит.
Чтобы выйти сразу из обоих циклов, заводят переменную-флаг типа bool и проверяют её в условии внешнего цикла. Найдём первую пару чисел от 1 до 3, произведение которых равно 6:
bool found = false;
for (int a = 1; a <= 3 && !found; a++)
{
for (int b = 1; b <= 3; b++)
{
if (a * b == 6)
{
Console.WriteLine(a + " " + b); // 2 3
found = true;
break;
}
}
}Разберём, как выходят оба цикла:
- Пока пара не найдена,
foundравнаfalse, и условие внешнего цикла зависит только отa. - При
a = 2иb = 3произведение равно6: программа выводит2 3и записывает вfoundзначениеtrue. breakзавершает внутренний цикл.- Внешний цикл выполняет шаг
a++и проверяет условие:!foundтеперь ложно, и внешний цикл тоже заканчивается.
Без && !found внешний цикл перешёл бы к a = 3 и вывел бы ещё пару 3 2. На собеседовании иногда спрашивают, как выйти сразу из двух циклов, и флаг в условии внешнего цикла — один из ответов.
Частая ошибка
Во вложенном цикле взяли то же имя переменной, что и во внешнем:
for (int i = 1; i <= 2; i++)
{
for (int i = 1; i <= 3; i++) // ✗
{
Console.Write(i);
}
}error CS0136: A local or parameter named 'i' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameterПрограмма не компилируется. Внутренний цикл стоит внутри внешнего, а там переменная i уже есть. Второе объявление с тем же именем сделало бы непонятным, о какой i идёт речь, и C# его запрещает. Дайте переменной внутреннего цикла своё имя, например j.
Главное
- Вложенный цикл проходит целиком на каждой итерации внешнего: при 2 днях по 3 круга его тело выполнится
2 * 3раз. - Условие внутреннего цикла может использовать переменную внешнего.
breakзавершает только ближайший цикл; чтобы выйти из обоих, проверяют флаг в условии внешнего.- У переменной вложенного цикла должно быть своё имя: повторить имя переменной внешнего цикла нельзя.