учебники, программирование, основы, введение в,

 

Модульная структура программы

Модульность программ
Модуль - это кусок программы, компилируемый отдельно от остальных ее частей. Именно возможность раздельной компиляции и является основным преимуществом модулей.
Простейшая модульность программы может достигаться за счет применения процедур и функций, однако этого не всегда достаточно. Если все подпрограммы содержатся в одном файле, то исправление единственной ошибки в какой-либо подпрограмме приведет к неизбежной перекомпиляции всего кода. А при современных размерах программ компиляция может длиться даже не минуты, а часы.
Кроме того, если коллектив программистов пишет одну большую программу (а именно в таких условиях работают сегодня все производители программного обеспечения), то каждому из них нужно обеспечить более или менее независимый "фронт работ". Даже два человека не могут одновременно исправлять один и тот же файл, иначе конфликт обновлений будет гарантирован. Что уж тут говорить о проектах, над которыми работают десятки и даже сотни человек! В такой ситуации модули, которые хранятся каждый в отдельном файле и могут быть отредактированы, откомпилированы и протестированы независимо от остальных частей программы, являются наилучшим решением этой проблемы.
Несколько модулей, являющихся составными частями одной программы, объединяются в библиотеку. Например, вместе с компилятором языка Pascal поставляются стандартные библиотеки, содержащие важнейшие подпрограммы обработки данных.
Стандартные модули языка Pascal
Перечислим самые распространенные модули, входящие в состав стандартных библиотек языка Pascal. Подробное описание этих библиотек можно найти в любом справочном издании.
System
Модуль System является основным: в нем содержатся все изученные нами стандартные процедуры и функции обработки арифметических выражений, множеств, строк и т.п. Специального подключения этот модуль не требует: его содержимым можно пользоваться по умолчанию.
Напомним, что этот модуль содержит следующие типы подпрограмм:

  1. подпрограммы для обработки величин порядковых типов данных (dec, inc, odd, pred, succ);
  2. арифметические функции;
  3. функции преобразования типов данных (chr, ord, round, trunc);
  4. процедуры управления процессом выполнения программы (break, continue, exit, halt);
  5. подпрограммы обработки строк (concat, copy, delete, insert, length, pos, str, val);
  6. подпрограммы файлового ввода и вывода;
  7. подпрограммы динамического распределения памяти (dispose, freemem, getmem, new);
  8. функции для работы с указателями и адресами (addr);
  9. а также некоторые другие подпрограммы (например, exclude, include, random, randomize, upcase).

Crt
Модуль Crt служит для организации "хорошего" вывода на экран. Подробнее о содержимом этого модуля мы расскажем в следующей лекции.
Wincrt
Модуль WinCrt предназначен для создания программ, поддерживающих простейший оконный интерфейс.
Printer
Модуль Printer позволяет производить вывод информации не на консоль, а на принтер (под операционной системой DOS).
Winprn
Модуль WinPrn является аналогом модуля Printer для операционной системы Windows.
Dos
Модуль Dos позволяет обмениваться информацией с операционной системой. Системное время, прерывания, состояния параметров окружения, процедуры обработки процессов, работа с дисковым пространством - всем этим занимается модуль Dos.
Windos
Модуль WinDos является аналогом модуля Dos для операционной системы Windows.
Strings
Модуль Strings позволяет перейти от стандартных строк языка Pascal к строкам, ограниченным нулем. В отличие от обычных строк, чья длина не может превышать 255 символов, эти строки могут состоять из 65 535 символов, причем конец каждой такой строки помечен символом #0.
Graph
Модуль Graph содержит разнообразнейшие подпрограммы, которые позволяют создавать на экране различные рисунки из многоцветных геометрических фигур. Модуль управляет также палитрами, фактурами фона и шрифтами.
Overlay
Модуль Overlay предоставляет возможность делать большие программы оверлейными (многократно использующими одну и ту же область памяти).
Winapi
Модуль WinApi отвечает за создание динамических библиотек. Этот модуль свойственен лишь поздним версиям языка Pascal (например, Turbo Pascal 7.0).

Подключение модулей
Для того чтобы подключить к программе какой-либо модуль, необходимо сразу после заголовка программы поместить следующую строку:
uses <имя_модуля>;
Если подключаемых модулей несколько, эта строка примет вид:
uses <имя_модуля_1>,...,<имя_модуля_N>;
Впрочем, совершенно не обязательно указывать имена всех модулей, так или иначе фигурирующих в программе. Достаточно указать имена лишь тех, к которым она будет обращаться непосредственно. А к каждому модулю, подключенному к головной программе, в случае необходимости можно подключить другие модули - и т. д.
Все константы, типы данных, переменные, процедуры и функции, описанные в каком-либо модуле, после его подключения к основной программе (или к другому модулю) становятся доступными этой программе (или модулю) без дополнительных объявлений.
Например, вы можете пользоваться функцией abs(), не объявляя и не описывая ее, поскольку эта функция включена в состав стандартного модуля System, автоматически подключаемого к любой программе на языке Pascal. Если же вы захотите очистить экран монитора перед выдачей результатов, вам придется подключить к вашей программе модуль crt и воспользоваться содержащейся в нем процедурой clrscr (см. лекцию 14).
Создание модульной программы
Перейдем теперь к детальному изучению структуры модулей и правил их создания и использования.
Структура модуля
В состав модуля входят четыре секции (любая из них может быть пустой, но ее заголовок все равно обязан присутствовать):
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;
...
Теперь, если в каком-либо третьем модуле встретится строка
uses mod1;
то внутри этого третьего модуля можно будет использовать (без дополнительных объявлений) тип massiv, переменные a и b, а также функцию min. Необходимо отметить, что использовать константу sto третий модуль не сможет, поскольку в нем не было указано
uses mod_const;
Если в секциях связей нескольких модулей были определены константы или переменные с одинаковыми именами, но с разными значениями, то путаницы позволит избежать уже знакомый нам прием: указание имени модуля перед идентификатором:
<имя_модуля>.<идентификатор>
Если имя модуля не указано, то идентификатор считается принадлежащим текущему модулю. И только если в текущем модуле этот идентификатор не был объявлен, то начинается поиск в подключенных модулях.
Например, если модульная программа имеет структуру, изображенную на, то таблица доступности переменных будет выглядеть так:

Связи

Способ обращения к одноименным переменным

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).
Если каждый модуль из тех, что составляют программу на, имеет непустую секцию инициализации, то эти секции будут выполнены в следующей последовательности: C, D, F, A, B. Если же к головной программе модули будут подключены в другом порядке, например:
uses B,A,D;
то секции инициализации будут выполняться в другой последовательности: F, B, C, D, A.
Замечание: Если секция инициализации в модуле отсутствует (а так чаще всего и бывает), то ключевое слово begin указывать не обязательно. Однако end. обязан закрывать текст модуля в любом случае.
Взаимодействие модулей
Если необходимо сделать так, чтобы несколько равноправных модулей имели доступ к одним и тем же переменным, константам и т.п., то наиболее практичным способом является введение дополнительного модуля, в котором будут объявлены все глобальные переменные, константы и типы данных. Секция реализации у этого нового модуля будет пустой.
Компиляция модулей
Исходные тексты модулей хранятся в файлах с расширением .pas, а результаты их компиляции - в файлах с расширением .tpu.
Существует три варианта превращения модульной программы в исполняемый код.

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

Пример модуля
В качестве примера мы приведем модуль, содержащий арифметические функции min и max для целых чисел, а также функцию возведения в степень. Все эти функции отсутствуют в стандартном модуле System.
unit my_arifm;
interface
function min(a,b: longint): longint;
function max(a,b: longint): longint;
function deg(a,b: double): double;

   implementation
function min;
begin if a>b then min:= b
else min:= a;
end;

      function max;
begin if a<b then max:= b
else max:= a;
end;

      function deg;
begin deg:= exp(b*ln(a))
end;
end.
Теперь, подключив этот модуль к любой своей программе, вы сможете пользоваться этими тремя функциями. Остальные необходимые в работе подпрограммы (например, тригонометрические функции tg, ctg, sec, arcsin, arсcos, arctg, arcctg, arcsec) желающие могут добавить в этот модуль самостоятельно.
Аналогичным образом, полезно единожды написать и отладить подпрограммы, обрабатывающие динамические структуры данных (списки, деревья, стеки и т.п.), сохранить их в специальном модуле, а затем пользоваться ранее проделанной работой вновь и вновь, не тратя времени на повторное программирование. Такой подход очень распространен в программировании вообще и носит несколько неуклюжее название "Повторное использование ранее созданных компонентов".
Передача аргументов из командной строки
В лекции 8 мы уже говорили о том, что любая процедура или функция, по сути, является полноценной программой. Но если подпрограммы способны получать аргументы извне, то почему этого не могут делать сами программы?
Однако в языке Pascal нет нарушений единообразия: любая программа тоже способна получать аргументы во время начала работы. Для этого нужно лишь вызывать ее из командной строки следующим образом:
<имя_программы> <список_аргументов>
Список аргументов - это последовательность передаваемых в программу значений, разделенных пробелами или символами табуляции.
В стандартном модуле System, автоматически подключаемом к любой программе на языке Pascal, имеются две функции, позволяющие программе принимать аргументы, переданные из командной строки.
Функция paramcount: word возвращает количество аргументов, переданных в программу из командной строки при вызове.
Функция paramstr(k: word): string возвращает k-й аргумент, переданный в программу из командной строки.
Если программа модульная, то обработку поступающих из командной строки аргументов удобнее всего занести в секцию инициализации одного из модулей. Впрочем, это не обязательно.
Например:
program arguments;
var i,k,n: word;
begin
n:= paramcount;
writeln('Из командной строки поступило ',n,'
аргументов:');
for i:= 1 to n do
writeln(i,'-й аргумент = ',paramstr(i));
end.
Если теперь вызвать эту программу из командной строки, скажем, с такими аргументами:
arguments abcd -36 +25.062 -t  /&amp;
то на экране получится следующий результат:
Из командной строки поступило 5 аргументов:
1-й аргумент = abcd
2-й аргумент = -36
3-й аргумент = +25.062
4-й аргумент = -t
5-й аргумент = /&amp;
Хорошим стилем считается включать в программу так называемый внешний help: если в нее можно передавать какие-либо аргументы из командной строки, необходимо сообщить потенциальному пользователю их возможные значения и смысл.
Чаще всего для отображения на экране внешней подсказки используются аргументы ? или /?.
Примером может служить краткая экранная подсказка хорошо известного архиватора arj.exe, которая появляется на экране в случае некорректного обращения к этой программе. Подсказка сообщает пользователю: а) имя программы; б) суть того, что именно появилось на экране; в) правила корректного вызова программы; г) примеры корректного вызова; д) краткий список возможных аргументов (команд и ключей).
ARJ 2.50a SHAREWARE Copyright (c) 1990-95 ARJ Software. Dec 12 1995
List of frequently used commands and switches. Type ARJ -? for more help.
Usage: ARJ <command> [-<sw> [-<sw>...]] <archive_name> [<file_names>...]
Examples: ARJ a -e archive, ARJ e archive, ARJ l archive *.doc

<Commands>
a: Add files to archive                       m: Move files to archive
d: Delete files from archive          t: Test integrity of archive
e: Extract files from archive         u: Update files to archive
f: Freshen files in archive                   v: Verbosely list contents of archive
l: List contents of archive           x: eXtract files with full pathname

<Switches>
c: skip time-stamp Check              r: Recurse subdirectories
e: Exclude paths from names    s: set archive time-Stamp to newest
f: Freshen existing files                     u: Update files (new and newer)
g: Garble with password                       v: enable multiple Volumes
i: with no progress Indicator         w: assign Work directory
m: with Method 0, 1, 2, 3, 4          x: eXclude selected files
n: only New files (not exist)         y: assume Yes on all queries
Более подробно о правилах организации хорошего интерфейса мы расскажем в следующей лекции.

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика