Стандартный язык Pascal не располагает средствами разработки и поддержки библиотек программиста (в отличие, скажем, от языка Fortran и других языков программирования высокого уровня), которые компилируются отдельно и в дальнейшем могут быть использованы как самим разработчиком, так и другими. Если программист имеет достаточно большие наработки, и те или иные подпрограммы могут быть использованы при написании новых приложений, то приходится эти подпрограммы целиком включать в новый текст.
В Turbo Pascal это ограничение преодолевается за счет, во-первых, введения внешних процедур, во-вторых, разработки и использования модулей. В настоящей публикации на примерах рассмотрим работу с теми и другими программными единицами.
Начнем с внешних подпрограмм.
Такой механизм предусматривает, что исходный текст каждой процедуры или функции хранится в отдельном файле и при необходимости с помощью специальной директивы компилятора включается в текст создаваемой программы.
Покажем это на примере задач целочисленной арифметики, где аргументы, результаты и промежуточные величины являются целыми (Integer, Word, LongInt и т.д.). Вот несколько таких задач.
1. Дано натуральное число n. Найти сумму первой и последней цифры этого числа.
2. Дано натуральное число n. Переставить местами первую и последнюю цифры этого числа.
3. Дано натуральное число n. Дописать к нему цифру k в конец и в начало (если это возможно, т.е. результат не выйдет за диапазон допустимых значений), или сообщить о невозможности выполнения операции.
4. Найти наибольшую цифру в записи данного натурального числа.
5. Дано натуральное число n. Переставить его цифры так, чтобы образовалось максимальное число, записанное теми же цифрами.
При решении каждой из этих задач может быть использована функция, возвращающая количество цифр в записи натурального числа.
Вот возможный вариант такой функции:
Function Digits(N : LongInt) : Byte;
Var Kol : Byte;
Begin
Kol := 0;
While N <> 0 Do Begin Kol := Kol + 1; N := N Div 10 End;
Digits := Kol
End;
Сохраним этот текст в файле с расширением .inc (это расширение внешних подпрограмм в Turbo Pascal), например, digits.inc.
Еще необходима функция возведения натурального числа в натуральную степень.
Function Power(A, N : LongInt) : LongInt; {файл power.inc}
Var I, St : LongInt;
Begin
St := 1;
For I := 1 To N Do St := St * A;
Power := St
End;
Попробуем использовать функции при решении задачи номер один.
Program Example1;
Var N, S : LongInt;
{$I digits.inc} {подключаем внешнюю функцию digits.inc, возвращающую количество цифр в записи числа}
{$I power.inc} {внешняя функция, выполняющая возведение числа A в степень N}
Begin
Write('Введите натуральное число: ');
ReadLn(N);
{для определения последней цифры числа N берем остаток от деления этого числа на 10, а для определения первой делим N на 10 в степени на единицу меньшую, чем количество цифр в записи числа (нумерация разрядов начинается с 0)}
S := N Mod 10 + N Div Power(10, Digits(N) - 1);
WriteLn('Искомая сумма: ', S)
End.
Внешние процедуры создаются и внедряются в использующие их программы аналогично функциям, и мы не будем подробно на этом останавливаться.
Далее речь пойдет о модулях: их структуре, разработке, компиляции и использовании.
Модуль — это набор ресурсов (функций, процедур, констант, переменных, типов и т.д.), разрабатываемых и хранимых независимо от использующих их программ. В отличие от внешних подпрограмм модуль может содержать достаточно большой набор процедур и функций, а также других ресурсов для разработки программ. Обычно каждый модуль содержит логически связанные между собой программные ресурсы.
В основе идеи модульности лежат принципы структурного программирования. Существуют стандартные модули Turbo Pascal, которые обычно описываются в литературе по данному языку.
Модуль имеет следующую структуру:
Unit <имя модуля>; {заголовок модуля}
Interface
{интерфейсная часть}
Implementation
{раздел реализации}
Begin
{раздел инициализации модуля}
End.
После служебного слова Unit записывается имя модуля, которое (для удобства дальнейших действий) должно совпадать с именем файла, содержащего данный модуль. Поэтому (как принято в MS DOS) имя не должно содержать более 8 символов.
В разделе Interface объявляются все ресурсы, которые будут в дальнейшем доступны программисту при подключении модуля. Для подпрограмм здесь указывается лишь полный заголовок.
В разделе Implementation реализуются все подпрограммы, которые были ранее объявлены. Кроме того, здесь могут содержаться свои константы, переменные, типы, подпрограммы и т.д., которые носят вспомогательный характер и используются для написания основных подпрограмм. В отличие от ресурсов, объявленных в разделе Interface, все, что дополнительно объявляется в Implementation, уже не будет доступно при подключении модуля. При написании основных подпрограмм достаточно указать их имя (т.е. не нужно полностью переписывать весь заголовок), а затем записать тело подпрограммы.
Наконец, раздел инициализации (который часто отсутствует) содержит операторы, которые должны быть выполнены сразу же после запуска программы, использующей модуль.
Приведем пример разработки и использования модуля. Поскольку рассмотренная ниже задача достаточно элементарна, ограничимся листингом программы с подробными комментариями.
Задача. Реализовать в виде модуля набор подпрограмм для выполнения следующих операций над обыкновенными дробями вида P/Q (P — целое, Q — натуральное): 1) сложение; 2) вычитание; 3) умножение; 4) деление; 5) сокращение дроби; 6) возведение дроби в степень N (N — натуральное); 7) функции, реализующие операции отношения (равно, не равно, больше или равно, меньше или равно, больше, меньше).
Дробь представить следующим типом:
Type Frac = Record
P : Integer;
Q : 1.. High(LongInt)
End;
Используя этот модуль, решить задачи:
1. Дан массив A — массив обыкновенных дробей. Найти сумму всех дробей, ответ представить в виде несократимой дроби. Вычислить среднее арифметическое всех дробей, ответ представить в виде несократимой дроби.
2. Дан массив A — массив обыкновенных дробей. Отсортировать его в порядке возрастания.
Unit Droby;
Interface
Type
Natur = 1..High(LongInt);
Frac = Record
P : LongInt; {Числитель дроби}
Q : Natur {Знаменатель дроби}
End;
Procedure Sokr(Var A : Frac);
Procedure Summa(A, B : Frac; Var C : Frac);
Procedure Raznost(A, B : Frac; Var C : Frac);
Procedure Proizvedenie(A, B : Frac; Var C : Frac);
Procedure Chastnoe(A, B : Frac; Var C : Frac);
Procedure Stepen(A : Frac; N : Natur; Var C : Frac);
Function Menshe(A, B : Frac) : Boolean;
Function Bolshe(A, B : Frac) : Boolean;
Function Ravno(A, B : Frac) : Boolean;
Function MensheRavno(A, B : Frac) : Boolean;
Function BolsheRavno(A, B : Frac) : Boolean;
Function NeRavno(A, B : Frac) : Boolean;
{Раздел реализации модуля}
Implementation
{Наибольший общий делитель двух чисел - вспомогательная функция, ранее не объявленная}
Function NodEvklid(A, B : Natur) : Natur;
Begin
While A <> B Do
If A > B Then
If A Mod B <> 0 Then A := A Mod B Else A := B
Else
If B Mod A <> 0 Then B := B Mod A Else B := A;
NodEvklid := A
End;
Procedure Sokr; {Сокращение дроби}
Var M, N : Natur;
Begin
If A.P <> 0 Then
Begin
If A.P < 0 Then M := Abs(A.P)
Else M := A.P; {Совмещение типов, т.к. A.P - LongInt}
N := NodEvklid(M, A.Q); A.P := A.P Div N; A.Q := A.Q Div N
End
End;
Procedure Summa; {Сумма дробей}
Begin
{Знаменатель дроби} C.Q := (A.Q * B.Q) Div NodEvklid(A.Q, B.Q);
{Числитель дроби} C.P := A.P * C.Q Div A.Q + B.P * C.Q Div B.Q;
Sokr(C)
End;
Procedure Raznost; {Разность дробей}
Begin
{Знаменатель дроби} C.Q := (A.Q * B.Q) Div NodEvklid(A.Q, B.Q);
{Числитель дроби} C.P := A.P * C.Q Div A.Q - B.P * C.Q Div B.Q;
Sokr(C)
End;
Procedure Proizvedenie;
Begin
{Знаменатель дроби} C.Q := A.Q * B.Q;
{Числитель дроби} C.P := A.P * B.P;
Sokr(C)
End;
Procedure Chastnoe;
Begin
{Знаменатель дроби} C.Q := A.Q * B.P;
{Числитель дроби} C.P := A.P * B.Q;
Sokr(C)
End;
Procedure Stepen; {Степень}
Var I : Natur;
Begin
C.Q := 1; C.P := 1; Sokr(A);
For I := 1 To N Do Proizvedenie(A, C, C)
End;
Function Menshe;
Begin Menshe := A.P * B.Q < A.Q * B.P End;
Function Bolshe;
Begin Bolshe := A.P * B.Q > A.Q * B.P End;
Function Ravno;
Begin Ravno := A.P * B.Q = A.Q * B.P End;
Function BolsheRavno;
Begin BolsheRavno := Bolshe(A, B) Or Ravno(A, B) End;
Function MensheRavno;
Begin MensheRavno := Menshe(A, B) Or Ravno(A, B) End;
Function NeRavno;
Begin NeRavno := Not Ravno(A, B) End;
{Раздел инициализации модуля}
Begin
End.
Дадим некоторые рекомендации по разработке модулей:
1) спроектировать модуль, т.е. выделить основные и вспомогательные подпрограммы, другие ресурсы;
2) каждую подпрограмму целесообразно отладить отдельно, после чего «вклеить» в текст модуля.
Сохраним текст разработанной программы в файле DROBY.PAS и откомпилируем наш модуль. Для этого можно воспользоваться внешним компилятором, поставляемым вместе с Turbo Pascal. Команда будет выглядеть так: TPC DROBY.PAS. Если в тексте нет синтаксических ошибок, получим файл DROBY.TPU, иначе будет соответствующее сообщение с указанием строки, содержащей ошибку. Другой способ компиляции модуля — в среде программирования Turbo Pascal выбрать в пункте меню Run подпункты Make или Build (при этом должна быть включена компиляция на диск).
Теперь можно подключить модуль к программе, где планируется его использование.
Для примера решим задачу суммирования массива дробей.
Program Sum;
Uses Droby;
Var A : Array[1..100] Of Frac;
I, N : Integer;
S : Frac;
Begin
Write('Введите количество элементов массива: ');
ReadLn(N);
S.P := 0; S.Q := 1; {Первоначально сумма равна нулю}
For I := 1 To N Do {Вводим и суммируем дроби}
Begin
Write('Введите числитель ', I, '-й дроби: '); ReadLn(A[I].P);
Write('Введите знаменатель ', I, '-й дроби: '); ReadLn(A[I].Q);
Summa(A[I], S, S);
End;
WriteLn('Ответ: ', S.P, '/', S.Q)
End.
Вторую задачу предлагаем решить читателю самостоятельно.
Как видно из примера, для подключения модуля используется служебное слово USES, после чего указывается имя модуля и происходит это сразу же после заголовка программы. Если необходимо подключить несколько модулей, они перечисляются через запятую.
При использовании ресурсов модуля совсем не нужно знать, как работают его подпрограммы. Достаточно обладать информацией, как выглядят их заголовки и какое действие эти подпрограммы выполняют. По такому принципу осуществляется работа со всеми стандартными модулями. Поэтому, если программист разрабатывает модули не только для личного пользования, ему необходимо сделать полное описание всех доступных при подключении ресурсов. В таком случае возможна полноценная работа с таким продуктом.