Семинар 3-ч.2

Тема семинара: "Массивы и динамическая память в C++

Цель: Закрепить практические навыки выделения, использования и освобождения динамической памяти для одномерных и двумерных массивов.

Инструкция: Каждому из вас присвоена уникальная задача. Ваша цель — написать законченную программу на C++, которая решает поставленную задачу. Код должен быть чистым, читаемым и, что самое главное, корректно управлять памятью (не должно быть утечек). Использование классов и STL (векторов, умных указателей) на этом этапе запрещено. Мы работаем только с new и delete[].


Список задач для семинара

  1. 1.     Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и выводит на экран.
  2. 2.     Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его первыми n членами арифметической прогрессии (первый член и разность также вводятся с клавиатуры) и выводит массив.
  3. 3.     Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его случайными числами от 0 до 99 и выводит на экран.
  4. 4.     Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и находит сумму всех элементов.
  5. 5.     Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и находит минимальный элемент.
  6. 6.     Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и находит максимальный элемент.
  7. 7.     Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и выводит все четные элементы массива.
  8. 8.     Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и выводит все элементы массива в обратном порядке.
  9. 9.     Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и подсчитывает количество положительных элементов.
  10. 10.  Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и проверяет, является ли массив палиндромом (читается одинаково слева направо и справа налево).
  11. 11.  Напишите программу, которая запрашивает у пользователя два целых числа rows и cols, создает динамический двумерный массив (матрицу) целых чисел размера rows x cols, заполняет его с клавиатуры и выводит на экран.
  12. 12.  Напишите программу, которая запрашивает у пользователя два целых числа rows и cols, создает динамический двумерный массив целых чисел размера rows x cols, заполняет его случайными числами от 1 до 50 и выводит на экран.
  13. 13.  Напишите программу, которая запрашивает у пользователя два целых числа rows и cols, создает динамический двумерный массив целых чисел размера rows x cols, заполняет его с клавиатуры и находит сумму всех элементов матрицы.
  14. 14.  Напишите программу, которая запрашивает у пользователя два целых числа rows и cols, создает динамический двумерный массив целых чисел размера rows x cols, заполняет его с клавиатуры и выводит на экран сумму элементов каждой строки.
  15. 15.  Напишите программу, которая запрашивает у пользователя размер квадратной матрицы n, создает динамический двумерный массив целых чисел размера n x n, заполняет его с клавиатуры и выводит на экран элементы главной диагонали.
  16. 16.  Напишите программу, которая запрашивает у пользователя размер квадратной матрицы n, создает динамический двумерный массив целых чисел размера n x n, заполняет его с клавиатуры и выводит на экран элементы побочной диагонали.
  17. 17.  Напишите программу, которая запрашивает у пользователя два целых числа rows и cols, создает динамический двумерный массив целых чисел размера rows x cols, заполняет его с клавиатуры и находит максимальный элемент в матрице.
  18. 18.  Напишите программу, которая запрашивает у пользователя два целых числа rows и cols, создает динамический двумерный массив целых чисел размера rows x cols, заполняет его с клавиатуры и транспонирует его (меняет строки и столбцы местами). Результат вывести в новой матрице.
  19. 19.  Напишите программу, которая запрашивает у пользователя целое число n, создает два динамических массива целых чисел размера n (A и B), заполняет их с клавиатуры, затем создает третий массив C, где каждый элемент C[i] = A[i] + B[i], и выводит массив C.
  20. 20.  Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и затем циклически сдвигает элементы на 1 позицию влево (первый элемент становится последним).
  21. 21.  Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и затем циклически сдвигает элементы на 1 позицию вправо (последний элемент становится первым).
  22. 22.  Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и удаляет из него все четные элементы (создает новый массив нужного размера). Выведите новый массив.
  23. 23.  Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и находит второй по величине элемент массива.
  24. 24.  Напишите программу, которая запрашивает у пользователя два целых числа rows и cols, создает два динамических двумерных массива целых чисел размера rows x cols (A и B), заполняет их с клавиатуры, затем создает третий массив C, где каждый элемент C[i][j] = A[i][j] * B[i][j], и выводит массив C.
  25. 25.  Напишите программу, которая запрашивает у пользователя два целых числа m и n, создает динамический массив целых чисел размера m, затем расширяет его до размера n (n > m), заполняя новые элементы нулями, и выводит итоговый массив.
  26. 26.  Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и переворачивает массив (первый элемент меняется местами с последним, второй с предпоследним и т.д.).
  27. 27.  Напишите программу, которая запрашивает у пользователя два целых числа rows и cols, создает динамический двумерный массив целых чисел размера rows x cols, заполняет его с клавиатуры и выводит на экран номер строки с наибольшей суммой элементов.
  28. 28.  Напишите программу, которая запрашивает у пользователя два целых числа rows и cols, создает динамический двумерный массив целых чисел размера rows x cols, заполняет его с клавиатуры и проверяет, есть ли в матрице отрицательные элементы.
  29. 29.  Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и сортирует массив методом "пузырька".
  30. 30.  Напишите программу, которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и находит наиболее часто встречающийся элемент (если таких несколько, вывести любой).

Фундаментальные правила использования <span style="font-size:10.0pt;font-family:Consolas; mso-fareast-font-family:"Times New Roman";mso-fareast-theme-font:major-fareast; color:#0F1115;background:#EBEEF2">new</span> и <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete</span>

1. Парность и соответствие операторов

Это самое важное правило, невыполнение которого ведет к неопределенному поведению.

·        <span style="font-size:10.0pt;font-family: Consolas;mso-fareast-font-family:"Times New Roman";mso-fareast-theme-font:major-fareast; color:#0F1115;background:#EBEEF2">new</span> → <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete</span>

·        <span style="font-size:10.0pt;font-family: Consolas;mso-fareast-font-family:"Times New Roman";mso-fareast-theme-font:major-fareast; color:#0F1115;background:#EBEEF2">new[]</span> → <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete[]</span>

ВНИМАНИЕ! Использование <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete</span> вместо <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete[]</span> для массива (и наоборот) — это критическая ошибка.

// ДЛЯ ОДНОГО ОБЪЕКТА
int* single_value = new int(42); // new
// ... использование ...
delete single_value;             // delete
 
// ДЛЯ МАССИВА ОБЪЕКТОВ
int* array = new int[10]; // new[]
// ... использование ...
delete[] array;           // delete[]

2. Гарантия освобождения памяти (Правило RAII)

У каждого <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">new</span> должен быть свой <span style="font-size:10.0pt;font-family:Consolas; mso-fareast-font-family:"Times New Roman";mso-fareast-theme-font:major-fareast; color:#0F1115;background:#EBEEF2">delete</span> в коде. Память не освобождается сама по себе, когда указатель выходит из области видимости. Указатель уничтожается, а память — нет. Это приводит к утечке памяти.

// ПЛОХО: УТЕЧКА ПАМЯТИ
void leak_memory() {
    int* array = new int[100];
    // ... использование массива ...
    return; // Указатель array уничтожается, но память под массив из 100 int'ов не освобождена!
} // УТЕЧКА 400 байт (если sizeof(int) == 4)
 
// ХОРОШО: ПАМЯТЬ ОСВОБОЖДЕНА
void no_leak() {
    int* array = new int[100];
    // ... использование массива ...
    delete[] array; // Память освобождена корректно
    return;
}

3. Обнуление указателя после удаления (Best Practice)

После вызова <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete</span>/<span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete[]</span> память становится невалидной. Указатель при этом продолжает хранить старый адрес ("висячий указатель"). Любая попытка использования этого указателя — неопределенное поведение.

int* ptr = new int(10);
delete ptr; // Память освобождена.
 
// ptr теперь "висячий указатель" (dangling pointer).
// *ptr = 20; // КАТАСТРОФА! Неопределенное поведение.
 
// Правило: обнуляйте указатель после удаления.
ptr = nullptr; // Теперь он безопасен. Попытка удалить nullptr безопасна (ничего не произойдет).
if (ptr != nullptr) { // Проверка стала осмысленной.
    *ptr = 20;
}

4. Проверка на <span style="font-size:10.0pt; font-family:Consolas;mso-fareast-font-family:"Times New Roman";mso-fareast-theme-font: major-fareast;color:#0F1115;background:#EBEEF2">nullptr</span> перед удалением (Защитное программирование)

Операторы <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete</span> и <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete[]</span> являются безопасными для нулевых указателей. Их можно применять к указателю со значением <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">nullptr</span>, и ничего не произойдет.

int* ptr = nullptr;
delete ptr; // Абсолютно безопасно. Никаких действий не производится.
 
// Поэтому часто используют такую конструкцию:
if (ptr != nullptr) {
    delete ptr;
    ptr = nullptr;
}
// Или просто:
delete ptr;
ptr = nullptr;

Проверка <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">if</span> не обязательна, но делает код более явным и читаемым.

5. Правило одного владельца (Кто отвечает за удаление?)

В коде без умных указателей должен быть четко определен один "владелец" памяти — часть кода (например, функция или модуль), которая берет на себя ответственность за вызов <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete</span>.

// Функция-создатель является владельцем и должна удалить память.
int* create_array(size_t size) {
    return new int[size];
}
 
void main() {
    int* my_array = create_array(100); // Владелец памяти теперь - функция main.
    // ... использование ...
    delete[] my_array; // main, как ответственный владелец, обязан удалить массив.
    my_array = nullptr;
}

Путаница в вопросе владения — частая причина утечек и двойного удаления.

Особые правила для двумерных массивов (массивов массивов)

Это прямое следствие основных правил, но его стоит выделить отдельно из-за сложности.

Правило: Освобождение памяти должно происходить в порядке, обратном выделению.

// 1. ВЫДЕЛЕНИЕ ПАМЯТИ
int rows = 5, cols = 10;
// Сначала создаем массив указателей.
int** matrix = new int*[rows]; // Шаг 1: new[] для массива указателей.
 
// Затем для каждого указателя в массиве создаем свой массив.
for (int i = 0; i < rows; ++i) {
    matrix[i] = new int[cols]; // Шаг 2: new[] для каждого подмассива.
}
 
// 2. ОСВОБОЖДЕНИЕ ПАМЯТИ (ОБРАТНЫЙ ПОРЯДОК!)
// Сначала удаляем каждый подмассив.
for (int i = 0; i < rows; ++i) {
    delete[] matrix[i]; // Шаг 1: delete[] для каждого подмассива.
    matrix[i] = nullptr; // Best practice
}
// Затем удаляем массив указателей.
delete[] matrix; // Шаг 2: delete[] для основного массива.
matrix = nullptr;

Итоговая памятка

1.     Соответствие: <span lang="EN-US" style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2; mso-ansi-language:EN-US">new</span>/<span lang="EN-US" style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family: "Times New Roman";mso-fareast-theme-font:major-fareast;color:#0F1115; background:#EBEEF2;mso-ansi-language:EN-US">delete</span><span lang="EN-US" style="font-size:10.0pt;font-family: Consolas;mso-fareast-font-family:"Times New Roman";mso-fareast-theme-font:major-fareast; color:#0F1115;background:#EBEEF2;mso-ansi-language:EN-US">new[]</span>/<span lang="EN-US" style="font-size:10.0pt;font-family:Consolas; mso-fareast-font-family:"Times New Roman";mso-fareast-theme-font:major-fareast; color:#0F1115;background:#EBEEF2;mso-ansi-language:EN-US">delete[]</span>.

2.     Не теряйте указатель: Убедитесь, что вызов <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete</span> произойдет после всех операций с памятью и до того, как указатель будет потерян.

3.     Один <span style="font-size:10.0pt; font-family:Consolas;mso-fareast-font-family:"Times New Roman";mso-fareast-theme-font: major-fareast;color:#0F1115;background:#EBEEF2">new</span> — один <span style="font-size:10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">delete</span>: На каждую успешную операцию выделения — ровно одна операция освобождения.

4.     Удаляйте в обратном порядке: Для сложных структур (как двумерные массивы).

5.     Обнуляйте после удаления: Защита от висячих указателей.

6.     Проверяйте на <span style="font-size: 10.0pt;font-family:Consolas;mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:major-fareast;color:#0F1115;background:#EBEEF2">nullptr</span>: Для безопасности и ясности кода.

 

1.     Запрещено использование STL-контейнеров.

o   Нельзя использовать: std::vectorstd::arraystd::string и другие.

o   Вместо этого: Мы создаем "голые" массивы в куче (динамической памяти) с помощью оператора new[].

o   Почему? std::vector внутри себя использует new[], но скрывает от нас всю сложность (автоматическое управление памятью, изменение размера). Мы же учимся делать это вручную.

2.     Запрещено использование умных указателей.

o   Нельзя использовать: std::unique_ptrstd::shared_ptr.

o   Вместо этого: Мы работаем с обычными ("сырыми", raw) указателями (int*double*int**).

o   Почему? Умные указатели автоматически вызывают delete/delete[] при выходе из области видимости, предотвращая утечки памяти. Мы же учимся управлять этим процессом вручную, чтобы осознать всю ответственность.

3.     Запрещено использование ООП.

o   Мы не создаем классы-обертки для массивов, которые в своих конструкторах/деструкторах вызывали бы new[]/delete[]. Мы пишем чисто процедурный код (функции main, возможно, вспомогательные функции).

Конкретные примеры:

Разрешено и является целью семинара:

// Создание одномерного динамического массива

int n;

std::cin >> n;

int* my_array = new int[n]; // Используем new[]

 

// ... работа с массивом my_array[i] ...

 

// Обязательное освобождение памяти

delete[] my_array; // Используем delete[]

// Создание двумерного динамического массива (матрицы)

int rows, cols;

std::cin >> rows >> cols;

int** matrix = new int*[rows]; // Массив указателей

for (int i =  ;0; i < rows; ++i) {

    matrix[i] = new int[cols]; // Каждому указателю - свой массив

}

 

// ... работа с матрицей matrix[i][j] ...

 

// Освобождение памяти в обратном порядке

for (int i = 0; i < rows; ++i) {

    delete[] matrix[i]; // Сначала удаляем каждую строку

}

delete[] matrix; // Затем удаляем массив указателей

Запрещено на этом семинаре:

// Запрещено: использование std::vector

#include <vector>

std::vector<int> vec(n); // Памятью управляет STL, а не мы.

 

// Запрещено: использование умных указателей

#include <memory>

auto smart_array = std::make_unique<int[]>(n); // delete[] вызовется сам.

 

// Запрещено: создание класса-обертки

class MyArray {

    int* data;

    size_t size;

public:

    MyArray(size_t n) : size(n), data(new int[n]) {} // new[] в конструкторе

    ~MyArray() { delete[] data; } // delete[] в деструкторе

};

// Памятью управляет деструктор класса, а не мы вручную в main.

·        Фундаментальное понимание:  память — это конечный ресурс, который вы явно запрашиваете у системы и обязаны вернуть.

·        Понимание ценности современных инструментов: Пропустив через себя сложность ручного управления, вы по-настоящему оцените удобство std::vector и умных указателей в будущем.

·        Отладка: В реальной жизни вы столкнетесь с legacy-кодом, где используется ручное управление памятью. Навыки, полученные здесь, критически важны для поиска и исправления утечек памяти и других ошибок.

Итог: На этом занятии вы — "системный программист". Вы сами отвечаете за каждый байт выделенной памяти. Цель — выработать привычку: на каждую успешную операцию new[] должен быть ровно один корректный delete[].

Последнее изменение: Пятница, 26 сентября 2025, 17:52