Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Косвенные и перекрестные ссылки на модули



Вначале рассмотрим случай косвенного использования модуля. Например, пусть имеются два модуля:

unit A; interface ....... end.   unit B; interface uses A; ....... end.

Здесь, как ясно видно, модуль В использует модуль А, и если в вызывающей программе будет использовано предложение uses вида:

USES B; = Uses B, A;

то все равно при этом неявно(косвенно) будет использован и модуль А, и он, естественно, будет тоже прикомпонован к вызывающей программе.

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

unit A; interface uses B; ....... end. unit B; interface uses A; ....... end.

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

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

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

unit C; interface type t=record
в этом модуле отсутствуют секции реализации и инициализации
c: char;

s: string;

i: integer;

end;

var

v: t;

const

m=25;

implementation

end.

ПРОГРАММА Program P; Uses A, B; .....
B
A
C
Unit B; interface uses C; ....... end.  
Unit A; interface uses C; ....... end.

Однако допускаются перекрестные связи между модулями через секции реализации.

В следующей программе показаны два модуля, которые " используют" друг друга. Основная программа Message использует модуль с именем Display. Модуль Display содержит в своей интерфейсной секции одну процедуру WriteXY, которая имеет три параметра: пару координат (x, y) и сообщение для вывода на экран. WriteXY перемещает курсор в точку (x, y) и выводит там сообщение. В противном случае она вызывает простую программу обработки ошибки.

Пока мы не видим здесь ничего интересного: процедура WriteXY просто используется вместо процедуры Write. Однако далее, когда программа обработки ошибки будет выводить сообщение на экран, начинаются перекрестные ссылки (ведь при этом она снова использует WriteXY). Таким образом, мы имеем процедуру WriteXY, вызывающую процедуру обработки ошибки SwapError, которая в свою очередь вызывает WriteXY для вывода сообщения на экран.

Пусть основная программа Message очищает экран и выполняет три обращения к процедуре WriteXY:

program Message;

{ выводит текст, используя WriteXY }

uses

WinCrt, Display;

begin

ClrScr;

WriteXY(1, 1, 'Левый верхний угол экрана');

WriteXY(100, 100, 'За пределами экрана');

WriteXY(81 - Lenght('Снова в экран..'), 15, 'Снова в экран..');

end.

Взгляните на координаты (x, y) при втором обращении к процедуре WriteXY. В точке с координатами (100, 100) на 80х25-символьном экране вывести текст невозможно. Давайте теперь посмотрим, как работает процедура WriteXY. Далее приведен текст исходного кода модуля Display, в котором содержится процедура WriteXY. Если координаты (x, y) являются допустимыми, она выводит на экран сообщение. В противном случае она выводит сообщение об ошибке.

unit Display;

{ содержит простую программу вывода информации на экран }

interface

procedure WriteXY(X, Y: integer, Message: string);

implementation

uses

Crt, Error;

procedure WriteXY(X, Y: integer, Message: string);

begin

if (X in [1..80] and Y in [1..25]

then

begin

Gotoxy(X, Y);

Write(Message);

end;

else

ShowError('Неверные координаты в процедуре WriteXY');

end;

end.

 

 

Процедура ShowError, вызываемая в процедуре WriteXY, показана в приведенном далее исходном коде модуля Error. Она всегда выводит сообщение об ошибке на 25-й строке экрана.

unit Error; {содержит простую программу сообщения об ошибке}

interface

procedure ShowError(ErrMsg: string);

implementation

uses

Display;

procedure ShowError(ErrMsg: string);

begin

WriteXY(1, 25, 'Ошибка: '+ ErrMsg);

end;

end.

Обратите внимание, что операторы uses в секции реализации обоих модулей (Display и Error) ссылаются друг на друга. Эти два модуля могут ссылаться друг на друга в секции реализации благодаря тому, что Borland Pascal может для обеих модулей выполнять полную компиляцию интерфейсных секций. Другими словами, компилятор правильно воспринимает ссылку на частично скомпилированный модуль A в секции реализации модуля В, если интерфейсные секции модуля A и модуля В не зависят друг от друга (и, следовательно, строго соблюдаются правила Паскаля, касающиеся порядка описания).

 

Пример модуля (стек)

Рассмотрим пример построения модуля, объединяющего в себе средства работы со структурой данных " стек" . В общем случае стек (магазин) работает по принципу " первым пришел - последним ушел" (LIFO), причем доступ к элементам стека возможен только через одно место - через т.н. голову стека, которая изменяет свое положение после каждой записи/чтения в/из стек/стека. В зависимости от реализации стек м.б. бесконечным (при реализации в виде динамической связанной структуры, где память выделяется и освобождается динамически - при выполнении программы/модуля) и конечным (при реализации в виде статической структуры - массива, для которой память выделяется только один раз - при компиляции программы).

Чаще всего стек используется в 2 случаях:

1) При интерпретации выражений, где до определенного момента операнды заталкиваются в стек.

2) При работе в таких ситуациях, где надо одновременно держать в памяти несколько поколений какого-то объекта, при этом доступен будет лишь самый «молодой» потомок. Пример – текстовые окна.

Пусть в стек надо записать последовательность символов a, b, c,.......

Обобщенный вид стека при выполнении действий:

Исходное состояние После записи а После записи b После записи с После чтения с
голова   голова a голова b голова c голова b  
          a   b   a  
              a      

 

Реализация стека в виде динамически связанной структуры:

Голова Голова Голова

 

inf=a   inf=b   inf=a   inf=с   inf=b   inf=a)
link=nil   link   link=nil   link   link   link=nil

После записи a После записи b После записи c

Реализация в виде массива из 3-х элементов:

Голова   Голова   Голова   Голова   Голова  
  a b c b  
    a b a  
3       a    

Исходное состояние После записи a После записи b После записи c После чтения c

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

Замечания:

1) Голова стека будет указывать на ту ячейку, в которую должна выполняться текущая запись.

2) Условимся, что голова стека не может перескакивать за границу массива, т.е. значение головы стека должно быть не больше числа элементов массива.

Итак, модель стека пусть имеет следующий вид:

j – указатель на голову
  При записи (push) j: = j+1     При чтении (pop) j: = j-1
 
 
 
 
 
 

Необходимые переменные

Const

n=10; {размер стека}

Var

j: byte; {голова стека}

s: array[1..10] of byte; {носитель}

FULL: boolean; {стек полон}

EMPTY: boolean; {стек пуст }

elem: byte; { то, что помещается в стек при Записи }

Исходное состояние (надо уставливать в секции инициализ.):

EMPTY: =true; FULL: =false; j: =1;

 

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

Запись Чтение
Если стек_ не_полон то Записать_в_стек иначе writeln('Стек полон'); всё Если стек_не_пуст то Прочитать_из_стека иначе writeln ('Стек пуст'); всё
  {Запись в j-ю ячейку} s[j] : = elem; {теперь стек не пуст} EMPTY: =false Если j < n то j: = j+1; иначе FULL: = true; {стек теперь полон} все   Если стек_не_полон то begin j: = j-1 если j = 1 то EMPTY: = t rue все end все {Чтение из j-й ячейки } elem: = s[j]; {стек теперь не полон} FULL: = false;

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

1) В секцию реализации

2) В основную программу

Unit stack;

Interface

Var elem: byte; {то, что считывается и записывается в стек}

Procedure push(elem: byte); Procedure pop(var elem: byte);

Implementation

Const
Заголовок приведен без списка параметров, потому что полный заголовок приведён в интерфейсной части
n=10;

Var

j: byte; {голова стека}

s: array[1..10] of byte;

FULL: boolean; {стек полон}

EMPTY: boolean; {стек пуст }

Procedure push; Procedure push; {помещение эл-та в вершину стека. }

begin

if (not FULL)

then

{запись в стек}

begin s[j]: = elem; EMPTY: = false; if j < n then j: = j+1 else FULL: = true; end

 


else Writeln(‘Стек полон’);

end;

Procedure pop(var elem: byte); {Чтение эл-та из стека}

begin

if not EMPTY

then

 
 
begin if (not FULL) then j: =j-1; if (j> 1) then begin j: =j-1; if j = 1 then EMPTY: = true; end; elem: = s[j]; FULL: = false; end

 


else Writeln(‘Стек пуст);

end;

begin { Инициализация }

j: = 1; EMPTY: = true; FULL: = false;

end.

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

 

Program P;

uses

stack;

begin

push(1);

push(2);

push(3);

pop(elem);

writeln('Считанный элемент = ', elem);

end.

 

Модули и большие программы

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

Два аспекта Borland Pascal способствуют использованию модулей в такой функции:

· высокая скорость компиляции и компоновки;

· способность работать с несколькими файлами одновременно, например, с программой и несколькими модулями.

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

Набросок большой программы-редактора может иметь вид:

program Edit;

uses

WinCrt, Strings, { стандартные модули из TPW.TPL }

Edit_Globals, { модули, написанные пользователем }

Edit_Init,

Edit_Print,

Edit_Read,

Edit_Write,

Edit_Format;

{ описание процедур и функций программы }

begin { основная программа }

end. { конец программы Edit }

Модули в данной программе могут содержаться в TPW.TPL, библиотеке исполняющей системы Windows, или быть отдельными файлами.TPW. В последнем случае Borland Pascal выполняет за вас управление проектом. Это означает, что при перекомпиляции программы Edit с помощью встроенного в компилятор средства формирования Borland Pascal сравнивает даты каждого файла.PAS и.TPW и перекомпилирует любой модуль, исходный код которого перекомпилирован.

Другая причина использования модулей в больших программах состоит в ограничения кодового сегмента. Процессоры 8086 (и родственные им) ограничивают размер сегмента кода 64 килобайтами. Это означает, что основная программа и любой сегмент данных не может превышать 64К. Borland Pascal интерпретирует это, создавая для каждого модуляотдельный сегмент кода. Без этого объем кода вашей программы не мог бы превышать 64К.


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-07-12; Просмотров: 492; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.061 с.)
Главная | Случайная страница | Обратная связь