Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

[ Миронченко ] Императивное и объектно-ориентированное програмирование на Turbo Pascal и Delphi

.pdf
Скачиваний:
68
Добавлен:
25.04.2014
Размер:
3.16 Mб
Скачать

271

Остальные функции вы можете найти в справочной системе, написав фразу

file management routines TSearchRec определяется так:

type

TSearchRec = record Time: Integer;

Size: Integer; //размер файла в байтах

Attr: Integer; //атрибуты файла

Name: TFileName; //название файла с расширением, но без пути доступа

ExcludeAttr: Integer;

FindHandle: THandle;

FindData: TWin32FindData; // вспомогательная инф.: время создания, полное имя и т.д.

end;

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

сзаданным расширением в подкаталогах.

Пример 7: Поиск файлов в директории.

1 : program Kap15b04;

2 :

3 : {$APPTYPE CONSOLE}

4 :

5 : uses

6 : SysUtils,

7 : Dialogs;

8 :

9 : const

10:n=10000;

11:type

12:MeinTSearchRec = record

13:TSR:TSearchRec;

14:VollName:string; {имя файла вместе с полным путем к нему}

15:end;

16:DateienInfoMas = array [1..n] of MeinTSearchRec;

17:

18:{Заносит в массив M все файлы в директории Dir (включая все

19:поддиректории) с расширением Erweiterung. В TotQuant записывает

20:общее количество таких файлов.}

21:procedure AlleDateienZuMas(var M:DateienInfoMas;var TotQuant:integer;var Dir,Erweiterung:string);

22:var

23:sr:TSearchRec;

24:s:string;

25:begin

26:{$I-} //ошибки ввода-вывода надо проверять самому с помощью

IOResult

27:ChDir(Dir); //устанавливаем новый текущий каталог

28:

29: if IOResult<>0 then //Если каталог Dir не существует

272

30:begin

31:TotQuant:=0;

32:exit;

33:end;

34:// Ищем pas-файлы в текущей директории

35:if FindFirst('*.'+Erweiterung,faAnyFile,sr)=0 then

36:repeat

37:inc(TotQuant);

38:M[TotQuant].TSR:=sr;

39:M[TotQuant].VollName:=Dir+'\'+sr.Name;

40:until FindNext(sr)<>0;

41:

FindClose(sr);

//освобождаем память, выд. под FindFirst

42:

 

 

43://Ищем подкаталоги

44:if FindFirst('*.*',faDirectory,sr)=0 then

45:repeat

46:if (sr.Name<>'.') and (sr.Name<>'..') and (sr.Attr=faDirectory)then //Если подкаталог

47:begin

48: s:=Dir+'\'+sr.Name;//Записываем в s путь к подкаталогу

49: alleDateienZuMas(M,TotQuant,s,Erweiterung); //пробегаем файлы подкаталога

50: ChDir(Dir); // снова возвращаемся к исходному каталогу

51:end;

52:until FindNext(sr)<>0;

53:FindClose(sr); //освобождаем память, выд. под FindFirst

54:end;

55:

56:{Выдает общий объем всех файлов, содержащихся в M}

57:function NehmTotUmfang(var M:DateienInfoMas;TotQuant:integer):integer;

58:var

59:i,s:integer;

60:begin

61:s:=0;

62:for i:=1 to ToTQuant do

63:s:=s+M[i].TSR.Size;

64:result:=s;

65:end;

66:

67:{Печатаем все M[i] в поле для вывода}

68:procedure SchrDateien(var M:DateienInfoMas;TotQuant:integer);

69:var

70:i:integer;

71:begin

72:for i:=1 to TotQuant do

73:writeln(M[i].VollName);

74:writeln('Quantitat = ',TotQuant);

75:writeln('Umfang (in Bytes) =',NehmTotUmfang(M,TotQuant));

76:end;

77:

78: var

273

79:DatMas:dateienInfoMas;

80:TotQuant:integer;{Количество записей в массиве}

81:Weg,Erw:string;

82:

83:begin

84:Weg:=InputBox('Поиск файлов в поддиректории','Введите путь к файлам','');

85:Erw:=InputBox('','Введите расширение (например - pas)','');

86:AlleDateienZuMas(DatMas,TotQuant,Weg,Erw);

87:SchrDateien(DatMas,TotQuant);

88:readln;

89:end.

Разберем процедуру AlleDateienZuMas. В 26-й строке используется директива компилятора {$I-}. Она означает, что ошибки ввода-вывода, которые будут происходить при использовании файлов, придется обрабатывать самостоятельно, используя функцию IOResult. По умолчанию используется {$I+}, при которой генерируются при ошибках генерируются исключения, которые можно затем обрабатывать. Этот способ больше отвечает объектно-ориентированному подходу, но мы еще не знаем ни ООП, ни исключений, поэтому будем использовать IOResult.

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

15.7. Файлы, связанные с проектом

Когда мы работали с ТР, основная программа и программный код модулей были записані в файлах с расширением .pas, откомпилированные модули – в файлах с расширением tpu и еще, естественно, мы могли создавать .exe файл, который и был готовым приложением.

ВDelphi в состав приложения входит значительно большее количество файлов:

Основной файл проекта имеет расширение dpr (Delphi Project).

Программный код модулей находится в файлах, имеющих расширение pas. После компиляции этих файлов создаются файлы соответствующие файлы с машинным кодом, но расширение у них не tpu, как в ТР, а dcu (Delphi compiled unit).

Если модуль содержит описание формы, то помимо pas-файла с исходным кодом создается файл с расширением dfm, в котором находится описание формы (в текстовом или двоичном виде).

Кроме того, приложение содержит файлы ресурсов (напр. значок программы), которые присоединяются компилятором на этапе компоновки проекта к исполняемому файлу. Они имеют расширение res.

Файлы настройки содержат параметры компилятора (с расширением cfg), проекта (с расширением bdsproj или dof в Delphi 2005 или Delphi 6,7 соответственно).

После компиляции проекта создается exe-файл, содержащий код готового

приложения.

274

15.8. Динамически подключаемые библиотеки (DLL)

Модули, на которые разбивается программа, подключаются к основной программе статически – т.е. при компоновке программы. С помощью DLL можно подключать части программы во время ее работы. Кроме того, используя DLL, можно использовать подпрограммы, написанные на других языках программирования. Использование DLL поддерживается операционной системой Windows (для этого в Win32 API есть специальные функции). Сама Windows тоже состоит из ряда DLL.

Описание библиотек отличается от описания основной программы лишь тем, что начинается файл с исходным кодом библиотеки с ключевого слова Library, а также в библиотеке есть раздел exports, в котором перечисляются подпрограммы, которые экспортируются библиотекой (т.е. подпрограммы, которые могут использоваться в программах, которые подключают DLL).

Пример 8: Пишем простую библиотеку. library MeineDLL;

function Summe(var A:array of integer):integer; stdcall; var

i:integer; begin

result:=0;

for i:=0 to high(A) do result:=result+A[i];

end;

procedure ZufalligMas(var A:array of integer;k:integer); stdcall; var

i:integer; begin

for i:=0 to High(A) do A[i]:=random(k);

end;

procedure SchreibMas(var A:array of integer); stdcall; var

i:integer; begin

for i:=0 to High(A) do write(A[i],' ');

end;

Exports

Summe index 1 name 'Summe',

ZufalligMas index 2 name 'ZufalligMas', SchreibMas index 3 name 'SchreibMas';

begin end.

275

Называется библиотека MeineDLL, следовательно, после компиляции появится файл MeineDLL.dll, который и будет подключаться к программам.

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

В конце библиотеки находится раздел exports, в котором «на экспорт» идут все 3 подпрограммы, написанные в библиотеке. После названия процедуры следует индекс, который позволяет при подключении библиотеки быстро находить адрес подпрограммы. Name – это внешнее имя подпрограммы (оно может отличаться от внутреннего, т.е. имени, которое ей дано внутри библиотеки).

15.9. Подключение библиотеки

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

Пример 9: Статическое подключение библиотеки. program StatischeDLL;

{$APPTYPE CONSOLE} const

n=5; var

A:Array [1..n] of integer;

function Summe(var A:array of integer):integer; stdcall; External 'MeineDLL';

procedure ZufalligMas(var A:array of integer;k:integer); stdcall; external 'MeineDLL';

procedure SchreibMas(var A:array of integer); stdcall;external 'MeineDLL';

begin randomize;

ZufalligMas(A,45);

SchreibMas(A);

writeln;

writeln(Summe(A));

readln;

end.

Как видите, были подключены 3 подпрограммы из библиотеки и успешно использованы в программе.

Для того чтобы «динамически» подключать библиотеки, т.е. самим решать, когда подключать библиотеку, а когда выгружать ее из памяти, надо использовать API-

276

функции LoadLibrary, GetProcAddress, FreeLibrary, которые описаны в модуле Windows.

Мы разберемся в том, как они работают на примере.

Пример 10: Динамическое подключение библиотеки. program DynDLL;

{$APPTYPE CONSOLE} uses

Windows; const

n=5; type

Mas=Array [1..n] of integer;

SummFunk = function (var A:array of integer):integer; stdcall; FullProz = procedure (var A:array of integer;k:integer); stdcall; SchrProz = procedure (var A:array of integer); stdcall;

var A:Mas;

H:LongWord;

Summe:SummFunk;

ZufalligMas:FullProz;

SchreibMas:SchrProz;

begin H:=LoadLibrary('MeineDLL.dll'); if H=0 then

begin

writeln('Нет такой библиотеки'); readln;

exit;

end;

@Summe:=GetProcAddress(H,PChar(1));

@ZufalligMas:=GetProcAddress(H,'ZufalligMas');

@SchreibMas:=GetProcAddress(H,'SchreibMas');

randomize;

ZufalligMas(A,45);

SchreibMas(A);

writeln;

writeln('Summe ',Summe(A)); FreeLibrary(H);

readln;

end.

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

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

277

15.10.Использование модулей в DLL

ВDLL можно использовать модули, как и в обычной программе, но отличие в том, что внутри модуля как в разделе implementation, так и в разделе interface можно экспортировать подпрограммы (как и в самой библиотеке, с помощью exports). При создании библиотеки все модули и сама библиотека компилируются в один файл с расширением dll, и при подключении этой библиотеки из основной программы можно подключать все необходимые функции.

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

15.11.Что нам дает DLL?

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

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

3.Привлекательна возможность написания программы на разных языках.

4.Но: библиотека может экспортировать только подпрограммы. Ни типы, ни переменные она экспортировать не в состоянии. Следовательно, классы из DLL переносить нельзя.

5.При использовании DLL следует учитывать, что если ваша подпрограмма использует модули в разных подпрограммах, то компилятор подключает к программе 1 экземпляр модуля, а если вы 2 раза подключите одну DLL в разных местах программы, то к программе будет подсоединено 2 копии одной и той же библиотеки.

278

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

16.1.Модульный подход и ООП

Вмодульном программировании программа состояла в общем случае из нескольких модулей, в которых сосредоточены подпрограммы, типы данных, глобальные переменные, и т.д. Разбиение программы на модули, как вы помните, - это не просто разделение одного большого файла с кодом на несколько, - модули обладают определенной самостоятельностью, т.к. у них есть внутренние подпрограммы, константы, типы …, которые невидимы в программе, которая подключает данный модуль. Но модульность принципиально не изменила подход процедурного программирования, который можно кратко выразить так: «натравить процедуру на матрицу». Локальность подпрограмм в модулях во многом напоминает локальность одной подпрограммы относительно другой.

Процедурный подход часто не соответствует нашему пониманию мира, который мы моделируем в программе: мы говорим, что машина едет, а не то, что есть функция «ехать», которая применяется к машине. Скорее у машины есть способность перемещаться, которая является ее неотъемлемой частью, иначе она – просто металлолом, а не средство передвижения. Но в таком случае мы должны каким-то образом объединить данные – саму машину (ее детали) с функциями машины – перемещаться, расходовать бензин и т.д. Именно объединение данных и функций положено в основу объектно-ориентированного программирования (ООП), которое мы подробно рассмотрим в этой главе.

Введем основные определения:

Тип данных, в котором объединены данные и функции, называется классом.

Экземпляр класса называется объектом этого класса.

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

Данные, которые находятся в классе, называются полями.

Позже мы рассмотрим еще одни элементы класса – свойства.

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

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

16.2. Как объединить данные с подпрограммами средствами процедурного программирования

Вы знаете, что программа хранится в памяти во время выполнения, следовательно, и сами подпрограммы, которые являются частью программы, тоже обладают определенным адресом (адрес подпрограммы = адрес ее начала) в оперативной памяти ПК. Кроме того, вы умеете пользоваться процедурными и функциональными типами, которые, по сути, являются ссылками на начало подпрограммы. Значит, чтобы объединить подпрограммы с данными, можно создать запись, в которой вместо каждой из функций находилась бы ссылка на некоторую подпрограмму. Безусловно, сама запись стала бы очень громоздкой, однако цели мы бы добились. Однако, для создания переменной такой записи, надо самостоятельно настроить все ссылки на подпрограммы, т.е. в каждую из них записать адрес нужной

279

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

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

На рис. 16.1 вы видите 2 переменные типа Чудо-Запись. В каждой из них 4 поля, 2 из которых – ссылки на некоторые функции (заранее заданной сигнатуры).

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

(см. рис 16.1).

 

ПодПр1

ЧудоЗапись1

ЧудоЗапись2

14

ПодПр2

132

2.25

34.99

 

ПодПр3

Рис 16.1 Чудо-записи, версия 1

Теперь договоримся, что набор подпрограмм у переменных, типом которых является чудо-запись, не будет изменяться на протяжении существования этих переменных. В таком случае можно будет сделать чудо-запись гораздо более компактной: для каждого типа «чудо-запись» сформировать где-то в памяти массив, который состоял бы из указателей на подпрограммы, а в самой чудо-записи будет храниться лишь указатель на этот массив. Создавать массивы для каждого типа «чудозапись» надо в начале программы. Таким образом, чудо-записи будут отличаться по от обычных записей лишь на размер одного указателя. Заметим, что каждая подпрограмма из массива должна принимать ссылку на переменную типа «чудо-запись» (кроме тех подпрограмм, которые не используют поля чудо-записи14).

На рисунке 16.2. вы видите 3 переменные (ЧЗ_1, ЧЗ_2, ЧЗ_3), типом которых является некоторая чудо-запись. При этом все ссылки на подпрограммы находятся в массиве ЧЗ (все ссылки уже указывают на подпрограммы). А у элементов типа чудозаписи хранится в одном из полей указатель на этот массив.

Массив с общими для переменных типа чудо-запись подпрограммами мы назовем дескриптором чудо-записи.

Значит, в принципе реализовать принцип инкапсуляции мы можем без особых трудностей на основе двух слоев указателей (если учесть, что и подпрограммы должны принимать указатели на записи, то – трех слоев). Другие принципы ООП, которые нам еще предстоит изучить, реализовать несколько сложнее.

14 В будущем мы назовем такие функции «методами класса»

280

ЧЗ

ПодПр3

 

ПодПр1

ПодПр4

 

ПодПр2

 

 

ЧЗ_1

ЧЗ_2

ЧЗ_3

234

453

176

342.65

32.45

4432

Рис 16.2 Чудо-записи, версия 2

16.3. Объявление класса. Способы доступа к элементам класса

Просто объединить данные и методы – уже хорошо, т.к. это позволяет мыслить в терминах классов и объектов. Для того чтобы сделать класс чем-то более целостным, можно ограничивать доступ к элементам класса. Это помогает обеспечить, например, непротиворечивость данных: доступ к полям будет обеспечиваться специальными функциями, которые будут проверять правильность введенных значений.

В Delphi есть 3 основных режима доступа к элементам класса – public (открытые), private (закрытые), protected (защищенные).

private – элементы класса могут использоваться лишь внутри методов класса либо методов, которые расположены в том же модуле, где объявлено private-поле.

public – элементы могут использоваться где угодно (так же, как и поля записи). Режим доступа protected мы обсудим позже, когда изучим наследование.

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

Способ доступа контролируется компилятором, поэтому если вы попытаетесь присвоить значения закрытым полям какого-то объекта вне области видимости этого поля, то компилятор выдаст сообщение об ошибке.

Объявляется класс в Delphi следующим образом:

type

KlassesName = class private

{описания private-полей и private-методов} public

{описания public-полей и public-методов} end;

Вобъявлении:

KlassesName – название класса, который мы хотим создать;

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