Технологии и методы программирования — на главную
Семестр 2 · ПовторениеМодуль 3. Строки и массивы в C++

Массивы

Назначение массивов, объявление и инициализация, индексы с нуля и выход за границы, ввод и вывод циклом, сумма, минимум и максимум, поиск, перестановка элементов, сортировка пузырьком и выбором, передача массива в функцию, динамические массивы new и delete.

лекции 2 ч
лаб. 4 ч
СРС 8 ч

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

Заводить десять переменных 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.

Показатели оценки

  1. степень соответствия выполненного задания поставленным целям, задачам и требованиям;
  2. оформление, структурирование и комментирование лабораторной работы;
  3. уникальность выполнения работы (отличие от работ коллег);
  4. успешные ответы на контрольные вопросы.

Баллы

  • 86–100
    отлично— оформление соответствует требованиям, критерии выдержаны, защита всего перечня контрольных вопросов.
  • 66–85
    хорошо— оформление соответствует требованиям, критерии выдержаны, защита только до 85 % контрольных вопросов.
  • 51–65
    удовлетворительно— оформление соответствует требованиям, критерии выдержаны, защита только до 51 % контрольных вопросов.
  • 0–50
    неудовлетворительно— оформление не соответствует требованиям, критерии не выдержаны, защита менее 51 % контрольных вопросов.

Проверьте себя

Викторина по теме

Вопрос 1 из 10
один вариант

Какой номер у последнего элемента массива `int a[5];`?

Ссылки на литературу

  1. Воробьев, Г. А. Основы программирования на C++ : учебно-методическое пособие. — Липецк : Липецкий ГПУ, 2022. — 89 с. — ISBN 978-5-907461-84-0. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
  2. Калитвин, В. А. Введение в программирование на C++ : учебное пособие. — Липецк : Липецкий ГПУ, 2023. — 84 с. — ISBN 978-5-907655-86-7. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
  3. Широбокова, С. Н., Кацупеев, А. А., Сулыз, А. В. Программирование на языке C++ для лабораторных занятий : учебное пособие. — Новочеркасск : ЮРГПУ, 2020. — 104 с. — ISBN 978-5-9997-0725-3. Лань : электронно-библиотечная система (дата обращения: 15.01.2026). Доступ: для авторизованных пользователей
Мой прогресс

Содержание