Лабораторная работа №1. Динамическое выделение памяти, работа с указателями
Создание динамических массивов, работа с указателями, управление памятью.
Требования к заданию
ЛК1 Указатели
ЛК1 Стек и куча
ЛК1 Константный указатель
Теоретическая основа
Указатели и динамическая память
Указатель — это переменная, которая в качестве своего значения содержит адрес байта памяти.
Динамическая переменная — переменная, на которую указывает указатель (находится в динамически распределяемой области памяти).
Основные операции с указателями:
&
— операция получения адреса*
— операция разыменования указателяnew
— выделение памятиdelete
— освобождение памятиnew[]
— выделение памяти под массивdelete[]
— освобождение памяти массива
Задание 1. Одномерный динамический массив
Требования
Создать одномерный динамический массив типа int
, заполнить его случайными числами, вывести на экран. Размер массива необходимо хранить в первом элементе массива.
Реализация
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
// Функция генерации случайного массива
int* genRandArray(int size, int maxValue) {
// Выделяем память для массива (размер + 1 для хранения размера)
int* arr = new int[size + 1];
// Проверяем успешность выделения памяти
if (!arr) {
cout << "Ошибка выделения памяти" << endl;
return nullptr;
}
// Сохраняем размер в первом элементе
arr[0] = size;
// Заполняем массив случайными числами
for (int i = 1; i <= size; i++) {
arr[i] = rand() % maxValue + 1;
}
return arr;
}
// Функция печати массива
void print(int* arr) {
if (!arr) {
cout << "Массив не инициализирован" << endl;
return;
}
int size = arr[0]; // Получаем размер из первого элемента
cout << size << ": ";
for (int i = 1; i <= size; i++) {
cout << arr[i];
if (i < size) cout << " ";
}
cout << endl;
}
int main() {
srand(time(0)); // Инициализация генератора случайных чисел
int size = rand() % 10 + 1; // Размер от 1 до 10
int maxValue = 100;
int* arr = genRandArray(size, maxValue);
print(arr);
// Очистка выделенной памяти
delete[] arr;
return 0;
}
Пример вывода
7: 44 11 24 41 10 57 100
Задание 2. Двумерный динамический массив
Требования
Создать двумерный динамический массив типа int
, заполнить его случайными числами, вывести на экран. Размер каждого одномерного массива — произвольный (матрица не обязана быть прямоугольной).
Реализация
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
// Функция генерации случайного массива (из задания 1)
int* genRandArray(int size, int maxValue) {
int* arr = new int[size + 1];
if (!arr) {
cout << "Ошибка выделения памяти" << endl;
return nullptr;
}
arr[0] = size;
for (int i = 1; i <= size; i++) {
arr[i] = rand() % maxValue + 1;
}
return arr;
}
// Функция печати одномерного массива (из задания 1)
void print(int* arr) {
if (!arr) return;
int size = arr[0];
cout << size << ": ";
for (int i = 1; i <= size; i++) {
cout << arr[i];
if (i < size) cout << " ";
}
cout << endl;
}
// Функция генерации двумерного массива
int** genRandMatrix(int size, int maxValue) {
// Выделяем память для массива указателей
int** matrix = new int*[size];
if (!matrix) {
cout << "Ошибка выделения памяти для матрицы" << endl;
return nullptr;
}
// Создаем каждую строку с произвольным размером
for (int i = 0; i < size; i++) {
int rowSize = rand() % 8 + 1; // Размер строки от 1 до 8
matrix[i] = genRandArray(rowSize, maxValue);
if (!matrix[i]) {
// Если не удалось выделить память для строки, очищаем уже выделенные
for (int j = 0; j < i; j++) {
delete[] matrix[j];
}
delete[] matrix;
return nullptr;
}
}
return matrix;
}
// Функция печати двумерного массива
void printMatrix(int** matrix, int size) {
if (!matrix) {
cout << "Матрица не инициализирована" << endl;
return;
}
cout << size << endl; // Выводим количество строк
for (int i = 0; i < size; i++) {
print(matrix[i]); // Используем функцию печати одномерного массива
}
}
int main() {
srand(time(0));
int size = rand() % 10 + 1; // Количество строк от 1 до 10
int maxValue = 100;
int** matrix = genRandMatrix(size, maxValue);
printMatrix(matrix, size);
// Очистка памяти
if (matrix) {
for (int i = 0; i < size; i++) {
delete[] matrix[i];
}
delete[] matrix;
}
return 0;
}
Пример вывода
4
1: 15
5: 54 23 15 5 12
7: 1 32 51 42 51 100 12
3: 50 37 17
Полный код программы
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int* genRandArray(int size, int maxValue) {
int* arr = new int[size + 1];
if (!arr) {
cout << "Ошибка выделения памяти" << endl;
return nullptr;
}
arr[0] = size;
for (int i = 1; i <= size; i++) {
arr[i] = rand() % maxValue + 1;
}
return arr;
}
void print(int* arr) {
if (!arr) return;
int size = arr[0];
cout << size << ": ";
for (int i = 1; i <= size; i++) {
cout << arr[i];
if (i < size) cout << " ";
}
cout << endl;
}
int** genRandMatrix(int size, int maxValue) {
int** matrix = new int*[size];
if (!matrix) {
cout << "Ошибка выделения памяти для матрицы" << endl;
return nullptr;
}
for (int i = 0; i < size; i++) {
int rowSize = rand() % 8 + 1;
matrix[i] = genRandArray(rowSize, maxValue);
if (!matrix[i]) {
for (int j = 0; j < i; j++) {
delete[] matrix[j];
}
delete[] matrix;
return nullptr;
}
}
return matrix;
}
void printMatrix(int** matrix, int size) {
if (!matrix) {
cout << "Матрица не инициализирована" << endl;
return;
}
cout << size << endl;
for (int i = 0; i < size; i++) {
print(matrix[i]);
}
}
int main() {
srand(time(0));
cout << "=== ЗАДАНИЕ 1: Одномерный массив ===" << endl;
int size = rand() % 10 + 1;
int maxValue = 100;
int* arr = genRandArray(size, maxValue);
print(arr);
delete[] arr;
cout << "\n=== ЗАДАНИЕ 2: Двумерный массив ===" << endl;
size = rand() % 10 + 1;
int** matrix = genRandMatrix(size, maxValue);
printMatrix(matrix, size);
if (matrix) {
for (int i = 0; i < size; i++) {
delete[] matrix[i];
}
delete[] matrix;
}
return 0;
}
Ответы на контрольные вопросы
1. В чем разница между статической и динамической памятью? Преимущества, недостатки.
Статическая память:
- Размещение: В области данных программы (глобальные переменные) или в стеке (локальные переменные)
- Время выделения: На этапе компиляции
- Размер: Фиксированный, известен заранее
- Управление: Автоматическое (освобождается при выходе из области видимости)
- Преимущества: Быстрое выделение, автоматическое управление
- Недостатки: Фиксированный размер, ограниченный объем стека
Динамическая память:
- Размещение: В куче (heap)
- Время выделения: Во время выполнения программы
- Размер: Может изменяться во время выполнения
- Управление: Ручное (программист должен освобождать память)
- Преимущества: Гибкость размера, большой объем доступной памяти
- Недостатки: Медленнее выделение, возможны утечки памяти
2. Что такое указатель?
Указатель — это переменная, которая содержит адрес другой переменной в памяти. Указатель позволяет косвенно обращаться к данным через их адрес.
int x = 10;
int* p = &x; // p содержит адрес переменной x
3. Что означает тип указателя?
Тип указателя определяет:
- Тип данных, на которые указывает указатель
- Количество байт, которые будут читаться/записываться при разыменовании
- Правила арифметики указателей
int* p; // указатель на int (4 байта)
char* c; // указатель на char (1 байт)
double* d; // указатель на double (8 байт)
4. Какие операции допустимы по отношению к указателю?
- Присваивание адреса:
p = &x;
- Разыменование:
*p = 5;
- Арифметические операции:
p++
,p--
,p + n
,p - n
- Сравнение:
p1 == p2
,p1 != p2
,p1 < p2
- Присваивание NULL:
p = nullptr;
- Проверка на NULL:
if (p != nullptr)
5. Константность указателей
int x = 10, y = 20;
// Константный указатель (нельзя изменить адрес)
int* const p1 = &x;
*p1 = 15; // OK
// p1 = &y; // ОШИБКА
// Указатель на константу (нельзя изменить значение)
const int* p2 = &x;
// *p2 = 15; // ОШИБКА
p2 = &y; // OK
// Константный указатель на константу
const int* const p3 = &x;
// *p3 = 15; // ОШИБКА
// p3 = &y; // ОШИБКА
// Константный массив
const int arr[] = {1, 2, 3, 4, 5};
// Константная матрица
const int* const matrix[] = {arr, arr+2};
6. Размер (sizeof) указателя, от чего зависит
Размер указателя зависит от архитектуры процессора и операционной системы:
- 32-битные системы: 4 байта
- 64-битные системы: 8 байт
cout << "Размер int*: " << sizeof(int*) << " байт" << endl;
cout << "Размер char*: " << sizeof(char*) << " байт" << endl;
cout << "Размер double*: " << sizeof(double*) << " байт" << endl;
// Все указатели имеют одинаковый размер независимо от типа данных
7. Зачем необходимо чистить память?
Причины освобождения динамической памяти:
- Предотвращение утечек памяти: Неосвобожденная память остается занятой до завершения программы
- Экономия ресурсов: Освобожденная память может быть использована повторно
- Предотвращение фрагментации: Своевременное освобождение предотвращает фрагментацию кучи
- Стабильность программы: Утечки памяти могут привести к исчерпанию доступной памяти
// Правильное использование
int* p = new int[100];
// ... работа с массивом ...
delete[] p; // Обязательно освобождаем память
p = nullptr; // Обнуляем указатель для безопасности
// Для двумерных массивов
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
// ... работа с матрицей ...
for (int i = 0; i < rows; i++) {
delete[] matrix[i]; // Освобождаем каждую строку
}
delete[] matrix; // Освобождаем массив указателей
Правило: Каждому new
должен соответствовать delete
, каждому new[]
должен соответствовать delete[]
.