Динамические структуры данных

В общее меню

Введение

Основные
инструкции


Структурированные типы данных

Подпрограммы

Рекурсия

Классы

Динамические структуры данных

Сортировка
Введение

В предыдущих обзорах мы рассматривали программирование, связанное с обработкой только статических данных. Статическими величинами называются такие, память под которые выделяется во время компиляции и сохраняется в течение всей работы программы.

В языках программирования существует и другой способ выделения памяти под данные, который называется динамическим. В этом случае память под величины отводится во время выполнения программы. Такие величины будем называть динамическими. Раздел оперативной памяти, распределяемый статически, называется статической памятью; динамически распределяемый раздел памяти называется динамической памятью (динамически распределяемой памятью).

Использование динамических величин предоставляет программисту ряд дополнительных возможностей. Во-первых, подключение динамической памяти позволяет увеличить объем обрабатываемых данных. Во-вторых, если потребность в каких-то данных отпала до окончания программы, то занятую ими память можно освободить для другой информации. В-третьих, использование динамической памяти позволяет создавать структуры данных переменного размера.

Работа с динамическими величинами связана с использованием еще одного типа данных — ссылочного типа. Величины, имеющие ссылочный тип, называют указателями.

Указатель содержит адрес поля в динамической памяти, хранящего величину определенного типа. Сам указатель располагается в статической памяти.

Адрес величины — это номер первого байта поля памяти, в котором располагается величина. Размер поля однозначно определяется типом.

Величина ссылочного типа (указатель) описывается следующим образом:

		имя_типа *<идентификатор>;

Вот примеры описания указателей:

	
  int *P1;
  char *P2;  

Здесь P1 — указатель на динамическую величину целого типа; P2 — указатель на динамическую величину символьного типа.

Сами динамические величины не требуют описания в программе, поскольку во время компиляции память под них не выделяется. Во время компиляции память выделяется только под статические величины. Указатели — это статические величины, поэтому они требуют описания.

Каким же образом происходит выделение памяти под динамическую величину? Память под динамическую величину, связанную с указателем, выделяется в результате выполнения стандартной процедуры malloc. Формат обращения к этой процедуре:

		
	идентификатор = (тип_идентификатора*) malloc (sizeof(тип_идентификатора))

Считается, что после выполнения этого оператора создана динамическая величина, имя которой имеет следующий вид:

		<имя динамической величины> = *<указатель>

Пусть в программе, в которой имеется приведенное выше описание, присутствуют следующие операторы:

	P1 = (int*) malloc (sizeof(int));
	P2 = (char*) malloc (sizeof(char));

После их выполнения в динамической памяти оказывается выделенным место под три величины (две скалярные и один массив), которые имеют идентификаторы:

	*P1, *P2

Например, обозначение *P1 можно расшифровать так: динамическая переменная, на которую ссылается указатель P1.

Дальнейшая работа с динамическими переменными происходит точно так же, как со статическими переменными соответствующих типов. Им можно присваивать значения, их можно использовать в качестве операндов в выражениях, параметров подпрограмм и пр. Например, если переменной *P1 нужно присвоить число 25, переменной *P2 присвоить значение символа "A", то это делается так:

		*P1 = 25;
		*P2 = 'A';		

Кроме процедуры malloc значение указателя может определяться оператором присваивания:

			<указатель> = <ссылочное выражение>;

В качестве ссылочного выражения можно использовать

  • указатель;
  • ссылочную функцию (т.е. функцию, значением которой является указатель);
  • константу NULL.

NULL — это зарезервированная константа, обозначающая пустую ссылку, т.е. ссылку, которая ни на что не указывает. При присваивании базовые типы указателя и ссылочного выражения должны быть одинаковы. Константу NULL можно присваивать указателю с любым базовым типом.

До присваивания значения ссылочной переменной (с помощью оператора присваивания или функции malloc) она является неопределенной.

Рассмотрим пример. Пусть в программе описаны следующие указатели:

		int *D, *P;		

Тогда допустимыми являются операторы присваивания

		D = P; K = NULL;
поскольку соблюдается принцип соответствия типов. Оператор K = D ошибочен, т.к. базовые типы у правой и левой части разные.

Если динамическая величина теряет свой указатель, то она становится "мусором". В программировании под этим словом понимают информацию, которая занимает память, но уже не нужна.

Представьте себе, что в программе, в которой присутствуют описанные выше указатели, в разделе операторов записано следующее:

  D = (int*) malloc (sizeof(int));
  P = (int*) malloc (sizeof(int));  
{Выделено место в динамической памяти под две целые переменные. 
 Указатели получили соответствующие значения}
  *D = 3; *P = 5;
{Динамическим переменным присвоены значения}
  P = D;
{Указатели P и D стали ссылаться на одну и ту же величину, равную 3}
  cout << *P << *D; {Дважды напечатается число 3}

Таким образом, динамическая величина, равная 5, потеряла свой указатель и стала недоступной. Однако место в памяти она занимает. Это и есть пример возникновения "мусора". На схеме показано, что произошло в результате выполнения оператора P = D.

В Паскале имеется стандартная процедура, позволяющая освобождать память от данных, потребность в которых отпала. Ее формат:

		free(<указатель>);

Например, если динамическая переменная *P больше не нужна, то оператор

		free(P);

удалит ее из памяти. После этого значение указателя P становится неопределенным. Особенно существенным становится эффект экономии памяти при удалении больших массивов.

В версиях Турбо-C++, работающих под операционной системой MS DOS, под данные одной программы выделяется 64 килобайта памяти (или, если быть точнее, 65520 байт). Это и есть статическая область памяти. При необходимости работать с большими массивами информации этого может оказаться мало. Размер динамической памяти — много больше (сотни килобайт). Поэтому использование динамической памяти позволяет существенно увеличить объем обрабатываемой информации.

Следует отчетливо понимать, что работа с динамическими данными замедляет выполнение программы, поскольку доступ к величине происходит в два шага: сначала ищется указатель, затем по нему — величина. Как это часто бывает, действует "закон сохранения неприятностей": выигрыш в памяти компенсируется проигрышем во времени.

Пример. Дан текстовый файл размером не более 64 Кб, содержащий действительные числа, по одному в каждой строке. Переписать содержимое файла в массив, разместив его в динамически распределяемой памяти. Вычислить среднее значение элементов массива. Очистить динамическую память. Создать целый массив размером 10000, заполнить его случайными целыми числами в диапазоне от –100 до 100 и вычислить его среднее значение.

Назад Вперед


Рейтинг ресурсов УралWeb

 

© Шестаков А.П., 2000-2007
Сайт создан в системе uCoz