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

Структура модуля

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

unit <имя_модуля>;
 interface {секция внешних связей}
 implementation {секция реализаций}
 begin {секция инициализации}
end.

Разберем каждую из этих секций отдельно.

Название

В отличие от заголовка программы (program <имя_программы>;), который может и отсутствовать, заголовок модуля (unit <имя_модуля>;) обязан присутствовать всегда.

Кроме того, очень полезно давать модулям и содержащим их файлам одинаковые имена. Иначе говоря, модуль с именем modul_1 желательно разместить в файле с именем modul_1.pas, и т.п.

Секция внешних связей

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

Если для объявления какого-либо объекта нужны сведения об объекте, объявленном в другом модуле, то имя этого модуля необходимо указать в этой же секции:

interface
 [uses <список_вспомогательных_модулей>;]
 [const <список_внешних_констант>;]
 [type <список_внешних_типов_данных>;]
 [var <список_внешних_переменных>;]
 [procedure <объявление_внешней_процедуры>;]
 [function <объявление_внешней_функции>;]

Например, пусть у нас есть два модуля: mod_const, содержащий описания базовых констант и типов данных, и mod1, использующий эти описания (мы приводим только секции внешних связей):

unit mod_const;
 interface
 const sto = 100;
 type one_to_sto = 1..sto;
...
unit mod1;
 interface
 uses mod_const;
 const dvesti = 2*sto;
 type massiv = array[1..dvesti] of byte;
 var a: massiv;
 b: one_to_sto;
 function min(x,y:one_to_sto):one_to_sto;
...

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

Рис. 13.1.  Пример структуры модульной программы

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

uses mod1;

то внутри этого третьего модуля можно будет использовать (без дополнительных объявлений) тип massiv, переменные a и b, а также функцию min. Необходимо отметить, что использовать константу sto третий модуль не сможет, поскольку в нем не было указано

uses mod_const;

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

<имя_модуля>.<идентификатор>

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

Например, если модульная программа имеет структуру, изображенную на рис. 13.1, то таблица доступности переменных будет выглядеть так:

Связи Способ обращения к одноименным переменным
program prg; uses A,B,C; p a.p b.p c.p не видна не видна
unit A; uses C,D,F; не видна p не видна c.p d.p f.p
unit B; uses F; не видна не видна p не видна не видна f.p
unit C; - не видна не видна не видна p не видна не видна
unit D; - не видна не видна не видна не видна p не видна
unit F; - не видна не видна не видна не видна не видна p

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

unit mod_1;
 interface
 uses mod_2;
...

unit mod_2;
 interface
 uses mod_1;
...

Секция реализации

Этот раздел модуля содержит реализации всех подпрограмм, которые были объявлены в секции внешних связей. Как и в случае косвенной рекурсии (см. лекцию 9), здесь объявление подпрограммы оторвано от ее описания. Однако ключевое слово forward здесь является излишним.

Кроме того, в этой же секции объявляются и описываются внутренние (невидимые вне модуля) метки, константы, типы данных, переменные и подпрограммы.

implementation
 [uses <список_вспомогательных_модулей>;]
 [const <список_внутренних_констант>;]
 [type <список_внутренних_типов_данных>;]
 [var <список_внутренних_переменных>;]
 [procedure <описание_внешней_процедуры>;]
 [function <описание_внешней_функции>;]
 [procedure <объявление_и_описание_внутренней_процедуры>;]
 [function <объявление_и_описание_внутренней_функции>;]

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

unit mod_1;
 interface
 ...
 implementation
 uses mod_2;
...

unit mod_2;
 interface
 ...
 implementation
 uses mod_1;
...

Хороший пример реальной рекурсии при обращениях к модулям (позаимствованный, правда, из оригинальной документации) дается в книге М.В. Сергиевского и А.В. Шалашова "Турбо Паскаль 7.0. Язык. Среда программирования". Позволим себе привести (с небольшими изменениями) этот пример.

Смысл рекурсии здесь состоит в том, что вывод некоторого сообщения на заданную позицию экрана при помощи процедуры message, содержащейся в модуле mod_1, может сгенерировать ошибку, сообщение о которой (процедура _error из модуля mod_2) снова задействует процедуру message - но уже с другими параметрами.

unit mod_1;
 interface
 procedure message(x,y: byte; msg: string);
 implementation
 uses mod_2, crt;
 procedure message;
 begin if(x in [1..80-length(msg)]and(y in [1..25])
 then begin gotoxy(x,y); {позиционирование курсора}
 write(msg)
 end
 else _error('Сообщение не входит в экран')
 {вызов процедуры из модуля mod_2}
 end;
end.

unit mod_2;
 interface
 procedure _error(msg:string);

 implementation
 uses mod_1;
 procedure _error;
 begin message(1,25,msg); {вызов процедуры из модуля mod_1}
 end;
end.

Секция инициализации

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

begin
 <произвольные_операторы>
end.

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

  • Если модуль А подключает модуль В (не важно, в какой именно секции), то секция инициализации модуля В будет выполнена раньше, чем секция инициализации, содержащаяся в модуле А.
  • Если два модуля В и С подключаются на одном уровне (считаются равноправными), то их секции инициализации будут выполнены в том порядке, в каком имена этих модулей указаны в разделе uses.

Замечание: Последовательность подключения модулей соответствует обратному обходу орграфа связей (см. лекцию 12).

Если каждый модуль из тех, что составляют программу на рис. 13.1, имеет непустую секцию инициализации, то эти секции будут выполнены в следующей последовательности: C, D, F, A, B. Если же к головной программе модули будут подключены в другом порядке, например:

uses B,A,C; 

то секции инициализации будут выполняться в другой последовательности: F, B, C, D, A.

Замечание: Если секция инициализации в модуле отсутствует (а так чаще всего и бывает), то ключевое слово begin указывать не обязательно. Однако end. обязан закрывать текст модуля в любом случае.

Взаимодействие модулей

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

Использование модуля определений

Рис. 13.2.  Использование модуля определений

Компиляция модулей

Исходные тексты модулей хранятся в файлах с расширением .pas, а результаты их компиляции - в файлах с расширением .tpu.

Существует три варианта превращения модульной программы в исполняемый код.

  • Все подключаемые модули должны быть откомпилированы заранее; после этого можно компилировать головную программу, используя команду Compile | Compile главного меню или нажатие клавиш ALT+F9. Если вы случайно забыли откомпилировать какой-либо модуль (соответствующий файл с расширением .tpu отсутствует), то компилятор выдаст сообщение о недостающем файле. Если в текст какого-либо модуля были внесены изменения, но перекомпиляция не производилась (изменился файл .pas, но файл .tpu остался прежним), то в исполняемый код программы будет включена старая версия этого модуля.
  • Компилирование при помощи команды Compile | Make (ей эквивалентно нажатие клавиши F9) обновит все подключаемые к программе модули, которые либо еще не были откомпилированы, либо изменились с момента последней компиляции. Кроме того, перекомпилированы будут также модули, которые обращаются к тем модулям, чьи секции связи изменились (изменения в секциях реализации такого эффекта не вызовут).
  • Компиляция, активизированная при помощи команды Compile | Build, затронет все модули, вне зависимости от наличия в них изменений.