Блок 1. Раздел 3. Тема 4
Двойные и тройные вложенные циклы
Сам по себе цикл означает, что какой-то блок кода будет повторяться. Внутри этого блока кода можно заставить повторяться другой блок кода, т.е. один цикл можно писать внутри другого. Такие циклы называются вложенными. Они нужны, например, для работы с любыми таблицами.
Рассмотрим распечатку таблицы умножения:
System.out.println  ("Это таблица умножения!"); 
for (int a = 1; a < 10; a ++) {  
//внутри первого цикла можно сделать 2ой 
    for (int b = 1; b < 10; b ++) 
        System.out.print (" " + a*b); //печатаем числа через пробел 
    System.out.println (); //поставить enter для новой строчки
}
Вывод программы:
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81

Чтобы распечатать одну строчку нужен цикл. А у нас тут целый набор строк. Для перебора всех строк и нужен второй цикл. Он ставит enter после каждой строки, и знает номер текущей строки.

Компилятор выполняет все команды подряд. Он идет по коду сверху вниз и встречает первый for. Для него это значит - выполнить тело этого цикла столько-то раз. Он начинает выполнять цикл первый раз, снова сверху вниз и встречает вложенный цикл. Снова это значит - выполнить тело этого цикла столько-то раз. Выполнив весь вложенный цикл, компилятор идёт дальше, а дальше - новый оборот первого цикла.

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

Подобная ситуация часто возникает в играх с клетками на игровом поле по типу морского боя. Вообще с любыми таблицами так или иначе приходится отсчитывать номер строки и номер ячейки внутри этой строки, и очень помогает двойной цикл. Мы напишем игру "морской бой" в разделе 5 про массивы и таблицы.

Пример. Распечатать символ a, так что в первой строке всего один символ, во второй - два, в третьей - три, и так далее до 5.
/* Будет вот так: 
a 
aa 
aaa 
aaaa 
aaaaa */ 

for (int i = 1; i <= 5; i ++) { // переменная i отсчитывает строки 
    for (int j = 0; j < i; j ++) { // печатаем букву i раз 
        System.out.print ('a'); // печатаем 'a' без пробелов 
    } 
    System.out.println (); //поставить enter для новой строчки 
} 
Переведем этот код на русский язык для наглядности:
в начале i = 1 
повторять блок пока  i <= 5 и каждый раз увеличивать i на 1 
начало1 
    полностью выполнить цикл на i оборотов  
    начало2 
        печать ‘a’ 
    конец2 
    поставить enter 
конец1 
На каждом обороте внешнего цикла полностью выполняется весь внутренний. Итого получим:
первый оборот внешнего цикла i=1 и внутренний делает 1 печать a: 

a 

второй оборот внешнего цикла i = 2 и внутренний печатает 

aa 

третий оборот внешнего цикла i = 3, поэтому внутренний делает 3 оборота: 

aaa 

и так далее 
Можно делать и тройные циклы. Расположим оба предыдущих цикла внутри дополнительного внешнего на 3 оборота.
int k = 0; 
while (k < 3) { 
    //внутренние циклы те же, что и выше 
    for (int i = 1; i <= 5; i ++) { // переменная i отсчитывает строки 
        for (int j = 0; j < i; j ++) { // печатаем букву i раз 
            System.out.print ('a'); // печатаем 'a' без пробелов 
        } 
    System.out.println (); //поставить enter для новой строчки 
    } 
    k ++; 
} 

/*На экране будет повтор предыдущей печати 3 раза: 

a 
aa 
aaa 
aaaa 
aaaaa 
a 
aa 
aaa 
aaaa 
aaaaa 
a 
aa 
aaa 
aaaa 
aaaaa */
Естественно вместо печати буквы 'a' внутри циклов мог быть абсолютно любой, в том числе и достаточно сложный код – этот цикл был дан просто для примера. Имейте в виду, что можно комбинировать while, for и do-while в любых вариантах – компилятор их исполняет независимо. Как только он видит слово «цикл», то полностью выполняет его. И если этот цикл был в другом, то он полностью выполнится несколько раз.
Когда двойные циклы пишут через while, то очень часто забывают позаботиться о счетчике внутреннего цикла. Перепишем предыдущий двойной цикл:
int i = 1, j = 0; 
while (i <= 5) { 
    j = 0; //это присваивание часто забывают 
    while (j < i) { 
        System.out.print ('a');  
        j ++; 
    } 
    System.out.println (); 
    i ++; 
} 
Что будет, если не присвоить j = 0 перед выполнением внутреннего цикла? На первом обороте все будет нормально, ведь мы и так написали j = 0 ещё перед циклом. Но затем внутренний цикл выполнится полностью первый раз и будет j = 1. На следующем обороте нам хотелось напечатать букву два раза, но она вообще не напечатается, т.к. условие цикла j < i уже не выполняется. Иными словами мы хотели считать обороты от 0 до i, а считаем от i до i, то есть там нечего и считать. Так что заботьтесь о своих переменных – они этого достойны :)
Самое главное мы уже освоили. Теперь время решать задачи. Ответьте на несколько вопросов, чтобы лучше усвоился новый материал:
Вопрос 1. Если написать break во вложенном цикле, то остановится только вложенный цикл или весь двойной?
Остановится только вложенный. Если хотите остановить и внешний, то напишите в нём свой if и break с аналогичным условием, и он остановит в свою очередь свой цикл. Также и continue действует внутри своего цикла и ничего не знает про внешние.
Вопрос 2. Какова разница между тем, что один цикл написан внутри другого, то есть внутри его блока фигурных скобок, и тем, что один цикл написан в коде после другого, то есть ниже?
Разница огромная.

Первый вариант с вложенными циклами:
for (int i = 0; i < 5; i++)
for (int j = 0; j < 5; j++)
System.out.println (j);

Второй вариант, когда тот же самый цикл написан после другого, а вовсе не внутри:
for (int i = 0; i < 5; i++);
// Точка с запятой говорит, что теперь это отдельный цикл
for (int j = 0; j < 5; j++)
System.out.println (j);

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

В коде в данном случае разница всего в один символ - одна точка с запятой обозначила конец тела первого цикла. Но теперь получилась совершенно другая концепция.

Это обычное дело в программировании, поэтому отладка ошибок часто занимает не меньше времени, чем написание самой программы. В этом деле очень помогает отладчик - он позволяет видеть как программа работает по шагам, строчка за строчкой, и чему равна каждая переменная на каждом шаге. По-английски отладка будет debug, поэтому обычно кнопка отладки зовётся debug. Просто нажмите на неё и дальше щелкайте по кнопке "следующий шаг". А для того, чтобы смотреть, какое значение у переменных на каждом шаге обычно есть кнопка "add watch", то есть добавить наблюдение и нужно указать имя переменной.

Задача 1. Ниже дан код, печатающий "пирамидку" из букв 'a', как было в материале выше. Как изменить код, чтобы печатался только контур этой пирамидки, а вместо всех внутренних символов стояли бы пробелы?
/* Сейчас печатается так: 
a 
aa 
aaa 
aaaa 
aaaaa */ 

for (int i = 1; i <= 5; i ++) { // переменная i отсчитывает строки 
    for (int j = 0; j < i; j ++) { // печатаем букву i раз 
        System.out.print ('a'); // печатаем 'a' без пробелов 
    } 
    System.out.println (); //поставить enter для новой строчки 
} 

/* Как сделать, чтобы печаталось так:
a 
 a 
  a 
   a 
    a */ 
Решение
for (int i = 1; i <= 5; i++) { // переменная i отсчитывает строки
for (int j = 0; j < i-1; j++) { // печатаем букву i-1 раз
System.out.print(' '); // печатаем пробелы вместо старых 'a'
}
System.out.println('a'); //поставить 'a' и enter для новой строчки
}
Решайте больше задач по этому разделу здесь.
В следующей теме подведём итоги раздела 3