Студопедия
Случайная страница | ТОМ-1 | ТОМ-2 | ТОМ-3
АрхитектураБиологияГеографияДругоеИностранные языки
ИнформатикаИсторияКультураЛитератураМатематика
МедицинаМеханикаОбразованиеОхрана трудаПедагогика
ПолитикаПравоПрограммированиеПсихологияРелигия
СоциологияСпортСтроительствоФизикаФилософия
ФинансыХимияЭкологияЭкономикаЭлектроника

Руководство пользователя 19 страница




 

B.Pascal 7 & Objects /UG - 182 -

 

Как использовать указатели?

─────────────────────────────────────────────────────────────────

 

Теперь вы получили достаточно хорошее представление о том, в

каких ситуациях вы можете использовать указатели, и можно расс-

мотреть их фактическое применение. В данном разделе охватываются

следующие темы:

 

* Распределение динамических переменных.

 

* Освобождение выделенной для динамических переменных памя-

ти.

 

* Распределение и освобождение выделенных объемов памяти.

 

* Проверка доступного в динамически распределяемой области

пространства.

 

Borland Pascal предусматривает две пары процедур для выде-

ления и освобождения памяти, распределяемой для динамических пе-

ременных. Чаще всего используются процедуры New и Dispose, кото-

рые отвечают большинству потребностей. Процедуры GetMem и FreeMem

выполняют те же функции, но на более низком уровне.

 

 

Выделение памяти для динамических переменных

─────────────────────────────────────────────────────────────────

 

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

является распределение динамических переменных в динамически

распределяемой области памяти. Borland Pascal предусматривает два

способа выделения для указателя памяти: процедура New и процедура

GetMem.

 

Использование New как процедуры

 

New - это очень простая процедура. После описания перемен-

ной-указателя вы можете вызвать процедуру New для выделения

пространства в динамически распределяемой памяти для указываемого

переменной элемента. Приведем пример:

 

var

IntPointer: ^Integer;

StringPointer: ^String;

 

begin

New(IntPointer); { выделяет в динамически распреде-

ляемой области два байта }

New(StringPointer); { выделяет в динамически распреде-

. ляемой области 256 байт }

.

.

end.


 

B.Pascal 7 & Objects /UG - 183 -

 

 

Пример 8.2 Распределение динамической переменной с помощью

процедуры New.

 

После вызова процедуры New переменная-указатель указывает на

память, выделенную в динамически распределяемой памяти. В данном



примере IntPointer указывает на двухбайтовую область, выделенную

процедурой New, а IntPointer^ - это допустимая целочисленная пе-

ременная (хотя это целочисленное значение еще не определено).

Аналогично, StringPointer указывает на выделенный для строки

256-байтовый блок, а его разыменование дает доступную для исполь-

зования строковую переменную.

 

Использование New как функции

 

Кроме выделения памяти для конкретной динамической перемен-

ной вы можете использовать New как функцию, возвращающую указа-

тель конкретного типа. Например, если PInteger - это тип, опреде-

ленный как ^Integer, а IntPopinter имеет тип PInteger, то следую-

щие два оператора эквивалентны:

 

New(IntPointer);

IntPointer:= New(PInteger);

 

Это особенно полезно в случаях, когда может потребоваться

присваивать переменной-указателю элементы различных типов. Иногда

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

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

это только создав для процедуры и функции параметр:

 

SomeProcedure(New(PointerType));

 

В этом случае SomeProcedure будет добавлять передаваемый па-

раметр к некоторому списку. В противном случае распределяемая па-

мять будет потеряна. Библиотеки Borland Turbo Vision и Borland

Pascal широко используют этот метод для присваивания динамических

объектов спискам.


 

B.Pascal 7 & Objects /UG - 184 -

 

 

Использование New с объектами

 

Когда вы используете New как функцию или процедуру для выде-

ления динамического объекта, то можете добавить необязательный

второй параметр, который задает применяемый для инициализации

объекта конструктор. В Примере 8.3 первое обращение к New распре-

деляет пространство для объекта, но не инициализирует этот объ-

ект. Второй вызов выделяет память и вызывает для задания объекта

конструктор Init.

 

type

PMyObject = ^TMyObject;

TMyObject = object

constructor Init;

end;

 

var

MyObject, YourObject: PMyObject;

begin

New(MyObject); { объект не инициализируется }

New(YourObject, Init); { вызов Init для инициализации

объекта }

 

end.

 

Пример 8.3 Создание динамических объектов.

 

Примечание: Об объектах и их конструкторах рассказыва-

ется в Главе 9 "Объектно-ориентированное программирование".

 

 


 

B.Pascal 7 & Objects /UG - 185 -

 

Освобождение памяти,

выделенной для динамических переменных

─────────────────────────────────────────────────────────────────

 

Память, распределенная для переменных с помощью New, после

завершения работы с ними должна освобождаться. Это позволит ис-

пользовать динамически распределяемую память для других перемен-

ных. Чтобы освободить память, выделенную для динамической пере-

менной, вы должны использовать процедуру Dispose. В Примере 8.2

вы можете добавить следующее:

 

Dispose(StringPointer);

Dispose(IntPointer);

 

Нужно помнить, что если вы распределяете динамические пере-

менные с помощью New, то освобождать выделенную для них память

после завершения работы с этими переменными нужно с помощью

Dispose.

 

 

Процедуры GetMem и FreeMem

─────────────────────────────────────────────────────────────────

 

Иногда нежелательно выделять память тем способом, как это

делает New. Вам может потребоваться выделить больше или меньше

памяти, чем это делает New по умолчанию, либо до начала выполне-

ния вы можете просто не знать, сколько памяти вам нужно использо-

вать. Borland Pascal выполняет такое распределение с помощью про-

цедуры GetMem.

 

Процедура GetMem воспринимает два параметра: переменную-ука-

затель, для которой вы хотите распределить память, и число расп-

ределяемых байт.

 

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

 

Пусть, например, у вас есть прикладная программа, которая

считывает 1000 строк из файла и записывает их в динамическую па-

мять. Вы не знаете, насколько длинной будет каждая из этих строк,

поэтому вам потребуется описать строковый тип такого размера,

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

предположить, что не все строки имеют максимальную длину, то у

вас будет бесполезно использоваться память.

 

Чтобы решить эту проблему, вы можете считать каждую строку в

буфер, затем выделить столько памяти, сколько требуется для фак-

тических данных в строке. Пример этого показан ниже:

 

type PString = ^String;

 

var

ReadBuffer: String;

LinewRead: array[1..1000] of PString;


 

B.Pascal 7 & Objects /UG - 186 -

 

TheFile: Text;

LineNumber: Integer;

 

begin

Assign(TheFile, 'FOO.TXT');

Reset(TheFile);

for LineNumber:= 1 to 1000 do

begin

Readln(ReadBuffer);

GetMem(LinesRead[LineNumber], Length(ReadBuffer) + 1);

LinesRead[LineNumber]^:= ReadBuffer;

end;

end.

 

Пример. 8.4 Динамическое распределение памяти для строки.

 

Вместо выделения для строк 256К (256 символов на строку 1000

раз) вы выделили 4К (4 байта на указатель 1000 раз), плюс объем,

фактически занимаемый текстом.

 

Освобождение выделенной памяти

 

Аналогично тому, как требуется освобождать память, выделен-

ную с помощью New, вам нужно освобождать память, распределенную с

помощью процедуры GetMem. Это можно сделать с помощью процедуры

FreeMem. Аналогично тому, как каждому вызову New должен соответс-

твовать парный вызов Dispose, каждому вызову процедуры GetMem

должен соответствовать вызов FreeMem.

 

Как и GetMem, процедура FreeMem воспринимает два параметра:

освобождаемую переменную и объем освобождаемой памяти. Важно,

чтобы объем освобождаемой памяти точно совпадал с объемом выде-

ленной памяти. New и Dispose, основываясь на типе указателя,

всегда знают, сколько байт нужно выделять или освобождать. Но в

случае GetMem и FreeMem объем выделяемой памяти находится всецело

под вашим контролем.

 

Если вы освободите меньше байт, чем было выделено, то остав-

шиеся байты теряются (происходит "утечка" динамически распределя-

емой памяти). Если вы освободите большее число байт, чем было вы-

делено, то можете освободить память, распределенную для другой

переменной, что может привести к порче данных. В защищенном режи-

ме освобождение большего объема памяти, чем было выделено, вызо-

вет ошибку по нарушению защиты (GP).

 

Предположим, например, что вы собираетесь выделить память

для одной или более записей данных типа TCheck:

 

type

PCheck = ^ TCheck;

TCheck = record

Amount: Real;

Mounth: 1..12;


 

B.Pascal 7 & Objects /UG - 187 -

 

Day: 1..31;

Year: 1990..2000;

Payee: string[39];

end.

 

Пример 8.5 Простой тип записи.

 

Каждая запись типа TCheck занимает 50 байт, поэтому, если у

вас есть переменная ThisCheck типа PCheck, вы можете распределить

динамическую запись следующим образом:

 

GetMem(ThisGheck, 50);

 

а позднее освободить ее с помощью вызова:

 

FreeMem(ThisCheck, 50);

 

Использование с процедурой GetMem функции SizeOf

 

Однако убедиться, что вы каждый раз выделяете и освобождаете

один и тот же объем памяти, недостаточно. Вы должны обеспечить

распределение правильного объема памяти. Предположим, вы изменили

определение TCheck. Например, если вы переопределили TCheck.Payee

как 50-символьную строку вместо 39-символьной, то не сможете по-

лучить и освобождать достаточно памяти. Надежнее всего использо-

вать в программе функцию SizeOf, например:

 

GetMem(ThisCheck, SizeOf(TCheck));

.

.

.

 

FreeMem(ThisCheck, SizeOf(TCheck));

 

Это не только обеспечивает, что вы выделяете и освобождаете

один и тот же объем, но гарантирует, что при изменении размера

типа ваша программа все равно будет выделять нужную память.

 

 


 

B.Pascal 7 & Objects /UG - 188 -

 

Проверка объема доступной

динамически распределяемой памяти

─────────────────────────────────────────────────────────────────

 

В Borland Pascal определены две функции, возвращающие важную

информацию о динамически распределяемой области памяти: MemAvail

и MaxAvail.

 

Функция MemAvail возвращает общее число байт, доступных для

распределения в динамической памяти. Перед выделением большого

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

такой объем памяти доступен.

 

Функция MaxAvail возвращает размер наибольшего доступного

блока непрерывной памяти в динамически распределяемой области.

Первоначально при запуске программы MaxAvail равно MemAvail, пос-

кольку вся динамически распределяемая область памяти является

доступной и непрерывной. После распределения нескольких блоков

памяти пространство в динамически распределяемой области скорее

всего станет фрагментированным. Это означает, что между частями

свободного пространства имеются распределенные блоки. Функция

MaxAvail возвращает размер наибольшего свободного блока.

 

Подробнее о том, как Borland Pascal работает с динамически

распределяемой областью памяти, рассказывается Главе 21 ("Вопросы

использования памяти") "Руководства по языку".

 

 

Общие проблемы использования указателей

─────────────────────────────────────────────────────────────────

 

Указатели позволяют вам делать в Паскале некоторые важные

вещи, но есть пара моментов, которые при работе с указателями

нужно отслеживать. При использовании указателей допускаются сле-

дующие общие ошибки:

 

- разыменование неинициализированных указателей;

- потери динамически распределяемой памяти ("утечки").

 

 

Разыменование неинициализированных указателей

─────────────────────────────────────────────────────────────────

 

Одним из общих источников ошибок при работе с указателями

является разыменование указателя, который еще не был инициализи-

рован. Как и в случае других переменных Паскаля, значение пере-

менной-указателя не будет определено, пока вы не присвоите ей

значение, так что она сможет указывать на какой-то адрес в памя-

ти.

 

Перед использованием указателей им всегда нужно присваивать

значения. Если вы разыменовываете указатель, которому еще не

присвоено значение, то считанные из него данные могут представ-


 

B.Pascal 7 & Objects /UG - 189 -

 

лять собой случайные биты, а присваивание значения указываемому

элементу может затереть другие данные, вашу программу или даже

операционную систему. Это звучит несколько пугающе, но при опре-

деленной дисциплине такие вещи легко отслеживаются.

 

Использование пустого указателя

 

Чтобы избежать разыменования указателей, которые не указыва-

ют на что-либо значащее, нужен некоторый способ информирования о

том, что указатель недопустим. В Паскале предусмотрено зарезерви-

рованное слово nil, которое вы можете использовать в качестве со-

держательного значения указателей, которые в данный момент ни на

что не указывают. Указатель nil является допустимым, но ни с чем

не связанным. Перед разыменованием указателя вы должны убедиться,

что он отличен от nil (не пуст).

 

Предположим, например, что у вас есть функция, возвращающая

указатель на некоторый элемент в памяти. Вы можете указать, что

такая функция не может найти элемент, возвращая значение nil.

 

var ItemPointer: Pointer;

 

function FindIten: Pointer;

begin

.

.

.

{ найти элемент, возвращая указатель на него или nil,

если элемент не найден }

 

end;

 

begin

ItemPointer:= nil; { начнем в предположении nil }

ItemPointer:= FindItem; { вызвать функцию }

if ItemPointer <> nil then... { для надежности разымено-

вания ItemPointer }

end.

 

 

Потери динамически распределяемой памяти

─────────────────────────────────────────────────────────────────

 

При использовании динамически распределяемых переменных час-

то возникает общая проблема, называемая утечкой динамической па-

мяти. Утечка памяти - это ситуация, когда пространство выделяется

в динамически распределяемой памяти и затем теряется - по ка-

ким-то причинам ваш указатель не указывает больше на распределен-

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

 

Общей причиной утечек памяти является переприсваивание дина-

мических переменных без освобождения предыдущих. Простейшим слу-

чаем является следующий:


 

B.Pascal 7 & Objects /UG - 190 -

 

 

var IntPointer: ^Integer;

 

begin

New(IntPointer);

New(IntPointer);

end.

 

Пример 8.6 Простая утечка памяти.

 

При первом вызове New в динамически распределяемой памяти

выделяется 8 байт, и на них устанавливается указатель IntPointer.

Второй вызов New выделяет другие 8 байт, и IntPointer устанавли-

вается на них. Теперь у вас нет указателя, ссылающегося на первые

8 байт, поэтому вы не можете их освободить. В программе эти байты

будут потеряны.

 

Естественно, утечка памяти может быть не такой очевидной,

как в Примере 8.6. Выделение памяти почти никогда не происходит в

последовательных операторах, но может выполняться в отдельных

процедурах или далеко отстоящих друг от друга частях программы. В

любом случае лучший способ отслеживания динамических переменных -

это установка их в nil при освобождении. Тогда при попытке расп-

ределить их снова вы можете убедиться что они имеют значение nil:

 

var IntPointer: ^Integer;

 

begin

New(IntPointer);

.

.

.

Dispose(IntPointer);

IntPointer:= nil;

.

.

.

if IntPointer = nil then New(IntPointer);

end.

 

 

Управление связанным списком

─────────────────────────────────────────────────────────────────

 

Предположим, вы хотите написать программу для ведения своих

личных счетов. Вы можете хранить все данные о счетах в записях,

таких как запись типа TCheck, определенная в Примере 8.5. Но при

написании программы трудно предположить, с каким количеством сче-

тов вам придется иметь дело. Одно из решений здесь состоит в соз-

дании большого массива записей счетов, но это приведет к лишним

затратам памяти. Более элегантное и гибкое решение состоит в рас-

ширении определения записи и включении в нее указателя на следую-

щую запись списка, что приведет к образованию связанного списка,


 

B.Pascal 7 & Objects /UG - 191 -

 

показанного ниже:

 

type

PCheck = ^TCheck;

TCheck = record

Amount: Real;

Month: 1..12;

Day: 1..31;

Year: 1990..2000;

Payee: string[39];

Next: PCheck; { указывает на следующую запись }

end.

 

Пример 8.7 Записи в связанном списке.

 

Теперь вы можете считать каждую запись счета из файла и вы-

делить для нее память. Если запись находится в конце списка, поле

Next следует сделать равным nil. В вашей программе требуется отс-

леживать только два указателя: первый счет в списке и "текущий"

счет.

 

 


 

B.Pascal 7 & Objects /UG - 192 -

 

Построение списка

─────────────────────────────────────────────────────────────────

 

Ниже приведена процедура, которая строит связанный список

записей, считывая их из файла. Здесь подразумевается, что вы отк-

рыли файл записей TCheck и именем CheckFile, который содержит по

крайней мере одну запись.

 

var ListChecks, CurrentCheck: PCheck;

 

procedure ReadChecks;

begin

New(ListOfChecks); { выделить память для первой записи }

Read(CheckFile, ListOfChecks^); { считать первую запись }

CurrentCheck:= ListOfChecks; { сделать первую запись

текущей }

while not Eof(CheckFile do

begin

New(CurrentCheck^.Next); { выделить память для

следующей записи }

Read(CheckFile, CurrentCheck^.Next^); { считать

следующую запись }

CurrentCheck:= CurrentCheck^.Next; { сделать следующую

запись текущей }

end;

CurrentCheck^.Next:= nil; { после последней считанной

записи следующей нет }

end.

 

Пример 8.8 Построение связанного списка.

 

 

Перемещение по списку

─────────────────────────────────────────────────────────────────

 

Когда у вас есть список, вы можете легко выполнять поиск в

нем конкретной записи. В Примере 8.9 показана функция, которая

находит первый счет с конкретной суммой и возвращает указатель на

него.

 

function FindCheckByAmount(AnAmount: Real): PCheck;

var Check: PCheck;

begin

TempCheck:= ListOfChecks; { указывает на первую запись }

while (Check^.Amount <> AnAmount) and

(Check^.Next <> nil) do

Check:= Check^.Next;

if Check^.Amount = AnAmount then

FindCheckByAmount:= Check { возвращает указатель на

найденную запись }

else FindCheckByAmount:= nil; { или nil, если таких

записей нет }

end;


 

B.Pascal 7 & Objects /UG - 193 -

 

 

Рис. 8.9 Поиск в связанном списке.

 

 

Освобождение выделенной для списка памяти

─────────────────────────────────────────────────────────────────

 

Как показано в процедуре DisposeChecks в Примере 8.10, вы

можете перебрать список, дойдя до каждого элемента и освободив

его.

 

procedure DisposeChecks;

var Temp: PCheck;

begin

CurrentCheck:= ListOfChecks; { указывает на первую

запись }

while CurrentCheck <> nil do

begin

Temp:= CurrentCheck^.Next { сохранить указатель Next }

Dispose(CurrentCheck); { освобождение текущей записи }

CurrentCheck:= Temp; { сделать сохраненную запись

текущей }

end;

end;

 

Пример 8.10 Освобождение памяти для связанного списка.

 


 

B.Pascal 7 & Objects /UG - 194 -

 

────────────────────────────────────────────────────────────────────────────

Глава 9. Объектно-ориентированное программирование

─────────────────────────────────────────────────────────────────

 

Объектно-ориентированное программирование представляет собой

метод программирования, который весьма близко напоминает наше по-

ведение. Оно является естественной эволюцией более ранних новов-

ведений в разработке языков программирования. Объектно-ориентиро-

ванное программирование является более структурным, чем все пре-

дыдущие разработки, касающиеся структурного программирования. Оно

также является более модульным и более абстрактным, чем предыду-


Дата добавления: 2015-09-30; просмотров: 27 | Нарушение авторских прав







mybiblioteka.su - 2015-2024 год. (0.077 сек.)







<== предыдущая лекция | следующая лекция ==>