Семинар 3-ч.2
Тема семинара: "Массивы и
динамическая память в C++
Цель: Закрепить
практические навыки выделения, использования и освобождения динамической памяти
для одномерных и двумерных массивов.
Инструкция: Каждому из вас
присвоена уникальная задача. Ваша цель — написать законченную программу на C++,
которая решает поставленную задачу. Код должен быть чистым, читаемым и, что
самое главное, корректно управлять памятью (не должно быть
утечек). Использование классов и STL (векторов, умных указателей) на этом
этапе запрещено. Мы работаем только с new и delete[].
Список задач для
семинара
- 1. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и выводит на экран.
- 2. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его первыми n членами
арифметической прогрессии (первый член и разность также вводятся с клавиатуры)
и выводит массив.
- 3. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его случайными числами от 0 до 99 и выводит на экран.
- 4. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и находит сумму всех элементов.
- 5. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и находит минимальный элемент.
- 6. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и находит максимальный элемент.
- 7. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и выводит все четные элементы массива.
- 8. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и выводит все элементы массива в обратном
порядке.
- 9. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и подсчитывает количество положительных
элементов.
- 10. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и проверяет, является ли массив палиндромом
(читается одинаково слева направо и справа налево).
- 11. Напишите программу,
которая запрашивает у пользователя два целых числа rows и cols, создает динамический
двумерный массив (матрицу) целых чисел размера rows x cols, заполняет его с клавиатуры и выводит на экран.
- 12. Напишите программу,
которая запрашивает у пользователя два целых числа rows и cols, создает динамический
двумерный массив целых чисел размера rows x
cols, заполняет его случайными числами от 1 до 50 и выводит на экран.
- 13. Напишите программу,
которая запрашивает у пользователя два целых числа rows и cols, создает динамический
двумерный массив целых чисел размера rows x
cols, заполняет его с клавиатуры и находит сумму всех элементов матрицы.
- 14. Напишите программу,
которая запрашивает у пользователя два целых числа rows и cols, создает динамический
двумерный массив целых чисел размера rows x
cols, заполняет его с клавиатуры и выводит на экран сумму элементов каждой
строки.
- 15. Напишите программу,
которая запрашивает у пользователя размер квадратной матрицы n, создает динамический двумерный массив целых чисел размера n x n, заполняет его с клавиатуры и выводит на экран элементы главной диагонали.
- 16. Напишите программу,
которая запрашивает у пользователя размер квадратной матрицы n, создает динамический двумерный массив целых чисел размера n x n, заполняет его с клавиатуры и выводит на экран элементы побочной
диагонали.
- 17. Напишите программу,
которая запрашивает у пользователя два целых числа rows и cols, создает динамический
двумерный массив целых чисел размера rows x
cols, заполняет его с клавиатуры и находит максимальный элемент в матрице.
- 18. Напишите программу,
которая запрашивает у пользователя два целых числа rows и cols, создает динамический
двумерный массив целых чисел размера rows x
cols, заполняет его с клавиатуры и транспонирует его (меняет строки и столбцы
местами). Результат вывести в новой матрице.
- 19. Напишите программу,
которая запрашивает у пользователя целое число n, создает два динамических массива целых чисел размера n (A и B), заполняет их с клавиатуры, затем создает третий массив C,
где каждый элемент C[i] = A[i] + B[i], и выводит массив C.
- 20. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и затем циклически сдвигает элементы на 1
позицию влево (первый элемент становится последним).
- 21. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и затем циклически сдвигает элементы на 1
позицию вправо (последний элемент становится первым).
- 22. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и удаляет из него все четные элементы (создает
новый массив нужного размера). Выведите новый массив.
- 23. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и находит второй по величине элемент массива.
- 24. Напишите программу,
которая запрашивает у пользователя два целых числа rows и cols, создает два
динамических двумерных массива целых чисел размера rows x cols (A и B), заполняет их с клавиатуры, затем создает третий массив C,
где каждый элемент C[i][j] = A[i][j] *
B[i][j], и выводит массив C.
- 25. Напишите программу,
которая запрашивает у пользователя два целых числа m и n, создает динамический
массив целых чисел размера m, затем расширяет его
до размера n (n > m), заполняя новые элементы нулями, и выводит итоговый массив.
- 26. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и переворачивает массив (первый элемент
меняется местами с последним, второй с предпоследним и т.д.).
- 27. Напишите программу,
которая запрашивает у пользователя два целых числа rows и cols, создает динамический
двумерный массив целых чисел размера rows x
cols, заполняет его с клавиатуры и выводит на экран номер строки с наибольшей
суммой элементов.
- 28. Напишите программу,
которая запрашивает у пользователя два целых числа rows и cols, создает динамический
двумерный массив целых чисел размера rows x
cols, заполняет его с клавиатуры и проверяет, есть ли в матрице отрицательные
элементы.
- 29. Напишите программу,
которая запрашивает у пользователя целое число n, создает динамический массив целых чисел размера n, заполняет его с клавиатуры и сортирует массив методом
"пузырька".
- 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::vector, std::array, std::string и другие.
o Вместо этого: Мы создаем
"голые" массивы в куче (динамической памяти) с помощью
оператора new[].
o Почему? std::vector внутри себя использует new[], но скрывает от нас
всю сложность (автоматическое управление памятью, изменение размера). Мы же
учимся делать это вручную.
2. Запрещено
использование умных указателей.
o Нельзя использовать: std::unique_ptr, std::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[].