Содержание
В прошлом уроке мы научились хранить несколько значений в одном массиве и обходить их циклом. Но данные не всегда удобно располагать в одну линию. Расписание занятий, игровое поле, таблица оценок — всё это строки и столбцы.
В этой статье разберём матрицы в C++ — двумерные массивы — и инструмент для работы с ними: вложенные циклы. Начнём с таблицы умножения, проследим выполнение двух циклов, проверим поведение break, а затем перейдём к объявлению матрицы, индексам и диагоналям.
После этой статьи Вы сможете:
- объяснить, в каком порядке выполняются вложенные циклы;
- вывести таблицу с помощью двух циклов
for; - предсказать, какой цикл остановит
break; - объявить и заполнить двумерный массив, обратиться к его элементу;
- найти главную и побочную диагонали квадратной матрицы;
- решить три задачи к уроку: таблица умножения, треугольник Паскаля и сумма диагоналей.
Вложенные циклы в C++
Вложенный цикл — это цикл, который находится внутри тела другого цикла. Тот, который снаружи, называют внешним, а находящийся в его теле — внутренним.
Мы уже умеем повторять действие: например, вывести десять произведений числа 2. Теперь нужно повторить не одно действие, а целую последовательность действий — сначала умножение на 1, затем на 2, на 3 и так до 10.
От одной строки к таблице умножения
Сначала выведем одну строку — результаты умножения чисел от 1 до 10 на 2:
#include <iostream>
int main() {
for (int i = 1; i <= 10; i++) {
std::cout << 2 * i << " ";
}
std::cout << "\n";
return 0;
}
2 4 6 8 10 12 14 16 18 20
Чтобы получить остальные строки, можно скопировать цикл десять раз и заменить двойку на другие множители. Но меняется только один множитель. Значит, повторяющийся участок можно поместить в ещё один цикл:
#include <iostream>
int main() {
for (int j = 1; j <= 10; j++) {
for (int i = 1; i <= 10; i++) {
std::cout << j * i << "\t";
}
std::cout << "\n";
}
return 0;
}
Внешний цикл по j выбирает множитель для строки. Внутренний по i выводит десять чисел этой строки. Символ \t разделяет столбцы табуляцией, а \n переводит курсор на новую строку.
Как выполняется вложенный цикл
Проследим более короткий пример. Пока внешний счётчик равен 0, внутренний проходит все свои значения. Только после этого меняется внешний счётчик, а внутренний запускается заново:
#include <iostream>
int main() {
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
std::cout << "(" << i << ", " << j << ") ";
}
std::cout << "\n";
}
return 0;
}
(0, 0) (0, 1) (0, 2)
(1, 0) (1, 1) (1, 2)
В коротком примере с выводом пар на каждой новой итерации внешнего цикла снова выполняется объявление int j = 0. Поэтому внутренний счётчик не продолжает считать с прежнего значения, а снова начинается с нуля.
Если внешний цикл выполняется n раз, а внутренний каждый раз — m раз, тело внутреннего цикла выполнится n * m раз. Для таблицы 10×10 это 100 произведений. Но если граница внутреннего цикла зависит от внешнего счётчика, количество итераций нужно считать отдельно.
Вложенные циклы — это не только for внутри for. Внутрь while можно поместить for, внутрь for — do while и так далее. Выбирайте вид каждого цикла по его собственной задаче.
break во вложенном цикле C++
Оператор break внутри внутреннего цикла завершает только этот цикл. Выполнение продолжается с команды, которая идёт сразу после него. Внешний цикл при этом продолжает работать.
#include <iostream>
int main() {
for (int j = 1; j <= 10; j++) {
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break;
}
std::cout << i << " ";
}
std::cout << "\n";
}
return 0;
}
При i == 5 срабатывает break, поэтому пятёрка уже не выводится. Затем выполняется перевод строки, внешний счётчик увеличивается, и внутренний цикл начинается заново. Результат — десять одинаковых строк:
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
Как выйти из двух вложенных циклов
Дополнительный пример для закрепления: попробуем остановить перебор сразу на двух уровнях.
Один break не завершает сразу два цикла. Если после найденного результата нужно остановить весь перебор, удобно завести логическую переменную — флаг. Внутренний цикл установит флаг и завершится, а внешний проверит его в условии.
#include <iostream>
int main() {
bool found = false;
for (int i = 1; i <= 5 && !found; i++) {
for (int j = 1; j <= 5; j++) {
if (i * j == 6) {
std::cout << i << " " << j << "\n";
found = true;
break;
}
}
}
return 0;
}
Программа найдёт первую пару 2 3. После выхода из внутреннего цикла сначала выполнится шаг i++, затем внешний цикл проверит !found, получит ложь и тоже остановится. Это дополнительный приём: сначала убедитесь, что понимаете поведение одного break.
Что такое двумерный массив в C++
Двумерный массив в C++ — это массив, элементами которого являются массивы. Его удобно представлять как прямоугольную таблицу из строк и столбцов. Для числовой таблицы также используют слово матрица.
У одномерного массива один индекс. Для ячейки таблицы нужны два: сначала индекс строки, затем индекс столбца. Например, в расписании сначала выбираем день, а внутри него — номер занятия.
Как объявить матрицу в C++
const int ROWS = 4;
const int COLS = 5;
int matrix[ROWS][COLS] = {};
| Часть объявления | Значение |
|---|---|
int | Тип одного элемента |
matrix | Имя массива |
ROWS | Количество строк: 4 |
COLS | Количество столбцов в каждой строке: 5 |
{} | Все элементы инициализируются нулями |
Всего в этой матрице 4 * 5 = 20 элементов. Размеры обычного встроенного массива должны быть положительными константными выражениями, известными на этапе компиляции.
Запись int matrix[n][m];, где n и m прочитаны с клавиатуры, не входит в стандартный C++. Некоторые компиляторы принимают её как расширение. В учебных задачах ниже мы объявляем массив с запасом, а введённые размеры используем только как границы обхода.
Инициализация двумерного массива
Чтобы задать значения сразу, запишем каждую строку в отдельной паре фигурных скобок. Возьмём таблицу Паскаля 4×5 из видеоурока:
#include <iostream>
int main() {
int pascal[4][5] = {
{1, 1, 1, 1, 1},
{1, 2, 3, 4, 5},
{1, 3, 6, 10, 15},
{1, 4, 10, 20, 35}
};
std::cout << pascal[2][1] << "\n";
return 0;
}
Если значений в строке меньше, чем столбцов, оставшиеся элементы будут нулями. Если не перечислить все строки, недостающие строки тоже заполнятся нулями. Пустые скобки {} обнуляют весь массив. А локальный массив int matrix[4][5]; без инициализации нельзя читать до того, как Вы присвоили значения его элементам.
Можно перечислить значения и без внутренних фигурных скобок: первые пять попадут в первую строку, следующие пять — во вторую и так далее. Например, int a[2][3] = {1, 2, 3, 4}; даст строки 1 2 3 и 4 0 0. Но отдельные скобки для каждой строки делают структуру матрицы заметнее, поэтому в примерах используем их.
Индексы строк и столбцов
Доступ к ячейке записывается так: matrix[row][col]. Оба индекса начинаются с нуля. Для матрицы 4×5 допустимы строки 0, 1, 2, 3 и столбцы 0, 1, 2, 3, 4.
В примере выше pascal[2][1] — значение 3: третья строка, второй столбец. Индекс и порядковый номер в обычной речи отличаются на единицу. Последняя ячейка — pascal[3][4], в общем случае matrix[ROWS - 1][COLS - 1].
Чтобы изменить элемент, ему присваивают новое значение, как обычной переменной: matrix[1][2] = 99;. Не меняйте местами строку и столбец: matrix[2][1] — уже другая ячейка, даже если значения в них совпадают. Индексом может быть и целочисленное выражение, например i + 1, если результат остаётся в допустимых границах.
Ввод и вывод двумерного массива в C++
Внешний цикл перебирает строки, внутренний — столбцы. За полное выполнение внутреннего цикла мы обрабатываем одну строку. Для следующей строки внутренний счётчик снова начинается с нуля.
Полная программа ниже считывает размеры n и m от 1 до 100, затем n * m целых чисел и выводит ту же матрицу:
#include <iostream>
int main() {
const int MAX_ROWS = 100;
const int MAX_COLS = 100;
int matrix[MAX_ROWS][MAX_COLS];
int n, m;
std::cin >> n >> m;
if (n < 1 || n > MAX_ROWS || m < 1 || m > MAX_COLS) {
return 0;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
std::cin >> matrix[i][j];
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (j > 0) std::cout << " ";
std::cout << matrix[i][j];
}
std::cout << "\n";
}
return 0;
}
Пример ввода:
2 3
10 20 30
40 50 60
Результат:
10 20 30
40 50 60
std::cin читает числа независимо от того, разделены они пробелами или переводами строк. Но для человека удобнее записывать входные данные в форме таблицы. При выводе вид таблицы создаём сами: пробелы ставим между элементами, перевод строки — после завершения внутреннего цикла.
В памяти выделены все 100×100 элементов, но программа использует только первые n строк и m столбцов. Это тот же массив «с запасом», который мы разбирали в прошлом уроке. Все используемые ячейки сначала получают значения при вводе, поэтому читать их после ввода можно.
Квадратная матрица
Квадратная матрица — матрица, у которой число строк равно числу столбцов: 1×1, 2×2, 3×3 и так далее. Если размер равен n, оба индекса меняются от 0 до n - 1.
Именно для квадратной матрицы в этом уроке рассматриваем две диагонали, соединяющие противоположные углы: главную и побочную.
Главная диагональ C++: условие i == j
Главная диагональ идёт из левого верхнего угла в правый нижний. У каждого её элемента индекс строки равен индексу столбца: i == j.
Для матрицы 4×4 это ячейки [0][0], [1][1], [2][2], [3][3]. Поэтому перебрать главную диагональ можно одним циклом: для каждой строки i выбираем столбец i.
Побочная диагональ в C++: условие i + j == n - 1
Побочная диагональ идёт из правого верхнего угла в левый нижний. Индекс строки растёт, а индекс столбца уменьшается: 0 и n - 1, затем 1 и n - 2 и так далее.
Сумма индексов всегда равна n - 1. Отсюда два равносильных способа записи: условие i + j == n - 1 при обходе всей матрицы или индекс столбца n - 1 - i при обходе одним циклом.
Выведем обе диагонали матрицы 3×3:
#include <iostream>
int main() {
const int N = 3;
int matrix[N][N] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int i = 0; i < N; i++) {
std::cout << matrix[i][i] << " ";
}
std::cout << "\n";
for (int i = 0; i < N; i++) {
std::cout << matrix[i][N - 1 - i] << " ";
}
std::cout << "\n";
return 0;
}
1 5 9
3 5 7
| Область квадратной матрицы | Условие для индексов с нуля |
|---|---|
| Главная диагональ | i == j |
| Выше главной диагонали | i < j |
| Ниже главной диагонали | i > j |
| Побочная диагональ | i + j == n - 1 |
| Выше побочной диагонали | i + j < n - 1 |
| Ниже побочной диагонали | i + j > n - 1 |
В таблице «выше» и «ниже» относятся к обычному рисунку матрицы: индекс строки увеличивается сверху вниз. Для включения самой диагонали замените строгий знак сравнения на нестрогий.
Сумма диагоналей: как не посчитать центр дважды
Дополнительная практика к формулам из видеоурока: найдём сумму элементов на двух диагоналях.
Если n нечётное, диагонали пересекаются в ячейке [n / 2][n / 2]. У матрицы 3×3 это [1][1]. Если независимо сложить обе диагонали, центральный элемент попадёт в сумму два раза.
Когда в задаче нужна сумма всех ячеек, лежащих хотя бы на одной диагонали, используйте логическое «или». Во время обхода каждой ячейки будет выполнено не больше одного прибавления:
#include <iostream>
int main() {
const int N = 3;
int matrix[N][N] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
long long sum = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (i == j || i + j == N - 1) {
sum += matrix[i][j];
}
}
}
std::cout << sum << "\n";
return 0;
}
25
Получилось 1 + 3 + 5 + 7 + 9 = 25. Два отдельных if без дополнительной проверки дали бы 30: пятёрку прибавили бы дважды. Для чётного размера общего центра нет.
Тип long long для суммы полезен, когда отдельные элементы помещаются в int, а результат сложения — уже нет. Ограничения задачи проверяют не только для входных данных, но и для промежуточных результатов.
Треугольник Паскаля в двумерном массиве
Вернёмся к таблице Паскаля из видеоурока. Матрицу можно не заполнять числами вручную, а вычислять её элементы по правилу: в верхней строке и левом столбце стоят единицы, а каждая оставшаяся ячейка равна сумме соседей сверху и слева.
1 1 1 1 1
1 2 3 4 5
1 3 6 10 15
1 4 10 20 35
- Верхняя строка:
a[0][j] = 1. - Левый столбец:
a[i][0] = 1. - Остальные ячейки при
i > 0 && j > 0:a[i][j] = a[i - 1][j] + a[i][j - 1].
Например, значение a[2][2] равно a[1][2] + a[2][1] = 3 + 3 = 6. Заполнять удобно построчно, сверху вниз и слева направо: тогда оба нужных соседа уже вычислены. В верхней строке и левом столбце формулу суммы не применяем, иначе получим индекс −1.
Как это связано с привычным треугольником Паскаля? Посмотрите на ячейки, у которых одинакова сумма индексов. При i + j = 0 получим 1, при i + j = 1 — 1 1, затем 1 2 1, 1 3 3 1. Это строки классического треугольника, расположенные по наклонным линиям таблицы. Конечный прямоугольник содержит только часть этих строк; недостающие ячейки находятся за его границами. Нули справа здесь не добавляются.
Полную программу Вы напишете самостоятельно в контесте. Для этого урока заданы 1 <= n, m <= 15. В таблице 15×15 наибольшее число — 40 116 600; оно помещается в обычный 32-битный int. При увеличении размеров коэффициенты быстро растут, поэтому тип данных нужно выбирать заново по ограничениям.
Частые ошибки при работе с матрицами
| Ошибка | Как исправить |
|---|---|
i <= n или j <= m при индексации с нуля | Использовать i < n и j < m. |
| Строки и столбцы перепутаны | В a[i][j] первый индекс — строка, второй — столбец. |
В обоих циклах граница n для прямоугольной матрицы | По строкам идти до n, по столбцам — до m. |
| Перевод строки внутри внутреннего цикла | Перенести std::cout << "\n"; после него. |
Ожидание, что break завершит оба цикла | Проверять общий флаг во внешнем цикле. |
Условие побочной диагонали i + j == n | При индексах с нуля требуется n - 1. |
| Двойной учёт центра | Использовать || в одном условии или отдельно вычесть повторный центр. |
| Чтение неинициализированной ячейки | Сначала ввести/вычислить значение или обнулить массив с помощью {}. |
Обращение за границы массива не обязано приводить к понятной ошибке. Программа может работать неверно или аварийно завершиться. Подробнее — в статье «Segmentation fault в C++». Проверяйте оба индекса и не ориентируйтесь только на то, что один запуск прошёл успешно.
Задачи на вложенные циклы и матрицы
Закрепите материал в контесте к уроку. Задачи идут от первого двойного цикла до обработки квадратной матрицы:
- Таблица умножения. По числу строк и столбцов вывести таблицу произведений. Массив не требуется: достаточно двух вложенных циклов.
- Матрица Паскаля. Заполнить прямоугольную матрицу: в верхней строке и левом столбце единицы, остальные элементы — суммы соседей сверху и слева.
- Сумма двух диагоналей. Найти сумму элементов на главной и побочной диагоналях. Общий центральный элемент учитывать один раз.
Сначала сформулируйте словами, что означает каждый счётчик. Затем проверьте программу на маленьком примере, где результат легко посчитать вручную. Для матриц особенно полезны размеры 1×1, 2×3 и 3×3: они помогают заметить ошибки с границами, перепутанные размеры и двойной учёт центра.
Короткие ответы на частые вопросы
Чем матрица отличается от двумерного массива?
В этом уроке матрица — числовая прямоугольная таблица, а двумерный массив — способ хранить её в программе. Встроенный двумерный массив устроен как массив строк, причём каждая строка сама является массивом.
Как получить элемент матрицы в C++?
Запишите a[i][j], где i — индекс строки, j — индекс столбца. Индексы начинаются с нуля; последний элемент матрицы n × m имеет индексы [n - 1][m - 1].
Какое условие у главной и побочной диагоналей?
Для квадратной матрицы размера n и индексов с нуля главная диагональ задаётся условием i == j, побочная — i + j == n - 1.
Почему break не выходит из всех вложенных циклов?
Так определено поведение оператора: он завершает только ближайшую окружающую подходящую конструкцию. Чтобы завершить два цикла, передайте информацию наружу, например через логический флаг, и проверьте его в условии внешнего цикла.
Подведём итоги
- При каждой итерации внешнего цикла внутренний запускается заново и выполняется до своего условия завершения или break.
breakиз внутреннего цикла не останавливает внешний.- Матрица объявляется как
int a[ROWS][COLS]; первый размер задаёт строки, второй — столбцы. - Ввод и вывод выполняются двумя циклами; перевод строки ставят после вывода всех столбцов.
- У главной диагонали
i == j, у побочнойi + j == n - 1. - В нечётной квадратной матрице диагонали имеют общий центр. Его учёт зависит от условия задачи.
Теперь Вы умеете работать с данными не только в один ряд, но и в виде таблицы. Откройте CLion, воспроизведите примеры и попробуйте менять размеры и границы циклов. После этого переходите к задачам: практика поможет связать индексы в программе с ячейками матрицы.
В следующем уроке перейдём к указателям: сначала разберём саму идею и смысл указателя, а работу с динамической памятью оставим для отдельного занятия.
Использованная литература
- Рабочий проект стандарта C++: массивы [Электронный ресурс]. — URL: https://eel.is/c++draft/dcl.array (дата обращения: 30.10.2026).
- Рабочий проект стандарта C++: оператор break [Электронный ресурс]. — URL: https://eel.is/c++draft/stmt.break (дата обращения: 30.10.2026).
- Рабочий проект стандарта C++: оператор continue [Электронный ресурс]. — URL: https://eel.is/c++draft/stmt.cont (дата обращения: 30.10.2026).
- Треугольник Паскаля — Wolfram MathWorld [Электронный ресурс]. — URL: https://mathworld.wolfram.com/PascalsTriangle.html (дата обращения: 30.10.2026).