Массивы
Назначение массивов, объявление и инициализация, индексы с нуля и выход за границы, ввод и вывод циклом, сумма, минимум и максимум, поиск, перестановка элементов, сортировка пузырьком и выбором, передача массива в функцию, динамические массивы new и delete.
Задача: прочитать десять чисел и напечатать те, что больше среднего. Первая половина решается известными средствами — сумму можно накопить в цикле. Вторая не решается никак: чтобы сравнить числа со средним, их надо помнить все, а до сих пор каждое следующее число затирало предыдущее.
Заводить десять переменных a1, a2, …, a10 бессмысленно: код с ними
не пишется циклом, а при одиннадцати числах его придётся переписать.
Массив решает ровно эту задачу — он хранит много значений одного типа
под одним именем, и каждое доступно по номеру.
Объявление и инициализация
В объявлении указываются тип элементов, имя и размер в квадратных скобках. Размер — константа, известная на этапе компиляции: переменную туда поставить нельзя (для такого случая есть динамические массивы, см. ниже).
int marks[5] = {5, 4, 5, 3, 4}; // размер и значения
int zeros[4] = {}; // все элементы — нули
double weights[3] = {1.5, 2.25, 0.5};
int days[] = {31, 28, 31}; // размер посчитает компилятор — здесь 3
cout << "первая оценка: " << marks[0] << endl;
cout << "последняя оценка: " << marks[4] << endl;
cout << "второй вес: " << weights[1] << endl;
cout << "дней в феврале: " << days[1] << endl;
cout << "третий ноль: " << zeros[2] << endl;Главное правило, из которого растут почти все ошибки с массивами:
нумерация начинается с нуля. У массива из пяти элементов номера
от 0 до 4, и элемента marks[5] не существует.
Массив без инициализации содержит мусор
int a[5]; без списка значений не обнуляет элементы: в них окажется то,
что лежало в этой памяти раньше. Печать такого массива даёт случайные числа,
которые к тому же меняются от запуска к запуску. Если нужны нули, пишите
int a[5] = {}; — или заполняйте массив сами, прежде чем читать.
Размер массива можно вычислить, не помня его: sizeof даёт размер в байтах,
и размер всего массива, делённый на размер одного элемента, — это количество
элементов.
int a[7] = {1, 2, 3, 4, 5, 6, 7};
cout << "байт под весь массив: " << sizeof(a) << endl;
cout << "байт под один элемент: " << sizeof(a[0]) << endl;
cout << "элементов: " << sizeof(a) / sizeof(a[0]) << endl;Приём работает только там, где компилятор видит объявление массива. Внутри функции, которой массив передали параметром, он даёт неверный ответ — об этом в разделе о передаче массивов.
Ввод и вывод
Смысл массива в том, что его элементы обходятся циклом: номер элемента — это и есть счётчик цикла.
const int N = 5;
int a[N];
for (int i = 0; i < N; i++) { // ввод
cin >> a[i];
}
cout << "массив: ";
for (int i = 0; i < N; i++) { // вывод
cout << a[i] << " ";
}
cout << endl;
cout << "в обратном порядке: ";
for (int i = N - 1; i >= 0; i--) {
cout << a[i] << " ";
}
cout << endl;const int N = 5; — именованная константа для размера. Так размер записан
в программе один раз: чтобы перейти на десять элементов, правится одна строка,
а не четыре. Именно const int, а не просто int: размер массива обязан быть
известен компилятору.
Обратите внимание на условие i < N, а не i <= N. Последний элемент имеет
номер N - 1, и <= вышло бы за границу массива — это самая частая ошибка
в работе с массивами, у неё даже есть имя: «ошибка на единицу».
Выход за границы массива не ловится сам
Обращение a[10] в массиве из пяти элементов компилируется и запускается.
Программа прочитает или, хуже того, запишет чужую память: соседние переменные,
служебные данные. Проявиться это может где угодно и когда угодно —
или не проявиться вовсе, пока программу не соберут другим компилятором.
Не наберите такой пример в поле выше «просто посмотреть»: результат не определён стандартом, и ожидать от него осмысленного вывода нельзя. Проверка границ — целиком забота автора программы.
Если номера не нужны, а нужны сами элементы, работает уже знакомый цикл по диапазону:
int a[6] = {3, -1, 4, -1, 5, -9};
int negatives = 0;
for (int x : a) {
if (x < 0) negatives++;
}
cout << "отрицательных: " << negatives << endl;Типовые алгоритмы
Дальше идут четыре алгоритма, из которых собирается большинство задач на массивы. Все они — один проход циклом с накопителем, ровно как в задачах на последовательности чисел из урока о циклах. Разница в том, что теперь массив остаётся в памяти, и проходов можно сделать сколько угодно.
Сумма и среднее
const int N = 5;
int a[N];
for (int i = 0; i < N; i++) cin >> a[i];
int sum = 0;
for (int i = 0; i < N; i++) {
sum += a[i];
}
double average = (double)sum / N; // приведение обязательно: sum и N целые
cout << "сумма: " << sum << endl;
cout << "среднее: " << average << endl;
cout << "больше среднего: ";
for (int i = 0; i < N; i++) {
if (a[i] > average) cout << a[i] << " ";
}
cout << endl;Это та самая задача из начала урока, и решается она двумя проходами: первый считает сумму, второй сравнивает элементы со средним. Без массива второго прохода не было бы — числа уже прочитаны и забыты.
Приведение (double)sum / N обязательно: без него sum / N — целочисленное
деление, и среднее 13.2 превратилось бы в 13.
Максимум и его номер
const int N = 7;
int a[N];
for (int i = 0; i < N; i++) cin >> a[i];
int maxValue = a[0]; // начальное значение — первый элемент, не ноль
int maxIndex = 0;
for (int i = 1; i < N; i++) {
if (a[i] > maxValue) {
maxValue = a[i];
maxIndex = i;
}
}
cout << "максимум " << maxValue << " на позиции " << maxIndex << endl;Два решения, которые здесь приняты, стоит разобрать отдельно.
Начинаем с a[0], а не с нуля. Нулевое начальное значение сломалось бы
на массиве из одних отрицательных чисел: максимумом остался бы ноль, которого
в массиве нет. Первый элемент подходит всегда.
Цикл идёт с i = 1. Нулевой элемент уже учтён начальным значением;
сравнивать его с самим собой не вредно, но и незачем.
Запоминать номер полезнее, чем только значение: зная maxIndex, можно
и напечатать значение (a[maxIndex]), и поменять его местами с другим
элементом. Минимум ищется тем же кодом с обратным знаком сравнения.
Поиск элемента
int a[6] = {4, 8, 15, 16, 23, 42};
int target;
cin >> target;
int found = -1; // -1 означает «не найдено»
for (int i = 0; i < 6; i++) {
if (a[i] == target) {
found = i;
break; // дальше искать незачем
}
}
if (found != -1) cout << "нашлось на позиции " << found << endl;
else cout << "такого элемента нет" << endl;Это линейный поиск: элементы проверяются подряд, пока не найдётся нужный.
Признак «не найдено» — значение -1, которого не может быть у настоящего
номера; тот же приём, что string::npos в уроке о строках.
break здесь уместен, в отличие от примера из урока о циклах: он не прячет
условие выхода, а выражает «результат получен, работа окончена». Без него
нашёлся бы последний подходящий элемент, а не первый.
Перестановка элементов
Поменять местами два элемента напрямую нельзя — первое же присваивание затрёт значение, которое ещё нужно. Нужна временная переменная, как при развороте строки:
const int N = 6;
int a[N];
for (int i = 0; i < N; i++) cin >> a[i];
int minIndex = 0, maxIndex = 0;
for (int i = 1; i < N; i++) {
if (a[i] < a[minIndex]) minIndex = i;
if (a[i] > a[maxIndex]) maxIndex = i;
}
int temp = a[minIndex]; // обмен через временную переменную
a[minIndex] = a[maxIndex];
a[maxIndex] = temp;
cout << "после обмена: ";
for (int i = 0; i < N; i++) cout << a[i] << " ";
cout << endl;Заодно здесь показан более короткий способ искать минимум и максимум:
хранить не значение, а номер. Сравнение a[i] < a[minIndex] делает
то же самое, что сравнение со значением, но отдельная переменная не нужна.
Сортировка массивов
Отсортировать массив — значит расставить его элементы по возрастанию (или по убыванию). Учебных алгоритмов сортировки много; разберём два самых наглядных.
Сортировка пузырьком
Идея: пройти по массиву, сравнивая соседние элементы, и менять их местами,
если они стоят не в том порядке. За один такой проход самый большой элемент
«всплывает» в конец — отсюда название. Повторив проход N - 1 раз, получим
отсортированный массив.
const int N = 5;
int a[N];
for (int i = 0; i < N; i++) cin >> a[i];
for (int pass = 0; pass < N - 1; pass++) {
for (int i = 0; i < N - 1 - pass; i++) {
if (a[i] > a[i + 1]) { // соседи стоят неправильно
int temp = a[i];
a[i] = a[i + 1];
a[i + 1] = temp;
}
}
cout << "после прохода " << pass + 1 << ": ";
for (int i = 0; i < N; i++) cout << a[i] << " ";
cout << endl;
}Печать после каждого прохода здесь не украшение: она показывает, как хвост
массива постепенно становится отсортированным. Поэтому внутренний цикл идёт
до N - 1 - pass, а не до N - 1: последние pass элементов уже на местах,
и трогать их незачем.
Замените > на < — массив отсортируется по убыванию.
Сортировка выбором
Идея другая: найти минимальный элемент и поставить его на первое место, потом
найти минимальный среди оставшихся и поставить на второе, и так далее.
Перестановок получается меньше — ровно N - 1.
const int N = 5;
int a[N];
for (int i = 0; i < N; i++) cin >> a[i];
for (int i = 0; i < N - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < N; j++) { // ищем минимум среди оставшихся
if (a[j] < a[minIndex]) minIndex = j;
}
if (minIndex != i) { // ставим его на место i
int temp = a[i];
a[i] = a[minIndex];
a[minIndex] = temp;
}
}
for (int i = 0; i < N; i++) cout << a[i] << " ";
cout << endl;Оба алгоритма делают вложенными циклами примерно N × N / 2 сравнений.
Для десяти элементов это полсотни действий — мгновение; для миллиона это
полтриллиона, и программа будет считать часами. Промышленные сортировки
устроены иначе, и в стандартной библиотеке уже есть готовая sort
из <algorithm> — про неё будет отдельный урок в третьем семестре.
Пузырёк и выбор пишут руками не ради скорости, а чтобы понимать,
что происходит внутри.
Передача массива в функцию
Рабочая программа требует темы «передача массивов в качестве параметров функций». Функции подробно разбираются в третьем семестре — здесь достаточно знать, что функция это именованный кусок кода со своими параметрами.
Массив передаётся функции не копией: функция получает адрес начала массива, то есть работает с тем же самым массивом, что и вызывающая сторона. Отсюда два практических следствия.
#include <iostream>
using namespace std;
// n приходится передавать отдельно: сам массив свой размер не помнит
int sum(const int a[], int n)
{
int total = 0;
for (int i = 0; i < n; i++) total += a[i];
return total;
}
void doubleAll(int a[], int n) // без const — функция меняет массив
{
for (int i = 0; i < n; i++) a[i] *= 2;
}
int main()
{
int data[5] = {1, 2, 3, 4, 5};
cout << "сумма: " << sum(data, 5) << endl;
doubleAll(data, 5);
cout << "после удвоения: ";
for (int i = 0; i < 5; i++) cout << data[i] << " ";
cout << endl;
cout << "сумма снова: " << sum(data, 5) << endl;
return 0;
}Следствие первое: размер надо передавать отдельно. Внутри функции
sizeof(a) / sizeof(a[0]) не сработает — параметр a это уже не массив,
а адрес его начала, и его размер к размеру массива отношения не имеет.
Поэтому второй параметр n есть у обеих функций.
Следствие второе: функция меняет исходный массив. doubleAll ничего
не возвращает, но после вызова data изменился. Если менять массив
не требуется, параметр помечают const — как в sum. Тогда случайное
присваивание внутри функции не скомпилируется, и это хорошо: ошибка
поймана компилятором, а не студентом, который час ищет, где испортились
данные.
Динамические массивы
Размер обычного массива известен компилятору. А если он выясняется только
во время работы — например, пользователь сам говорит, сколько чисел введёт?
Тогда массив запрашивают у системы во время выполнения оператором new[],
а по окончании работы обязательно возвращают оператором delete[].
int n;
cin >> n; // размер выясняется только сейчас
int *a = new int[n]; // запросили память под n элементов
for (int i = 0; i < n; i++) cin >> a[i];
int sum = 0;
for (int i = 0; i < n; i++) sum += a[i];
cout << "прочитано чисел: " << n << ", сумма " << sum << endl;
delete[] a; // вернули памятьЧто здесь нового:
int *a— указатель: переменная, хранящая адрес начала выделенной памяти. Обращениеa[i]работает так же, как у обычного массива;new int[n]— запрос памяти подnэлементов. Размер здесь обычная переменная, и в этом весь смысл;delete[] a— возврат памяти. Квадратные скобки обязательны:delete aбез них освободил бы только первый элемент.
Инициализировать динамический массив списком нельзя
int *a = new int[n] = {1, 2, 3}; — не компилируется, и размер при выделении
обязан быть указан всегда: new int[] без размера бессмысленно. Списком
в фигурных скобках инициализируют обычный массив, у которого размер известен
компилятору. Это ровно тот случай, о котором спрашивает вопрос викторины
ниже.
Забытый delete[] — утечка памяти: программа держит память, которой
уже не пользуется. В короткой учебной программе это незаметно (память вернёт
операционная система при выходе), в работающем сутками сервере — причина
падения. Правило простое: у каждого new[] есть свой delete[].
Подробный разговор про указатели, nullptr и ответственность за чужую
память ждёт в третьем семестре; здесь достаточно уметь завести массив,
размер которого заранее неизвестен.
Массив символов — это тоже массив
Строка в стиле C из прошлого урока — обычный массив типа char, и всё, что
сказано про массивы, относится к нему целиком: нумерация с нуля, отсутствие
проверки границ, передача в функцию без копии.
char word[] = "array"; // 5 букв + завершающий '\0' = 6 элементов
cout << "элементов в массиве: " << sizeof(word) / sizeof(word[0]) << endl;
for (int i = 0; word[i] != '\0'; i++) { // до нулевого байта
cout << word[i] << " ";
}
cout << endl;
word[0] = 'A';
cout << word << endl;Условие цикла — word[i] != '\0', а не i < 5: так устроены все функции
работы с C-строками, и strlen внутри делает ровно это. Для string
такой цикл не нужен, потому что длину он знает и так.
Задание к лабораторной
Вариант 1 (из рабочей программы). Дан одномерный массив из 10 целых чисел. Найти максимальный элемент и сравнить с ним остальные элементы. Вывести количество меньших максимального и больших максимального элемента. Объясните в отчёте, почему второе количество всегда равно нулю.
Вариант 2 (из рабочей программы). Одномерный массив из 10 целых чисел заполнить с клавиатуры, определить сумму тех чисел, которые больше 5. Дополнительно выведите их количество и среднее арифметическое.
Вариант 3 (из рабочей программы). Дан массив целых чисел. Найти сумму элементов с чётными номерами и произведение элементов с нечётными номерами. Вывести сумму и произведение. В отчёте укажите, что считается чётным номером, и не забудьте, что нумерация начинается с нуля.
Вариант 4 (из рабочей программы). Переставить в одномерном массиве минимальный элемент и максимальный. Предусмотрите случай, когда они совпадают.
Вариант 5 (из рабочей программы). Дан одномерный массив из 15 элементов. Элементам массива меньше 10 присвоить нулевые значения, а элементам больше 20 присвоить 1. Вывести на экран первоначальный и преобразованный массивы в строчку.
Вариант 6 (из рабочей программы). Дан одномерный массив целого типа. Получить другой массив, состоящий только из чётных чисел исходного массива, меньших 10, или сообщить, что таких чисел нет. Полученный массив вывести в порядке возрастания элементов.
Вариант 7 (из рабочей программы). Даны массивы A и B одинакового размера 10. Вывести исходные массивы. Поменять местами их содержимое и вывести вначале элементы преобразованного массива A, а затем — элементы преобразованного массива B.
Вариант 8. Программа читает размер массива n, выделяет память
динамически, заполняет массив с клавиатуры и печатает его в отсортированном
виде. Сортировка — своя, пузырьком или выбором. В отчёте покажите,
где в программе стоит delete[] и почему именно там.
Как оценивается лабораторная
Работа сдаётся преподавателю очно: сайт её не принимает и не оценивает. Шкала и показатели ниже — из рабочей программы дисциплины, раздел 7.
Показатели оценки
- степень соответствия выполненного задания поставленным целям, задачам и требованиям;
- оформление, структурирование и комментирование лабораторной работы;
- уникальность выполнения работы (отличие от работ коллег);
- успешные ответы на контрольные вопросы.
Баллы
- 86–100отлично— оформление соответствует требованиям, критерии выдержаны, защита всего перечня контрольных вопросов.
- 66–85хорошо— оформление соответствует требованиям, критерии выдержаны, защита только до 85 % контрольных вопросов.
- 51–65удовлетворительно— оформление соответствует требованиям, критерии выдержаны, защита только до 51 % контрольных вопросов.
- 0–50неудовлетворительно— оформление не соответствует требованиям, критерии не выдержаны, защита менее 51 % контрольных вопросов.
Проверьте себя
Викторина по теме
Какой номер у последнего элемента массива `int a[5];`?
Ссылки на литературу
- Воробьев, Г. А. Основы программирования на C++ : учебно-методическое пособие. — Липецк : Липецкий ГПУ, 2022. — 89 с. — ISBN 978-5-907461-84-0. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
- Калитвин, В. А. Введение в программирование на C++ : учебное пособие. — Липецк : Липецкий ГПУ, 2023. — 84 с. — ISBN 978-5-907655-86-7. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
- Широбокова, С. Н., Кацупеев, А. А., Сулыз, А. В. Программирование на языке C++ для лабораторных занятий : учебное пособие. — Новочеркасск : ЮРГПУ, 2020. — 104 с. — ISBN 978-5-9997-0725-3. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
Строки
Тип string, ввод через cin и getline, доступ к символам и их изменение, склейка и сравнение, методы size, substr, find, replace, insert, erase, перевод строки в число и обратно, функции <cctype>, разобранные задачи и строки в стиле C.
Двумерные массивы
Таблица как массив массивов, объявление и инициализация, обход вложенными циклами и выравнивание вывода, суммы по строкам и столбцам, поиск максимума с координатами, квадратные матрицы, диагонали, транспонирование, умножение матриц, передача в функцию и динамическая матрица.