- •Конспект лекций Часть 2 Оглавление
- •Часть 2 1
- •8. Указатели
- •8.1. Указатели Понятие указателя
- •Работа с указателями
- •Арифметика указателей
- •Ошибки при работе с указателями
- •If (p1) // Если указатель не равен 0, то все в порядке
- •8.2. Указатели и массивы
- •9. Функции и структура программы
- •9.1. Создание и использование функций Процедурный подход к разработке программ
- •Int Максимум_строки (int Строка)
- •Int Минимум_строки (int Строка)
- •Int Максимум_столбца (int Столбец)
- •Int Минимум_столбца (int Столбец)
- •Определение функций в программе
- •Завершение работы функции (инструкция return)
- •If ( Ошибка )
- •Список параметров функций
- •Обращение к функциям в программе
- •Передача данных по значению
- •Передача данных с помощью указателей
- •Передача данных по ссылке
- •Перегружаемые функции
- •Параметры по умолчанию
- •Функции с переменным числом параметров
- •Рекурсивное использование функций
- •Передача функций в качестве параметров
- •Встраиваемые функции (inline - функции)
- •Прототипы функций
- •9.2. Структура программы. Глобальные и локальные данные (области видимости и время жизни) Структура программы
- •Глобальные и локальные данные
- •Классы памяти
- •Многофайловые проекты
- •10. Структуры, объединения, перечисления
- •10.1. Структуры Определение структур
- •Доступ к полям структур
- •Указатели на структуры
- •Структурные параметры функций
- •Битовые поля структур
- •10.2. Объединения Обычные объединения
- •Анонимные объединения
- •10.3. Перечисления
- •Void WriteColor (тип_Спектр c )
- •11. Организация работы с файлами
- •11.1. Потоки для работы с файлами Общие сведения
- •Пример работы с файлом
- •11.2. Работа с файлами Создание потока, открытие и закрытие файла
- •Запись и чтение данных в текстовых файлах
- •Запись и чтение данных в двоичном режиме
- •If ( !File ) // Проверили удалось ли открыть файл
- •Как обнаружить конец файла?
- •Прямой доступ при работе с файлами
- •If ( !File ) // Проверили удалось ли открыть файл
- •Статус потоков ввода-вывода
- •Некоторые другие функции управления потоками ввода-вывода
- •Примеры по работе с файлами
- •12. Работа с динамической памятью Распределение памяти при работе программы
- •Динамическое выделение и освобождение памяти в стиле c
- •Возможные ошибки при работе с динамической памятью
- •Динамические массивы
- •Одномерные однонаправленные списки
- •Одномерные двунаправленные списки
- •Многомерные списки
12. Работа с динамической памятью Распределение памяти при работе программы
Схема распределения памяти под программу показана на следующем рисунке:
-
Большие адреса
Меньшие адреса
Стек
Динамическая область памяти
(heap- куча)
Область глобальных данных
Код программы
Область кода программы предназначена для хранения инструкций функций программы, обеспечивающих обработку данных. Данные в программе представляются переменными и константами. Для хранения глобальных данных (существуют в течение всего времени работы программы) предназначена область глобальных данных. Стек программы используется при вызове функций для передачи параметров и хранения локальных данных.
Распределение памяти для хранения всех обычных переменных осуществляется компилятором, и адреса и объемы соответствующих участков памяти (в области глобальных данных) жестко закреплены за этими переменными на все время работы программы и изменено быть не может.
Однако во многих задачах невозможно заранее предсказать, сколько места (количество переменных, объемы массивов и т.д.) потребуется для решения задачи – это так называемые задачи с неопределенной размерностью. Решить эту проблему можно лишь в том случае, если иметь механизм, позволяющий создавать новые объекты по мере возникновения необходимости в этих объектах или изменять объемы памяти, выделенные под эти объекты (например, объемы массивов).
Между областью глобальных данных и стеком располагается так называемая динамическая область памяти, которую как раз и можно использовать в процессе работы программы для реализации механизма динамического управления памятью.
Динамическое выделение и освобождение памяти в стиле C++
Для динамического управления памятью в языке C++ используются две инструкцииnewиdelete. Формат этих инструкций:
<Переменная-указатель> = new <Тип данных переменной-указателя>
delete <Переменная-указатель>
Инструкция new выделяет в динамической области участок памяти, достаточный для размещения данных, тип которых определяется типом данных переменной-указателя, и возвращает адрес этого участка. Этот адрес присваивается переменной-указателю. Например:
double *p; // Переменная-указатель на тип double
p = new double; // Выделение памяти
или так:
double *p = new double;
В этом примере инструкция new выделяет в динамической области участок памяти объемомsizeof (double)и присваивает адрес этого участка переменной-указателюp. Дальнейшая работа с переменной-указателем осуществляется как с обычным указателем на тип данныхdouble. Например:
*p = 3.14;
cout << *p * 2 << endl;// На экран выведено значение 6.28
cin >> *p; // Вводим с клавиатуры некоторое вещественное значение
cout << *p << endl;// На экран выведено значение, введенное с клавиатуры
Размер динамической области памяти ограничен, поэтому при многократном последовательном использовании инструкции newможет создаться ситуация, при которой попытка выделения очередного участка памяти с помощью операцииnewзавершится неудачей (возникнет ошибка, связанная с переполнением динамической области памяти). Для того чтобы избежать подобных ошибок, необходимо принудительно освобождать динамическую память с помощью инструкцииdelete:
delete p;
Инструкция deleteвозвращает участок памяти по адресуpв список свободной памяти, и в дальнейшем этот участок памяти может быть использован повторно для динамического размещения других данных.
Замечание. Инструкцииnewиdeleteэто парные инструкции, то есть они всегда должны использоваться совместно – каждой инструкцииnew должна соответствовать инструкцияdelete. Динамическая область памяти автоматически освобождается только при завершении программы, поэтому неконтролируемое использование инструкцииnewможет привести к переполнению динамической области памяти и, следовательно, к ошибкам в работе программы.
Тип данных переменной указателя может быть практически любым. Рассмотрим пример создания в динамической области некоторой структуры данных.
struct t_Person // Тип данных для "персоны"
{
char Fam[20]; // Фамилия
char Name[20]; // Имя
int Year; // Год рождения
};
setlocale ( 0, "" ); // Русификация консоли
t_Person *p = new t_Person; // Создаем структуру в динамической памяти
strcpy ( (*p).Fam, "Иванов" ); // Заносим фамилию
strcpy ( (*p).Name, "Иван" ); // Заносим имя
(*p).Year = 1995; // Заносим год рождения
cout << "Фамилия: " << (*p).Fam << endl; // Выводим фамилию
cout << "Имя: " << (*p). Name << endl; // Выводим имя
cout << "Год рождения: " << (*p).Year << endl; // Выводим год рождения
delete p; // Освобождаем память
Для обращения к отдельным полям структуры через переменную-указатель мы использовали следующие конструкции:
(*p).Fam, (*p).Name, (*p).Year
Здесь (*p) обеспечивает разыменование указателя (получение данных персоны, расположенных в памяти по адресуp), а затем с помощью оператора “точка” осуществляется обращение к данным соответствующего поля.
Существует другой способ доступа к полям структур через указатель на структуру с помощью оператора “стрелка” (не требующий предварительного разыменования указателя). Это делается так:
p -> Fam, p -> Name, p -> Year
То есть следующий вариант той же программы будет также корректным:
struct t_Person // Тип данных для "персоны"
{
char Fam[20]; // Фамилия
char Name[20]; // Имя
int Year; // Год рождения
};
setlocale ( 0, "" ); // Русификация консоли
t_Person *p = new t_Person; // Создаем структуру в динамической памяти
strcpy (p -> Fam, "Иванов" ); // Заносим фамилию
strcpy (p -> Name, "Иван" ); // Заносим имя
p -> Year = 1995; // Заносим год рождения
cout << "Фамилия: " << p -> Fam << endl; // Выводим фамилию
cout << "Имя: " << p -> Name << endl; // Выводим имя
cout << "Год рождения: " << p -> Year << endl; // Выводим год рождения
delete p; // Освобождаем память
Для некоторых типов данных одновременно с динамическим выделением памяти можно осуществлять и ее инициализацию. Например:
double *p = new double (3.14);// Инициализация значением 3.14
cout << *p << endl; // На экран выведено значение 3.14