IPB

> Лекция №10.2: Адреса и указатели. Списочные структуры данных
Чат
Форум
Загрузка...
 

страницы: 1 2 3 4

Содержание

Присваивания

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

p := q; {p : ^Integer; q : ^Byte}

Обойти эти ограничения позволяет универсальность нетипизированного указателя Pointer, совместимого с указателями любых типов:

{p : ^Integer; q : ^Byte; t : Pointer}
t := q;
p := t;

У указателей также существует свой «ноль», который означает, что указатель не указывает никуда:

p := nil;

Замечание: Если указатель не хранит конкретного адреса (его значение не определено), то это вовсе не означает, что он никуда не указывает. Скорее всего, он всё–таки указывает, но только в какую–нибудь неожиданную (хорошо, если не системную) область памяти.

Сравнения

Для указателей определены две операции сравнения: = и <>.

Две переменные, описанные как указатели на один и тот же тип данных, считаются совпадающими, если они указывают на одну и ту же область памяти.

Для разнотипных указателей сравнения невозможны: попытка записать

{p : ^Byte; q : ^Integer}
if p = q then
 WriteLn('yes');

вызовет ошибку уже на этапе компиляции.

Однако сравнивать типизированный и нетипизированный указатели можно.

Динамически распределяемая память

Поскольку к любой переменной можно обратиться двояко — по имени и по адресу, — есть возможность сократить эту избыточность и оставить только один способ. Как мы уже видели, наличие имени означает и наличие адреса. Иными словами, если вы дали переменной имя в разделе var, то в процессе компиляции у неё появится и адрес.

Задумаемся теперь: а если у переменной есть адрес, но нет имени, можно ли оперировать ею с прежней легкостью? Ответ на этот вопрос: «Да, можно!»

Итак, пусть у некоторой переменной нет имени. Тем не менее, можно расположить её в памяти, выделив под неё необходимое количество байтов, и т.д. У переменной будет адрес, будет значение, но не будет имени. Следовательно, обратиться к такой переменной можно будет только с помощью указателя.

«Безымянные» переменные отличаются от «нормальных» переменных:

  1. Нет имени — нечего описывать в разделе var.
  2. Ничего не описано, значит, на этапе компиляции память под переменную не выделена. Следовательно, необходима возможность выделять память (и отменять это выделение) прямо в процессе работы программы. Именно из–за этой гибкости такие переменные и называют динамическими.
  3. Если «потерять» указатель на переменную, то «никто не узнает, где могилка» её: переменная останется недоступным «мусором», занимая место в памяти, вплоть до конца работы программы.

Динамическое выделение памяти

Типизированные указатели

Для выделения памяти служит стандартная процедура New():

New(<имя_указателя>);

Эта процедура ищет в незанятой памяти подходящий по размеру кусок и, «застолбив» это место для безымянной динамической переменной, записывает в типизированный указатель адрес выделенного участка. Поэтому часто говорят, что процедура New() создаёт динамическую переменную. Размер выделяемого «куска памяти» напрямую зависит от типа указателя.

Например, если переменная p была описана как указатель на Integer–переменную, то процедура New(p) выделит два байта; под Real–переменную необходимо выделить шесть байт и т. д.

Нетипизированные указатели

Для того, чтобы выделить память, на которую будет указывать нетипизрованный указатель Pointer, нужно воспользоваться стандартной процедурой GetMem(p : Pointer; size : Word), которая выделит столько байт свободной памяти, сколько указано в переменной size.

Динамическое освобождение памяти

Типизированные указатели

Для уничтожения динамической переменной, то есть для освобождения занимаемой ею памяти, предназначена стандартная процедура

Dispose(<имя_типизир_указателя>)

Процедура Dispose() снимает пометку «занято» с определённого количества байтов, начиная с указанного адреса. Эта область памяти в дальнейшем считается свободной (хотя старое значение бывшей переменной в ней может некоторое время ещё оставаться). Количество освобождаемых байтов определяется типом указателя p.

В результате освобождения памяти при помощи процедуры Dispose() значение указателя, хранившего адрес освобождённой области, становится неопределённым. Во избежание проблем его лучше сразу же «обнулить»:

Dispose(p);
p := nil;
Нетипизированные указатели

Для того, чтобы освободить память, на которую указывает нетипизрованный указатель, нужно воспользоваться стандартной процедурой FreeMem(p : Pointer; size : Word), которая освободит в памяти столько байтов (начиная с указанного в переменной p адреса), сколько задано в переменной size.

Списочные структуры

Если для каждой динамической переменной описывать и хранить её «личный» указатель, то никакой выгоды на этапе выполнения программы получить не удастся: часть памяти, как и прежде, будет выделяться статически, а её общий объем даже увеличится — ведь каждый указатель требует для себя четыре байта.

Следовательно, нужно сделать так, чтобы место под хранение адресов будущих переменных также выделялось динамически. Решением этой проблемы и служат списки — специальные динамические структуры.

Списки применяются, например, в таких ситуациях:

  • программист заранее ничего не знает о том, какой именно объём памяти может потребоваться его программе;
  • некоторые (особенно «тяжёлые») переменные нужны поочередно, и после того как первые «отработали своё», их можно смело стирать из памяти, не дожидаясь конца работы программы, — освобождать место для других «тяжёлых» переменных;
  • в процессе обработки данных нужно провести большую работу по перестройке всей структуры «на ходу»; и т.д.

страницы: 1 2 3 4

Примечания

 
 К началу страницы 
 

Код для вставки: :: :: :: ГОСТ ::
Поделиться: //
 



-
Хостинг предоставлен компанией "Веб Сервис Центр" при поддержке компании "ДокЛаб"