Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
15
file:///home/disk1/12balov/files/36/document.doc
Понятие модуля или, в более общем случае, модульного программирования, возникло на определенном этапе развития вычислительного дела и было обусловлено, в первую очередь, возрастающими объемами программ, их увеличивающейся внутренней сложностью и коллективным характером разработок. К настоящему времени понятие модуля проделало значительную эволюцию от примитивного "разрубания" текста программы на произвольные части или создания библиотек включаемых фрагментов до независимо хранимых и разрабатываемых, независимо компилируемых и тестируемых программных единиц со строго определенными интерфейсами, которые могут объединяться в различных сочетаниях, что характерно, например, для языка Ada.
При всех несомненных достоинствах алголоподобных языков, большинство из них имеет существенный недостаток - отсутствие модульности. Представление программной системы как единой языковой конструкции является препятствием для эффективной организации коллективных разработок сложных систем, затрудняет понимание и модификацию программ. Понятие подпрограмм лишь частично решает проблему ввиду неявных и слабо контролируемых информационных зависимостей подпрограмм и их окружения.
С этой точки зрения, введение понятия модуля в Turbo Pascal, которое было проведено, начиная с 4-ой версии системы, явилось решающим шагом на пути его превращения в язык, пригодный для крупных разработок производственного и коммерческого назначения на современном уровне технологии программирования. Стал возможным современный стиль реализации программных пакетов различного назначения и ориентации, легко подключаемых к любой программе. Кроме того, за счет введения модулей удалось ослабить ограничения на суммарный объем готовых программ.
Модульные средства в Turbo Pascal'e заметно слабее аналогичных возможностей тех языков, для которых модульный принцип был положен в основу их проектирования. Однако следует признать, что разработчики языка Turbo Pascal нашли удачный компромисс, достаточно органично встроив принципиально новое понятие в считающийся уже классическим язык Pascal, не нарушив при этом его целостности, элегантности и простоты и одновременно значительно расширив его возможности.
В языке Turbo Pascal модуль (unit) по определению считается отдельной программой. Если подпрограмма является структурным элементом Pascal-программы и не может существовать вне ее, то модуль представляет собой отдельно хранимую и независимо компилируемую единицу. С учетом этого факта можно дополнить рассмотрение общей структуры Pascal-программы следующей синтаксической диаграммой для случая языка Turbo Pascal:
Программа на языке Turbo Pascal
В самом общем виде модуль представляет собой совокупность (коллекцию) программных ресурсов, предназначенных для использования другими модулями и программами. Под ресурсами в данном случае понимаются любые программные объекты языка Turbo Pascal - константы, типы, переменные, подпрограммы. Важно понимать, что модуль сам по себе не является выполняемой программой - его объекты ИСПОЛЬЗУЮТСЯ другими программными единицами.
Все программные ресурсы модуля можно разбить на две части: объекты, прямо предназначенные для использования другими программами или модулями, и объекты рабочего характера. Например, если модуль содержит некоторую подпрограмму универсального назначения, пригодную для использования другими программами, то, скажем, вызываемые этой подпрограммой процедуры и функции, содержащиеся в модуле, и используемые ею переменные имеют сугубо внутренний характер. В соответствии с этим модуль, кроме заголовка, имеет две основные части, называемые интерфейсом и реализацией.
В интерфейсной части модуля сосредоточены описания объектов, доступных из других программ; такие объекты называют видимыми вне модуля. В части реализации помещаются рабочие объекты, называемые также невидимыми или скрытыми.
Заголовок модуля составляется из служебного слова unit и следующего за ним идентификатора, являющегося именем модуля. Заголовок завершается символом ';' (точка с запятой). Интерфейсная часть начинается со служебного слова interface, за которым следует совокупность обычных описаний. Часть реализации начинается служебным словом implementation, за которым идут описания скрытых объектов. Завершает модуль, как и программу, служебное слово end и символ '.' (точка).
Кроме перечисленных частей, модуль может содержать так называемый раздел инициализации, предназначенный для установки начальных значений переменных модуля перед его использованием. Этот раздел следует после раздела реализации, начинается со служебного слова begin и содержит последовательность операторов.
Таким образом, общая структура модуля может быть представлена следующей схемой:
Приведем простейший пример модуля. Для того, чтобы избежать многократного описания в различных программах некоторых общеупотребительных типов данных, молено сосредоточить их в одном модуле. Данный пример касается описаний, связанных с датами, месяцами, днями и т.д.
unit Calendar;
interface
type
Days = (Mon,Tue,Wed,Thu,Fri,Sat,Sun);
WorkingDays = Mon..Fri;
Months = (Jan,Feb,Mar,Apr,May,June,
July,Aug,Sept,Oct,Nov,Decem);
Summer = June..Aug;
Autumn = Sep..Nov;
Spring = Mar..May;
DayNo = 1..31;
YearNo = 1900..2000;
Date = record
Day : DayNo;
Month : Months;
Year : YearNo
end;
implementation
end.
Данный модуль, ввиду своей простоты, не содержит разделов реализации и инициализации. Механизм использования модулей в других программах будет описан далее.
14.3 Подпрограммы в модулях
Процедуры и функции могут использоваться в модулях наравне с другими Pascal-объектами. Однако для них имеются особенности, обусловленные их структурой. Как уже отмечалось, заголовок подпрограммы содержит всю информацию, необходимую для ее вызова: ее имя, количество и типы параметров и (для функций) тип результата. С другой стороны, тело подпрограммы содержит блок, раскрывающий ее алгоритм. Можно считать, что заголовок подпрограммы является ее интерфейсом, а тело - реализацией. В соответствии с этой точкой зрения в интерфейсной части модуля должны быть представлены только ЗАГОЛОВКИ процедур и функций, видимые (доступные) для других программ (аналогично предварительным описаниям, но без служебного слова forward), а их полные описания будут содержаться в разделе реализации. При этом полное описание подпрограммы может иметь СОКРАЩЕННЫЙ заголовок, состоящий только из служебного слова procedure или function, имени подпрограммы и символа ';'. (Разумеется, можно повторить полный заголовок подпрограммы, но тогда он должен быть точно таким же, как и заголовок в интерфейсной части).
В качестве примера можно привести модуль, содержащий средства работы с комплексными числами
unit CmplVals;
interface
type
Complex = record { способ представления
комплексных чисел }
Re, Im : real
end;
{ Заголовки процедур, реализующих
операции над комплексными числами }
procedure InitC (R,I:real; Var C:Complex);
procedure AddC (Cl,C2:Complex;var R:Complex);
procedure MultC (Cl,C2:Complex;var R:Complex);
procedure DivC (Cl,C2:Complex;var R:Complex);
procedure WriteC(C:Complex);
implementation
{ Полные описания процедур
(с сокращенными заголовками) }
procedure InitC;
begin
with C do
begin
Re:=R; Im:=I
end
end;
procedure AddC;
begin
with R do
begin
Re := Cl.Re + C2.Re;
Im := Cl.Im + C2.Im
end
end;
procedure MultC;
begin
with R do
begin
Re := Cl.Re*C2.Re+Cl.Im+C2.Im;
Im := Cl.Im*C2.Re+Cl.Re*C2.Im
end
end;
procedure DivC;
var
Tmp : real;
begin
with C2 do Tmp:=Re*Re+Im*Im;
with R do
begin
Re := (Cl.Re*C2.Re+Cl.Im*C2.Im)/Tmp;
Im := (C2.Re*Cl.Im+Cl.Re*C2.Im)/Tmp
end
end;
procedure WriteC;
begin
with С do
begin
Write(Re);
if Im=0 then Exit;
if Im>0 then Write(+);
Write(Im);
Write('i')
end
end;
end.
Таким образом, механизм модулей позволяет скрыть детали реализации тех или иных программных подсистем,
предоставив в распоряжение использующих программ строго определенную совокупность интерфейсных объектов. Если необходимо, например, расширить модуль CmplVals введением новых процедур или изменить реализацию какой-либо процедуры, то если интерфейс модуля при этом останется неизменным, такая модификация НИКАК НЕ ОТРАЗИТСЯ на использующих программах.
Модуль компилируется точно таким же образом, как и обычные подпрограммы; возможна компиляция из интегрированной среды или с помощью компилятора командной строки. Но так как модуль не является непосредственно выполняемой единицей, то в результате его компиляции образуется дисковый файл с расширением .TPU (Turbo Pascal Unit), при этом имя файла берется из имени файла с исходным текстом модуля.
Для того, чтобы получить доступ к интерфейсным объектам модуля, необходимо указать в программе имя нужного ТРU-файла. Соответствующая конструкция называется спецификацией используемых модулей и имеет следующий общий вид:
uses U1, U2, U3;
где uses - служебное слово, U1, U2, UЗ - идентификаторы используемых модулей. Эта спецификация должна идти непосредственно после заголовка программы; если некоторый модуль использует объекты другого модуля, то такая спецификация должна следовать сразу после служебного слова interface.
При наличии спецификации использования в данной программе считаются известными все описания из интерфейсной части подключенного модуля. К интерфейсным объектам модуля можно обращаться в программе точно так же, как если бы они были описаны в самой этой программе.
Следующий пример иллюстрирует использование модуля CmplVals:
program DaingComplex;
uses
CmplVals;
var
C1, C2, C3 : Complex;
begin
InitC(1,2,C1); InitC(3,4,C2);
MultC(C1,C2,C3); WriteC(C3);
DivC(C1,C2,C3); WriteC(C3)
end.
Необходимо особо отметить следующие важные моменты, связанные с использованием модулей:
1. Может случиться так, что идентификаторы интерфейсной части используемого модуля частично пересекаются с идентификаторами использующей программы. В этом случае действует следующее правило видимости имен: интерфейсные идентификаторы модуля, указанного первым в uses-списке, образуют самый внешний блок программы; интерфейсные идентификаторы второго модуля образуют блок, вложенный в первый блок, и т.д. Если, например, в программе имеется спецификация вида uses А, В; то вложенность блоков выглядит так:
Таким образом, идентификаторы внешнего блока программы будут "экранировать" одноименные идентификаторы модулей А и В; аналогично, идентификаторы модуля А будут перекрыты одноименными идентификаторами модуля В.
Однако, существует возможность доступа к интерфейсу используемого модуля несмотря на наличие в программе одноименных идентификаторов. Пусть имеется следующий модуль:
unit А;
interface
var
X:real;
implementation
...
end.
Далее, пусть программа, использующая этот модуль, также содержит переменную X:
program Р;
uses А;
var X : integer;
begin
...
end.
Для того чтобы в программе Р иметь доступ к интерфейсной переменной X из модуля А, необходимо задать составное имя, структура которого похожа на селектор поля записи:
А.Х
Здесь А - имя модуля, X - идентификатор его интерфейсной переменной. В этом случае конфликт имен снимается, так как простое указание имени X будет означать обращение к соответствующей переменной из программы Р, например: А.Х := Round(X);
2. Возможны случаи косвенных использований. Например, пусть имеются два модуля:
unit A; interface . . . end. |
unit B; interface uses A; . . . end. |
Если некоторая программа использует модуль B, то в соответствующей спецификации использования необходимо указать только модули, НЕПОСРЕДСТВЕННО используемые в программе. В данном примере достаточной является следующая спецификация:
program P;
uses В;
. . .
end.
(Необходимо отметить, что в фирменной документации по системе Turbo Pascal можно встретить и противоположное требование, согласно которому в спецификации использования должны быть указаны ВСЕ модули, прямо или косвенно используемые программой. Однако из практики видно, что компилятор правильно обрабатывает программы, в спецификации использования которых указаны только модули, непосредственно используемые ею).
3. Схема использования модулей может образовывать древовидную структуру любой сложности, но при этом недопустимо явное или косвенное обращение модуля к самому себе. Так например, следующие отношения являются ошибочными:
unit A; interface uses B; . . . end. |
unit B; interface uses A; . . . end. |
Однако допускается взаимное использования модулей, позволяющее ослабить указанное ограничение. В этом случае спецификация использования может указываться в разделе реализации. Такая возможность используется сравнительно редко и в настоящем описании не приводится.
4. Если в модуле имеется раздел инициализации, то операторы из этого раздела будут выполнены ПЕРЕД началом выполнения программы, в которой используется данный модуль. Если программа использует несколько модулей, то их разделы инициализации будут выполнены в том же порядке, в котором эти модули перечислены в спецификации использования.
Особенности модулей требуют несколько более подробного рассмотрения вопросов, связанных с их компиляцией и использованием. Для случая компиляции программы никак не оговаривается связь имени в заголовке программы и имени дискового файла, содержащего эту программу; так как сам заголовок является необязательным, такая связь не имеет смысла.
В отличие от программ, заголовок модуля несет семантическую нагрузку, так как программа или другой модуль, использующие данный, могут ссылаться на него в uses-спецификации. С другой стороны, при трансляции программы, использующей модули, компилятор должен каким-то образом отыскать коды этих модулей, чтобы подключить их к компилируемой программе. Поэтому принято за правило, что имя файла, содержащего исходный текст модуля, должно совпадать с именем этого модуля (расширение имени файла при этом несущественно, но по умолчанию предполагается .PAS). Компилятор помещает код модуля, полученный в результате трансляции, в файл с таким же именем и расширением .TPU. При трансляции же программы, использующей этот модуль, компилятор ищет ТРU-файл с именем, заданным в uses-спецификации, и связывает его с кодом программы. Таким образом, в спецификации использования фактически задаются не имена модулей, а имена файлов, их содержащих.
Например, для спецификации
uses MyUnit;
помещенной в некоторой программе, компилятор перед трансляцией самой программы должен найти дисковый файл с именем MYUNIT.TPU; в этом файле должен находиться код модуля с заголовком вида
unit MyUnit;
Если все-таки необходимо хранить код модуля в файле с другим именем, то можно использовать директиву $U для переопределения имени файла. Эта директива имеет параметр, который трактуется как "настоящее" имя файла с данным модулем. Она должна находиться непосредственно перед именем модуля в спецификации использования. Например, конструкция
uses {$U MY) MyUnit;
приведет к тому, что компилятор будет искать код модуля MyUnit в дисковом файле MY. TPU.
При трансляции программы или модуля, использующего другие модули, компилятор последовательно отыскивает файлы, содержащие коды используемых модулей, с тем чтобы подключить их к компилируемой программе. При этом компилятор работает по следующей схеме:
1) Компилятор просматривает содержимое системного библиотечного файла модулей TURBO.TPL (Turbo Pascal Library). Этот файл будет кратко описан далее в этом разделе.
2) Если искомый модуль не найден в файле TURBO. TPL, то компилятор осуществляет поиск соответствующего TPU-файла в ТЕКУЩЕМ каталоге.
3) Если в текущем каталоге нужный файл не найден, то поиск продолжается в каталогах, заданных в альтернативе Options/ Directories/Unit Directories для интегрированной среды или в параметре /U вызова ТРСкомпилятора.
4) Если на предыдущих шагах файл не найден, то компилятор прекращает работу и выдает диагностическое сообщение об ошибке.
5) Если компилятор активизирован посредством альтернатив Compile/Make или Compile/Build, то вышеуказанные шаги проводятся в поисках ИСХОДНЫХ ТЕКСТОВ используемых модулей, которые будут оттранслированы перед трансляцией самой программы. При этом подразумевается, что имя файла с текстом модуля совпадает с именем модуля и имеет расширение .PAS.
Как было указано, первый шаг компилятора при поиске используемых модулей состоит в анализе системного файла TURBO.TPL. Этот файл имеет специальную структуру и предназначен для компактного хранения и быстрого доступа к наиболее часто используемым модулям. Обычно в этом файле содержатся несколько системных (стандартных) модулей, однако с помощью специальной служебной программы TPUMOVER можно произвольным образом конструировать файл TURBO.TPL, включая в него нужные модули и удаляя неиспользуемые.
Понятие библиотеки модулей является потенциально удобным, но к сожалению, Turbo Pascal поддерживает только один библиотечный модуль; нельзя сформировать библиотеку модулей в некотором TPL-файле и обеспечить подключение к программе модулей из этой библиотеки.
Turbo Pascal имеет восемь стандартных модулей, в которых, собственно, и содержатся все упоминаемые в книге системные процедуры и функции. Имена этих модулей следующие:
SYSTEM
DOS
CRT
PRINTER
OVERLAY
GRAPH
TORBO3
GRAPHS
Программные ресурсы, сосредоточенные в стандартных модулях, образуют мощные пакеты системных средств, которые обеспечивают высокую эффективность и широкий спектр применений системы Turbo Pascal.
Каждый модуль хранится в одноименном TPU-файле в системном каталоге Turbo Pascal. Кроме того, обычно модули System, Dos, Crt, Printer и Overlay входят в состав системного библиотечного файла TURBO. TPL. Для того, чтобы воспользоваться ресурсами стандартного модуля, необходимо указать его имя в спецификации использования по обычным правилам. Исключение сделано для модуля System, имя которого можно не указывать, так как содержащиеся в нем ресурсы подключаются автоматически к любой программе.
Каждый стандартный модуль содержит логически связанную совокупность типов, констант, переменных и подпрограмм, относящихся к определенной области применений. Далее приводится общая характеристика стандартных модулей.
В модуль System входят все процедуры и функции авторской версии языка Pascal, подпрограммы стандартного Паскаля, а также много дополнительных подпрограмм общего характера, в частности, ориентированные на конкретную операционную среду.
Модуль Dos содержит средства доступа к операционной системе и по существу является программным представлением системного интерфейса MS-DOS.
Модуль Crt обеспечивает практически полный спектр возможностей для доступа к экрану дисплея в текстовом режиме. Кроме того, в данный модуль включены средства чтения информации с клавиатуры (включая расширенные коды клавиш) и простейшего управления звуком.
Модуль Printer содержит единственный интерфейсный элемент - переменную Lst стандартного типа text, системно связанную с логическим устройством PRN (то есть с печатающим устройством, если оно имеется в конфигурации). Использование этой переменной в стандартных процедурах Write и WriteLn приводит к выводу информации на печать.
Модуль Overlay предоставляет средства для организации так называемых оверлейных программ, позволяющих обеспечить достаточно эффективное выполнение больших программных систем, размер которых превышает объем доступной оперативной памяти.
Модуль Graph объединяет многочисленные программные средства управления графическим режимом работы дисплея. Данный модуль обеспечивает использование всех возможностей наиболее распространенных типов дисплейных адаптеров - CGA, EGA, VGA, Hercules и т.п. как для монохромных, так и для цветных дисплеев, и позволяет создавать разнообразные и эффективные графические программы.
Модули Turbo3 и Graph3 обеспечивают совместимость с данной версией системы Turbo Pascal тех программ, которые были разработаны для ранней версии 3.0.
В данном разделе содержится законченный пример модуля, в котором сосредоточены средства работы с памятью типа "стек". Обратите внимание, что способ конкретной реализации стека скрыт в разделе implementation; поэтому переход к другой реализации стека (не в виде массива, а, например, в виде связанного списка) не повлечет за собой необходимость изменения программ, использующих этот модуль (эти программы "не узнают" о таком изменении).
unit StackOps; { Операции над стеком целых }
interface
procedure Push ( Elem:integer );
function Pop : integer;
function Empty : boolean;
function Full : boolean;
implementation
{ Стек реализован в виде линейного массива элементов;
переменная Тор отмечает текущую вершину стека }
const
Мах = 100;
var
Stack array[1..Max] of integer;
Top : integer;
procedure Push; { помещение элемента в вершину стека }
begin
if Top>Max then Exit;
Stack[Top] := Elem;
inc(Top)
end;
function Pop; { извлечение элемента из вершины стека }
begin
Рop := 0 ;
if Top=1 then Exit;
dec(Top);
Pop := Stack[Top]
end;
function Empty; { проверка на пустоту стека }
begin
Empty := (Top=1)
end;
function Full; { проверка на заполненность стека }
begin
Full := (Тор>Мах)
end;
begin
{ инициализация: первоначально стек пуст }
Тор := 1
end.
В данном заключительном разделе приводятся синтаксические диаграммы для конструкций, рассмотренных в этой главе. Кроме того, здесь показана полная синтаксическая диаграмма для программы.
Программа
Модуль
Заголовок модуля
Интерфейс модуля
Спецификация использования
Элемент интерфейса
Реализация модуля
Элемент реализации
Инициализация модуля