программирование :: delphi

От Delphi 4 к Delphi 5 часть 28



От Delphi 4 к Delphi 5

Указатели.

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

Указатель является величиной, указывающей на некоторый адрес в памяти, где хранятся какие-то данные. Если вы имеете дело с массивом или другим структурным типом, то указатель содержит адрес первого элемента в структуре. Переменные, которые содержат адреса других переменных, принято называть указателями .

Указатели объявляются точно так же, как обычные переменные:

var

P: Pointer; { объявление переменной указателя }

K: Integer; { объявление переменной целого типа }

Переменная P занимает четыре байта и может содержать адрес любого участка памяти, указывая на байты памяти со значениями любых типов данных. Для инициализации переменной, присвоим ей адрес переменной K.

P := Addr(К); { инициализация переменной с помощью функции Addr}

Инициализацию можно провести вторым способом:

P^ :=@K; { инициализация с помощью оператора @}

Обычно используется более краткий и удобный второй способ. Таким образом, если некоторая переменная Р содержит адрес другой переменной К, то говорят, что Р указывает на К (рисунок 1) . Вы можете изменить значение переменной К, не прибегая к идентификатору K.

Можно изменить значение содержимого указателя, например, вместо предварительно определенного значения в Р адреса К, присвоим значение 10.

Если вы запишете:

P^ := 10;

то это будет ошибкой.

Из-за сильной типизации языка Object Pascal перед присваиванием, необходимо преобразовать выражение P^ к целому типу Integer. Объявим указатель P следующим образом:

var

P: ^Integer;

Такая запись говорит о том, что переменная P по-прежнему является указателем, но теперь ей можно присваивать адреса только целых переменных.

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

При использовании типизированных указателей лучше предварительно вводить соответствующий указательный тип, а переменные указатели просто объявлять с этим типом.

Модифицируем предыдущий пример (рисунок 2):

type

PInteger = ^Integer;

var

P: PInteger;

Значение PInteger - указательный тип данных. Чтобы отличить указательные типы от других, лучше назначать им идентификаторы, начинающиеся с буквы P.

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

Рассмотрим объявление типа для указателя на некоторую запись:

type

PScreen = ^TScreen;

PScreen = record;

Processor;string[20];

Memory: Integer;

end;

var

P: PScreen;

Переменная Р типа PScreen является указателем и может содержать адрес любой переменной типа TScreen.

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

После объявления в секции var указатель содержит неопределенное значение. Переменные-указатели, как и обычные переменные, перед использованием нужно инициализировать. Использование неинициализированных переменных приводит к неправильным результатам, а использование неинициализированных указателей приводит к ошибке Acces violation (доступ к неверному адресу памяти) и принудительному завершению приложения.

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

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

Размещение динамических переменных производится в специальной области памяти, которая называется Heap (куча). Размер кучи равен размеру свободной памяти компьютера (рис 2.)

Для размещения динамической переменной (рисунок 3) вызывается стандартная процедура:

New(var P: Pointer);

Эта процедура выделяет требуемый по размеру участок памяти и заносит его адрес в переменную-указатель Р.

Рассмотрим пример:

type



PListEntry = ^TListEntry;

TListEntry = record

Next: PListEntry;

Text: string;

Count: Integer;

end;

var

List, P: PListEntry;

begin

...

New(P);

P^.Next := List;

P^.Text := 'Hello world';

P^.Count := 1;

List := P;

...

end;

Новая процедура создает новую динамическую переменную и устанавливает переменную указателя, чтобы указать на это. P является переменной любого типа указателя. Размер размещенного блока памяти соответствует размеру типа, на который P указывает. Вновь созданная переменная может ссылаться на P^. Если там недостаточно памяти пригодной для размещения динамической переменной, то генерируется исключение EOutOfMemory .

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

type



Str18 = string[18];

var

P: ^Str18;

begin

New(P);

P^ := 'Мой любимый язык Delphi...';

Dispose(P); { Освобождение памяти... }

end;

После выполнения процедуры Dispose указатель P перестает быть связан с конкретным адресом памяти. В нем будет случайное значение, как и до обращения к процедуре New. Не стоит делать попытки присвоить значение переменной Р, в противном случае это может привести к нарушению нормальной работы программы. Поэтому необходимо четко придерживаться последовательности работы с динамическими переменными:

создание динамической переменной;

выполнение с переменной необходимых действий;

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

Рассмотрим пример, показывающий работу указателей .

1 var

2 X, Y: Integer; // X и Y переменные целого типа

3 P: ^Integer; // P указатель на данные целого типа

4 begin

5 X := 17; // присвоение значения 17 для X

6 P := @X; // определение адреса X в P

7 Y := P^; /разыменование Р; помещение результата в Y

8 end;

Строка 2 объявляет X и Y как переменные целого типа. Строка 3 объявляет P как указатель данных целого типа; это означает, что P может указать на позицию X или Y. Строка 5 определяет величину X, и строка 6 назначает адрес X (обозначается @X) в P. Наконец, строка 7 извлекает величину в позиции, указанной на P (обозначается ^P), и присваивает это значение Y. После того, как выполнится программный код, X и Y будут иметь одну и ту же величину 17.

Если вы добавите на форму два компонента Edit1, Edit2 и напишете следующий код

Edit1.Text:=IntToStr(X);

Edit2.Text:=IntToStr(Y);

то вы можете убедиться, что значения X и Y одинаковы и равны 17.

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

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

Наш пример может показаться подобным тому, что мы окольным путем копировали целую величину одной переменной, присвоили это значение другой величине, в то время когда это можно произвести обычным оператором присваивания. Но указатели полезны по различным причинам. Вначале, поняв назначение указателя, вы можете лучше понимать язык программирования Object Pascal . Любой тип данных, который требует большие объемы, динамически распределяемой памяти, использует указатели. Кроме того, некоторые программы требуют обязательного использования указателей.

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

type

PInteger = ^Integer;

var

R: Single;

I: Integer;

P: Pointer;

PI: PInteger;

begin

P := @R;

PI := PInteger(P);

I := PI^;

end.

Конечно, реальные и целые загружаются в другие форматы. Этот пример просто копирует сырые двоичные данные из R на I, не преобразовывая их.

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

В Object Pascal имеется ряд предопределенных типов указателей. Это, прежде всего, типы указателей на строки: PAnsiChar и PWideChar , представляющие собой соответственно указатели на значения AnsiChar и WideCha r. Имеется также родовой тип PChar , определяющий указатель на Char (т.е. пока указывающий на AnsiChar ). Эти указатели используются при работе со строками с нулевым символом в конце.

PAnsiString, Pstring — типы переменной, на которую указывает указатель AnsiString.

PByteArray тип переменной, на которую указывает указатель ByteArray (объявлен в модуле SysUtils ). Используется для доступа к динамически размещаемым массивам.

PCurrency тип переменной, на которую указывает указатель Currency.

PExtended тип переменной, на которую указывает указатель Extended.

POleVariant тип переменной, на которую указывает указатель OleVariant.

PShortString тип переменной, на которую указывает указатель ShortString.

PTextBuf тип переменной, на которую указывает указатель TextBuf (объявлен в модуле SysUtils ). Внутренний тип буфера в записи файлов TTextRec .

PVarRec тип переменной, на которую указывает указатель TVarRec (объявлен в модуле System).

PVariant тип переменной, на которую указывает указатель Variant.

PWideString тип переменной, на которую указывает указатель WideString.

PWordArray т ип переменной, на которую указывает указатель TWordArray (объявлен в модуле SysUtils ). Используется для доступа к динамически размещаемым массивам 2-байтных величин.

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

type <имя типа указателя> = <тип данных>;

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

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

Операция разыменования не применима к указателям типа pointer. Чтобы провести разыменование указателя pointer, надо сначала привести его к другому типу указателя.

Указатели PChar и PWideChar.

При работе со строками с нулевым символом в конце часто используют специальные типы указателей: PChar и PWideChar, которые являются указателями соответственно на массивы с элементами типов Char и WideChar с нулевым символом в конце.

Переменные этих типов часто требуется передавать в различные функции и процедуры в качестве параметров. К тому же, типы Char и WideChar существенно упрощают коды. Дело в том, что эти типы совместимы со строковыми константами. Например, объявление и использование переменной типа PChar может иметь вид

var P: Pchar;



P := 'Привет!';

Это эквивалентно операторам

const SHello: array[0…7] of Char = 'Привет!'#0;

var P: Char;



P := @SHello;

согласитесь, первая запись гораздо короче.

Совместимость указателей типов PChar и PWideChar со строковыми константами позволяет в вызовах функций и процедур с параметрами соответствующих типов использовать просто строки символов.

Например, если некоторая процедура F требует параметр типа PChar, то обратиться к ней можно так:

var P: PChar;



P := 'Привет!';

F(P);

или

F('Привет');

Указатели типов PChar и PWideChar могут индексироваться точно так же, как строки и массивы символов. Например, P[0] - это первый символ строки, на которую указывает P.

В выражениях, операторах присваивания и при передаче параметров в функции и процедуры можно смешивать длинные строки (AnsiString) и строки с нулевым символом в конце типа PChar. Но иногда требуется явное приведение типа PChar к типу длинной строки. Например, операция склеивания (конкатенации) строк требует, чтобы хотя бы один ее операнд был типа строки.

Если же требуется склеить два объекта типа PChar, то это надо сделать с помощью приведения типа:

S := string(P1) + string(P2);

Константы указатели.

Когда вы объявляете константу указатель, вы должны проинициализировать эту величину. Есть три пути, чтобы сделать это: с @ оператором, с нулем и (если константа будет типом PChar) с литералом строки. Например, имеется глобальная переменная целого типа, вы можете объявить константу следующим образом:

const PI: ^Integer = @I;

Компилятор может решить эту проблему, поскольку глобальные переменные являются частью кодового сегмента. Так, функции и глобальные константы объявляются следующим образом:

const PF: Pointer = @MyFunction;

Поскольку литералы строки определены как глобальные константы, вы можете проинициализировать PChar константу с литералом строки:

const WarningStr: PChar = 'Warning!';

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

GetMem(var P: Pointer [;Size: Integer]) - создает в динамической памяти новую динамическую переменную с заданным размером Size и присваивает ее адрес указателю P.

Переменная указателя P может указывать на данные любого типа.

FreeMem(var P: Pointer [; Size: Integer]) - освобождает динамическую переменную.

Если в программе используется этот способ распределения памяти, то вызовы GetMem и FreeMem должны соответствовать друг другу.

GetMem(P4, SizeOf(ShortString)); { выделить блок памяти для P4}



FreeMem(P4); { освободить блок памяти }

С помощью процедуры GetMem одной переменной-указателю можно выделить разное количество памяти в зависимости от потребностей, в этом состоит ее основное отличие от процедуры New.

Рассмотрим пример:

GetMem(P4,20); {выделить блок в 20 байт для указателя P4}



FreeMem(P4); {освободить блок памяти}

В данном случае для указателя P4 выделяется меньше памяти, чем может уместиться в переменной типа ShortString, и программист сам должен предотвратить выход строки за пределы выделенного участка.

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

Для этого предназначена следующая процедура:

ReallocMem(var P: Pointer; Size: Integer) - освобождает блок памяти по значению указателя Р и выделяет для указателя новый блок памяти заданного размера Size.

Указатель P может иметь значение nil, а параметр Size - значение 0, что влияет на работу процедуры:

если P = nil и Size = 0, процедура ничего не делает;

если P = nil и Size <> 0, процедура выделяет новый блок памяти заданного размера, что соответствует вызову процедуры GetMem;

если P <> nil и Size = 0, процедура освобождает блок памяти, адресуемый указателем P, и устанавливает указатель в значение nil; это соответствует вызову процедуры FreeMem, с той лишь разницей, что FreeMem не очищает указатель;

если P <> nil и Size <> 0, процедура переопределяет память для указателя P; размер нового блока определяется значением Size. Данные из прежнего блока копируются в новый блок; если новый блок больше прежнего, то приращенный участок остается неинициализированным и содержит случайные данные.

Компонент Image.

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

    1. Запустите Delphi.
    2. Сохраните файл модуля под именем ImageView_pas, а файл проекта под именем ImageView. dpr.
    3. Поместите на форму компонент Image со страницы Additional палитры компонентов.
    4. Поместите на форму компонент OpenDialog со страницы Dialogs палитры компонентов.
    5. Поместите на форму компонент BitButton со страницы Additional палитры компонентов. Вы уже знаете, как придать данной кнопке более красивый вид, можете проделать это. Для обработчика события щелчка по кнопке OnClick напишите следующий программный код:
    6. procedure TForm1.BitBtn1Click(Sender: TObject);

      begin

      if OpenDialog1.Execute then

      begin

      Image1.Picture.LoadFromFile(OpenDialog1.FileName);

      Form1.ClientHeight:= Image1.Height+10;

      Image1.Top:= Form1.ClientRect.Top + (Form1.ClientHeight - Image1.Height)

      div 2;

      Form1.ClientWidth:= Image1.Width+10;

      Image1.Left:= Form1.ClientRect.Left + (Form1.ClientWidth - Image1.Width)

      div 2;



      end;

      end;

      end.

    7. Для того чтобы изображения разных размеров были в окне полностью видимы, необходимо установить для компонента Image1 свойство AutoSize в True. А приведенные выше операторы позволят размещать изображение по центру формы. В этом программном коде размеры клиентской области устанавливаются несколько больше размеров компонента Image1, которые в свою очередь адаптируются к размеру изображения благодаря свойству AutoSize.
    8. Для того чтобы просматривать только необходимые вам изображения, используя свойство Filter компонента OpenDialog, определите следующие значения. Например, запишите в разделе Filter Mame *.bmp;*.ico;*.wmf, в разделе Filter запишите *.bmp;*.ico;*.wmf (рисунок 4). На рисунке 5 показана процедура поиска необходимого изображения. На рисунке 6 показан результат работы программы ImageView.

Если у вас установлена версия Delphi 5, то на странице палитры компонентов Dialogs имеется компонент OpenPictureDialog, который вызывает окно открытия и предварительного просмотра изображений. Если вы посмотрите свойство Filter этого компонента (рисунок 7), то вы увидите, что за вас поработали и определили, какие файлы вы можете просматривать. Добавьте на форму компонент Image и Button и напишите следующий программный код:

procedure TForm1.Button1Click(Sender: TObject);

begin

if OpenPictureDialog1.Execute then

begin

Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName);

end;

end;

end.

Осуществляя поиск необходимого изображения, в Delphi 5 диалоговое окно открытия файлов содержит окно предварительного просмотра изображений.

Для компонента Image определено событие OnProgress, с помощью данного обработчика можно получить дополнительную информацию во время выполнения длительной по времени операции загрузки большого по объему изображения.

procedure TForm1.Image1Progress(Sender: TObject; Stage: TProgressStage;

PercentDone: Byte; RedrawNow: Boolean; const R: TRect;

const Msg: String);

Параметр Stage содержит состояние процесса загрузки (psStarting - начало, psRunning - идет загрузка, psEnding - процесс завершен ). Параметр PercentDone приблизительно указывает процент выполненной работы.

С помощью параметра RedrawNow Windows сообщает, нужно ли сейчас выполнить прорисовку части изображения. Этот параметр имеет смысл, только если свойство IncrementalDisplay компонента содержит True. Параметр R – прямоугольник, нуждающийся в прорисовке. Параметр Msg содержит одно или более слов, уточняющих состояние процесса.

Обычно в обработчике события по сигналу psStarting создается индикатор процесса типа TProgressBar, по сигналам psRunning изменяется позиция индикатора, а в момент psEnding индикатор уничтожается.

Событие OnProgress создается только при загрузке некоторых типов изображений, например, подготовленных в формате JPEG (Joint Photographic Except Group - объединенная группа фотографических экспертов). Пример использования события OnProgress вы можете найти в папке Borland \ Delphi \ Help \ Examples \ JEPG. На рисунке 8 представлен результат демонстрационной работы программы Ipegproi.

Литература:

    1. Марко Канту. Delphi 2 для Windows 95/NT. Москва. ООО "Малип". 1997г.
    2. Джон Матчо. Дэвид Р. Фолкнер. Delphi. Москва. БИНОМ. 1995г.
    3. Эндрю Возневич. Delphi. Освой самостоятельно. Москва. Восточная книжная компания. 1996г.
    4. К. Сурков, Д. Сурков, А. Вальвачев. Программирование в среде Delphi 2.0.
    5. ООО "Попурри" 1997 г.

    6. В.В.Фаронов. Delphi 5. Учебный курс. Москва. Издательство Нолидж. 2000г.
    7. А. Я. Архангельский. Программирование в Delphi 5. Москва. ЗАО "Издательство Бином". 2000г.

Владимир Скуратов

(c) компьютерная газета





© компьютерная газета