Перейти к содержимому
Теория · Циклы · урок 7 из 8

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

Тренировка бегуна часто состоит из подходов. Тренер говорит: «Три подхода по три круга». Бегун пробегает три круга, отдыхает, снова пробегает три круга — и так три раза. Получается повтор внутри повтора.

В программе это вложенный цикл — цикл, который стоит в теле другого цикла. Внешний цикл считает подходы, внутренний — круги в подходе. Вот таблица умножения от 1 до 3:

JAVA
public class Main {
    public static void main(String[] args) {
        for (int i = 1; i <= 3; i++) {
            for (int j = 1; j <= 3; j++) {
                System.out.print(i * j + " ");
            }
            System.out.println();
        }
    }
}

Программа печатает три строки:

TEXT
1 2 3
2 4 6
3 6 9

Давайте пройдём по программе:

  1. Внешний цикл берёт i, равную 1.
  2. Внутренний цикл проходит j от 1 до 3 и печатает 1 2 3.
  3. System.out.println() без аргументов только переводит строку.
  4. Внешний цикл переходит к i, равной 2, и внутренний снова проходит все j от 1 до 3.

На каждую итерацию внешнего цикла внутренний выполняется целиком. Поэтому тело внутреннего цикла выполнилось 3 * 3, то есть девять раз. У каждого цикла своя переменная: i — номер строки таблицы, j — номер столбца.

break во вложенном цикле

Допустим, в таблице нужно найти первую пару чисел с произведением 6 и на этом остановиться. Первая попытка — обычный break:

JAVA
for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        if (i * j == 6) {
            System.out.print(i + "*" + j + " "); // 2*3 3*2
            break;
        }
    }
}

Программа нашла две пары вместо одной. break завершает только ближайший цикл, в теле которого стоит, — внутренний. Внешний цикл этого не заметил, перешёл к i, равной 3, и нашёл вторую пару 3*2.

С бегуном то же самое. Команда «стоп» посреди подхода заканчивает подход, но не тренировку: после отдыха начнётся следующий. Чтобы закончить всю тренировку, тренеру нужно назвать именно её: «тренировка окончена».

Метки

Метка цикла — имя с двоеточием перед циклом, например outer:. С case в switch её роднит только двоеточие. Имя метки придумывают сами, как имя переменной, и пишут его в той же строке, что и цикл, или строкой выше. Сама метка ничего не делает: она только даёт циклу имя, на которое могут сослаться break и continue.

break с меткой

break outer завершает цикл с меткой outer, даже если стоит во внутреннем цикле:

JAVA
outer: for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        if (i * j == 6) {
            System.out.print(i + "*" + j + " "); // 2*3
            break outer;
        }
    }
}

Разберём, что происходит:

  1. Пока i равна 1, ни одно произведение не равно 6.
  2. При i, равной 2, и j, равной 3, условие истинно, и программа печатает 2*3.
  3. break outer завершает и внутренний, и внешний цикл. До i, равной 3, дело не доходит.

После break outer выполнение идёт к инструкции после помеченного цикла. Это не прыжок назад к метке: метка только называет цикл, который нужно завершить.

Здесь картинка с тренером неточна. Тренер может крикнуть «стоп» откуда угодно. В Java break outer работает только внутри цикла с меткой outer, а снаружи компилятор ответит undefined label: outer.

На собеседованиях часто спрашивают, как выйти сразу из двух вложенных циклов. Ответ — break с меткой.

continue с меткой

continue outer пропускает остаток итерации внешнего цикла. Внутренний цикл обрывается на середине, а внешний переходит к следующему i. Для бегуна это команда «подход окончен, начинай следующий». Её берут, когда по одному j уже ясно, что остаток строки таблицы не нужен.

Частая ошибка

break стоит внутри switch, а switch — внутри цикла:

JAVA
for (int i = 1; i <= 5; i++) {
    switch (i) {
        case 3:
            System.out.print("stop ");
            break; // ✗
        default:
            System.out.print(i + " ");
    }
}

Хотели закончить цикл на тройке, а программа печатает 1 2 stop 4 5. break внутри switch относится к самому switch: он выводит из switch, и цикл идёт дальше. Поставьте на цикл метку — loop: for (…) — и напишите break loop. Тогда цикл закончится на тройке, и программа напечатает 1 2 stop.

Главное

  • Во вложенном цикле внутренний цикл выполняется целиком на каждую итерацию внешнего.
  • Без метки break и continue действуют только на ближайший цикл, а break внутри switch — только на switch.
  • break outer завершает цикл с меткой outer:, а continue outer переходит к его следующей итерации.
  • Метка не место для прыжка назад: после break outer выполнение идёт к инструкции после помеченного цикла.