Динамическое выделение памяти
В языке С++ память можно выделять динамически. Динамическое распределение памяти используется, прежде всего, тогда, когда заранее неизвестно, сколько объектов понадобится в программе, и понадобятся ли они вообще. С помощью динамического распределения памяти можно гибко управлять временем жизни объектов, например, выделить память не в самом начале программы (как для глобальных переменных), но, тем не менее, сохранять нужные данные в этой памяти до конца программы. Распределяя подобным образом память динамически, ваша программа непрерывно изменяет свои потребности без дополнительного программирования. Если ваша программа запрашивает память во время выполнения, она указывает требуемое количество памяти, а используемый оператор языка C++ возвращает указатель на эту память.В этом параграфе рассматриваются действия, которые должна выполнить ваша программа для динамического распределения памяти, а впоследствии освобождения памяти во время выполнения. Вы освоите следующие основные концепции:
Динамическое распределение памяти во время выполнения является чрезвычайно полезной возможностью.
ИСПОЛЬЗОВАНИЕ ОПЕРАТОРА new Память для величины какого-либо типа можно выделить, выполнив операцию new. В качестве операнда выступает название типа, а результатом является адрес выделенной памяти. long* lp; lp = new long; // выделить память под новое целое число
float* cp; // создать новый объект типа float cp = new float; Созданный таким образом объект существует до тех пор, пока память не будет явно освобождена с помощью операции delete. В качестве операнда delete должен быть задан адрес, возвращенный операцией new: delete lp; delete cp; Если необходимо динамически создать массив, то нужно использовать немного другую форму new. Предположим, что вашей программе необходим массив для хранения 50 целочисленных значений. Используя оператор new, вы можете заказать эту память, как показано ниже: int *buffer = new int[50]; Говоря кратко, если оператор new успешно выделяет память, он возвращает указатель на начало области этой памяти. В данном случае, поскольку программа распределяет память для хранения массива целых чисел, она присваивает возвращаемый указатель переменной, определенной как указатель на тип int. Если оператор new не может выделить запрашиваемый вами объем памяти, он возвратит NULL-указатель, который содержит значение 0. Каждый раз, когда программа динамически распределяет память с использованием оператора new, она должна проверять возвращаемое оператором new значение, чтобы определить, не равно ли оно NULL. Чтобы определить, выделил ли оператор new память, ваша программа должна сравнить значение указателя с NULL, как показано в примерах ниже: Пример 1. Следующая программа использует оператор new для получения указателя на 100-элементный целочисленный массив: #include "stdafx.h" #include <iostream.h> void main() { Как видите, программа сразу проверяет значение, присвоенное оператором new переменной-указателю. Если указатель содержит значение NULL, значит new не смог выделить запрашиваемый объем памяти. Если же указатель содержит не NULL, следовательно, new успешно выделил память и указатель содержит адрес начала блока памяти.
Пример 2. Следующая программа использует оператор new для распределения памяти под массив из 500 значений с плавающей точкой: float *array = new float[100]; if (array!= NULL) cout << "Память выделена успешно"; Поскольку эта программа "жестко закодирована" на объем требуемой памяти, возможно, вам потребуется ее редактировать и перекомпилировать, если возникнет необходимость, чтобы программа выделила меньше или больше памяти. Как уже обсуждалось, одна из причин существования динамического распределения памяти состоит в том, чтобы избавиться от необходимости редактировать и перекомпилировать программу при изменении требований к объему памяти. Когда ваша программа использует оператор new для динамического распределения памяти, то вполне вероятно, что она сама посчитала количество будущих элементов и запросит выделить соответствующее количество памяти или запросит это значение у пользователя. Пример 3. Следующая программа запрашивает у пользователя количество памяти, которое необходимо выделить для хранения массива символов, и затем распределяет память, используя оператор new: #include "stdafx.h" #include <iostream> using namespace std; void main() { char *pointer; { ОСВОБОЖДЕНИЕ ПАМЯТИ Оператор C++ new позволяет вашей программе выделять память динамически во время выполнения. Если вашей программе больше не нужна выделенная память, она должна ее освободить, используя оператор delete. Для освобождения памяти с использованием оператора delete вы просто указываете этому оператору указатель на данную область памяти, как показано ниже: delete pointer; Пример 4. Следующая программа использует оператор delete для освобождения выделенной с помощью оператора new памяти:
#include "stdafx.h" #include <string.h> #include <iostream> using namespace std; void main() { По умолчанию, если ваша программа не освобождает выделенную ей память до своего завершения, операционная система автоматически освобождает эту память после завершения программы. Однако если ваша программа использует оператор delete для освобождения памяти по мере того, как она (память) становится ненужной, то эта память вновь становится доступной для других целей (возможно, для вашей программы, которая опять будет использовать оператор new, или для операционной системы). Пример 5. Следующая программ выделяет память для хранения массива из 1000 целочисленных значений. Затем она заносит в массив значения от 1 до 1000, выводя их на экран. Потом программа освобождает эту память и распределяет память для массива из 2000 значений с плавающей точкой, занося в массив значения от 1.0 до 2000.0: #include "stdafx.h" #include <iostream> using namespace std; const int n=10; int main() { float *array2; int *array1 = new int[n]; int i; if (array1!=NULL) { for (i = 0; i < n; i++) array1[i]=i+1; for (i = 0; i < n; i++) cout<<array1[i]<<' '; delete array1; }
cout <<endl; array2=new float[2*n]; if (array2!= NULL) { for (i = 0; i < 2*n; i++) array2[i]=(i+1)*1.0; for (i = 0; i < 2*n; i++) cout << array2[i] << ' '; delete array2; } cout <<endl; return 0;
} Как правило, ваша программа должна освобождать память с помощью оператора delete по мере того, как память становится ей не нужной. Выводы: 1. Способность выделять память динамически во время выполнения снимает с ваших программ зависимость от фиксированных размеров массивов. 2. Если оператор new успешно выделяет требуемую вашей программой память, он возвращает указатель на начало области этой памяти. 3. Если оператор new не может выделить требуемую вашей программой память, он возвращает NULL-указатель, который содержит значение 0. 4. Каждый раз, когда ваша программа распределяет память динамически с использованием оператора new, она должна проверять значение возвращаемого оператором new указателя, чтобы определить, не равен ли он NULL, что указывает на невозможность выделения памяти.
5. Используя указатель на массив, ваша программа может обращаться к памяти, выделенной с помощью оператора new. 6. Оператор new выделяет память из блока неиспользуемой памяти, называемой свободной памятью. 7. В зависимости от вашей операционной системы и модели памяти компилятора размер свободной памяти может быть различным. В среде MS-DOS свободная память может быть ограничена 64 Кбайт. 8. Если вашей программе больше не нужна выделенная память, она должна освободить ее (вернуть в свободную память), используя оператор delete. Структуры данных Понятие структуры Очень часто при обработке информации приходится работать с блоками данных, в которых присутствуют разные типы данных. Например, информация о книге в каталоге библиотеки включает в себя автора, название книги, год издания, количество страниц и т.п. Для хранения этой информации в памяти не подходит обычный массив, так как в массиве все элементы должны быть одного типа. Конечно, можно использовать несколько массивов разных типов, но это не совсем удобно. В современных языках программирования существует особый тип данных, который может включать в себя несколько элементов более простых (причем разных!) типов. Структура -это тип данных, который может включать в себя несколько полей – элементов разных типов (в том числе и другие структуры). В общем случае при работе со структурами следует выделить четыре момента: - объявление и определение типа структуры, - объявление структурной переменной, - инициализация структурной переменной, - использование структурной переменной. Данные типа структура, как и массивы, относятся к сложным структурам данных. Структура состоит из фиксированного числа элементов, называемых полями. Однако, в отличие от массива, поля могут быть различного типа. Например, структурой можно считать строку экзаменационной ведомости: Андреева С.В. 4 5 5 Данная структура состоит из четырех полей: одно поле - строка (ФИО студента) и три числовых поля (оценки студента по предметам). Поскольку структура - это новый тип данных, его надо предварительно объявить в начале программы. Определение типа структуры делается так: struct Имя { <тип> <имя 1-го поля>; <тип> <имя 2-го поля>; ………… <тип> <имя последнего поля>; }; Например, задание типа записи строки экзаменационной ведомости выглядит следующим образом: struct student { char fam[20]; int mathematics, informatics, history; }; Тогда при описании переменных можно использовать этот тип: struct student X;
Здесь X - переменная типа структура; struct student - тип; fam, mathematics, informatics, history - поля структуры. Отметим, что определение типа структуры может быть задано в программе на внешнем уровне, при этом имя пользовательского типа имеет глобальную видимость (при этом память не выделяется). Определение типа структуры также может быть сделано внутри функции, тогда имя типа структуры имеет локальную видимость. Чтобы упростить обращение к структурному типу, можно воспользоваться директивой #define. Например, для предыдущей структуры: #define stud struct student stud { char fam[20]; int mathematics, informatics, history; };
Теперь идентификатор stud заменит в любом месте программы громоздкий тип struct student. Теперь описании переменной типа структура будет выглядеть так: stud X; В более поздних версиях языка С ключевое слово typedef позволяет в программе создать синоним типа, который удобно использовать для объявления переменных структурного типа. Например: typedef struct student { char fam[20]; int mathematics, informatics, history; } STUD; Идентификатор STUD представляет собой синоним типа struct student. С помощью синонима STUD можно объявить переменную: STUD X; Для обращения к отдельным полям переменной типа структура используется составное имя: <имя переменной>.<имя поля> Например, для переменной X обращения к полям записываются следующим образом: X.fam, X. mathematics, X. informatics, X. history. При размещении в памяти структурной переменной можно выполнить ее инициализацию. Неявная инициализация производится для глобальных переменных, переменных класса static. Структурную переменную можно инициализировать явно при объявлении, формируя список инициализации в виде константных выражений. Формат: struct Имя переменная ={значение1, … значениеN}; Внутри фигурных скобок указываются значения полей структуры, например: struct student X={"Андреева С.В.", 4, 5, 5}; при этом первое значение записывается в первое поле, второе значение – во второе поле и т. д., а сами значения должны иметь тип, совместимый с типом поля. Обработка структур Над структурами возможны следующие операции: • присваивание значений структурной переменной; • получение адреса переменной с помощью операции &; • ввод и вывод значений переменных структурного типа; • сравнение полей переменных структурного типа. Операция присваивания применима, как к отдельным полям переменной структурного типа, так и к переменным в целом. При присваивании полям структуры значений, необходимо учитывать типы полей. Например: #include "stdafx.h" #include <string.h> typedef struct student // описание структуры { char fam[20]; int mathematics, informatics, history; } STUD; main() { STUD X; //описание переменной структурного типа strcpy(X.fam, "Андреева С.В. "); /*копирование фамилии в поле fam переменной Х */ X. mathematics=4; X. informatics=5; X. history=5; printf("\n %s %d %d %d", X.fam, X.mathematics, X.informatics,X.history);/*вывод информации из полей переменной Х ... } Для структурного типа возможно присваивание значений одной структурной переменной другой структурной переменной, при этом обе переменные должны иметь один и тот же тип. Присваивание значения одной переменной другой выполняется путем копирования значений соответствующих полей, например: ... main() { STUD X, Y; strcpy(X.fam,”Андреева С.В.”); X. mathematics=4; X. informatics=5; X. history=5; Y=X; // копирование информации из Х в Y printf("\n %s %d %d %d", Y.fam, Y.mathematics, Y.informatics, Y.history); ... } В результате выполнения этого копирования в Y.fam будет записано значение ”Андреева С.В.”, а в Y. mathematics – оценка 4, в. Y.informatics – 5 и в Y.history – тоже 5. Работа со структурной переменной обычно сводится к работе с отдельными полями структуры. Такие операции, как ввод с клавиатуры, сравнение полей и вывод на экран применимы только к отдельным полям. Например, в выше приведенном примере вывод информации о студенте осуществляется выводом значений отдельных полей с помощью функции printf(). С помощью структурного типа можно формировать массивы записей. Так, например информацию о 20 студентах можно хранить в массиве из 20 элементов структурного типа:
typedef struct student { char fam[20]; int mathematics, informatics, history; } STUD; main() { STUD Spis[20]; ... }
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|