Составление подпрограмм и модулей

В языке Turbo Pascal 7.0 программисту предоставлена возможность составлять два вида подпрограмм: подпрограм

мы-функции и подпрограммы-процедуры. С подпрограммами-функциями мы уже частично знакомы по стандартным математическим функциям (табл. 4.3) и функциям обработки строк. Значение любой стандартной функции можно получить либо с помощью оператора присваивания, например y:=Sin(x), z:=Sqrt(x), либо вывести на экран операторами вывода, например Write (Sin(x)), Writeln (Sqrt(x)). И в том, и в другом случае для получения значения функции необходимо указать значение ее аргумента х.

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

Например, для вычисления у = o*Cos(x)/ln(x) + Ехр(хД) необходимо указать значение х.

Функции пользователя конструируются специальным образом. Однако их использование аналогично описанному выше. При конструировании функции указывается ее имя, тип результата вычисления функции, в скобках перечисляются ее аргументы и их типы. Это так называемый заголовок функции. Далее составляется программа — тело функции, вычисляющая значение функции. В теле функции полученное значение функции обязательно присваивается ее имени. Это делается для того, чтобы это имя можно было использовать в выражениях. Вот как оформляется функция на Turbo Pascal 7.0.

Function Имя (х 1: тип хр 2- тип x2l •••> xw: ™п х«): Тип;

Var

{Объявление переменных, используемых при вычислении} {Значения функции}

Begin

{Предложения программы, вычисляющие}

{значение функции}

Имя:=Выражение;

End;

В заголовке после зарезервированного слова Function (функция) обязательно указывается ее имя. Далее в скобках через точку с запятой перечисляются аргументы функции и их типы. После через двоеточие записывается тип значения, которое возвращает функция. Аргументы функции принято называть ее формальными параметрами. Предложение «Имя:=Выражение» означает, что переменная Имя получает значение функции, вычисленное программой при конкретном значении ее параметров. Если аргументы одного типа, их можно перечислить через запятую, например хх, х2, х3: integer.

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

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

Function Fact (n: byte): longint: {Заголовок функции}

Var

F: longint; {Переменные подпрограммы-функции} i: byte;

Begin

If n<0 Then

Begin

Fact:=0; Exit; {Выход из тела функции} End;

F:=l;

For i=l to n do

F:=F*i;

Fact:=F;

End;

Имя подпрограммы-функции — Fact, формальный аргумент ее — п, его тип — byte, тип значения функции — longint. В операторе присваивания Fact:=F имя функции получает ее значение F, которое вычисляется подпрограммой при фактическом п. Если п < 0, то Fact = 0, и по оператору Exit осуществляется выход из тела функции.

Теперь рассмотрим применение программы-функции для вычисления числа сочетании из п элементов по т, определяемое

по формуле С"' =-—-.

(п - т)! т!

Согласно формуле для вычисления числа сочетаний С™ необходимо трижды вычислить значение факториала: п, т, (п - т). Чтобы не составлять программу, в которой трижды будут повторяться одни и те же действия по его вычислению, рационально составить программу-функцию и обращаться к ней для получения значения факториала при конкретных значениях п, т, п> т.

Подпрограмму мы уже составили. Теперь с учетом этого запишем последовательность действий, необходимых для вычисления С"'.

Пусть С — текущее значение числа сочетаний, а к = п - т. Тогда можно записать следующий порядок действий: С:=Рас1(п); С:=С/Рас1(к); С:=С/Рас1(ш). Ниже представлена программа вычисления С"' с учетом использования подпрограммы.

Program Soch; {Вычисление числа сочетаний}

Labi? m 1 ;

Var

n,m: byte; {Число элементов С"'} к: byte; {k=n-m}

С: Longint; {Число сочетаний С"'}

Function Fact (p: byte): longint: {Объявление функции}

Var

F: longint; {Переменные функции} i: byte;

Begin

F:=l;

For i=2 to p do F:=F*i;

Fact:=F;

End;

Begin

ml:

Writeln ('Введите n,m'); Readln (n,m);

If (n<0) Or (n

Begin

Writeln ('Повторите ввод');

Delay (120); ClrScr: Goto ml;

End;

k=n-m;

C:=Fact(n); C:=C/Fact(k); C:=C/Fact(m);

Writeln ('Число сочетаний C”=,' C);

End.

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

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

Таким образом, вместе с правилами составления подпрограмм функций мы познакомились с новым оператором Exit — выход из тела подпрограммы и функцией Delay(t:Word) приостановка выполнения программы на / миллисекунд.

Если результатом выполнения подпрограммы-функции является одно значение, присваиваемое ее имени, то подпрограмма-процедура позволяет на ее выходе получать более одного значения. С подпрограммами-процедурами мы уже частично знакомы по процедурам обработки строк Delete(CrpoKa, n,m), Insert(CrpoKa, Подстрока), УаЦСтрока, Переменная, Err), а также процедурам очистки экрана ClrScr, перемещения курсора в заданную позицию экрана Goto X,Y, задания фона экрана TextBackGround цвета выводимых символов и др. Следует отметить, что язык Turbo Pascal 7.0 содержит достаточно много различных стандартных процедур, для умелого использования которых нужно иметь перед собой специальный их перечень.

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

Procedure Имя (Var хр тип хр Var X2: тип X2; ..., Var хп: тип хя);

Var

{Объявление переменных, используемых при выполнении}

{процедуры}

Begin

{Предложения процедуры}

End;

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

Перед параметрами-значениями служебное слово Var не используется. Перед параметрами-переменными его запись обязательна. Тем самым по заголовку процедуры сразу же можно выяснить, какие параметры передаются в подпрограмму, а какие она возвращает.

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

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

{Заголовок процедуры}

{Объявление переменных процедуры}

бо {Заголовок цикла рисования звездочек} {Вывод звездочек}

{Восстановление строки}

Procedure Line 80;

Var

i: byte;

Begin

For i=l to n

Write ('*’);

Writeln;

End;

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

Procedure Line; (n: integer; c: char);

Var

i: byte;

Begin

For i=l to n do

Write (c);

Writeln;

End;

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

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

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

Program L*; {Рисование линий звездочек}

Var

i,k: byte; {Переменные основной программы}

Procedure Line 80; {Объявление процедуры}

Var

i: byte; {Переменная процедуры}

Begin

For i=l to 80 do

Write ('*'); {Вывод звездочек}

Writeln; {Восстановление строки}

End;

Begin {Начало основной программы}

к:=1;

For i=l to 7 do Begin

GotoXY(l,k); {Установка строки ввода}

Line 80; {Обращение к процедуре} k:=k+4; {Изменение номера строки}

End;

End.

В качестве примера конструирования процедуры, когда формальные параметры предусматривают передачу в подпрограмму массивов, рассмотрим вычисление скалярного произведения двух векторов Х= (л:,, х2, ..., хп), А = (а{, а2, ..., ап), компонентами которых являются действительные числа.

Как известно, скалярное произведение 5К двух векторов А и X определяется так: SK = ?,x, + а2х2 + ... + altxn. Поэтому порядок его вычисления очевиден: в цикле необходимо накапливать сумму произведений одноименных компонент векторов.

Условимся, что в основной программе будет осуществляться ввод компонентов векторов и вывод скалярного произведения. На процедуру возлагается только вычисление этого произведения.

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

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

Procedure SK (А,Х: MASS; n: byte; Var S: real);

где SK — имя процедуры, служащее для обращения к ней; п — размер массивов; S — возвращаемое в основную программу скалярное произведение векторов.

Теперь составим программу.

Program Skai; {Вычисление скалярного произведения}

Туре

MASS=Array [1,30] of real; {Тип массивов}

Var

А,Х: MASS;

i,n: byte; {Индекс элемента массива и размер массива}

S: real; {Скалярное произведение}

Procedure SK (А,Х: MASS; n: byte; Var S: real);

Var j: byte;

Begin {Начало вычисления скалярного произведения}

S:=0;

For j=l to n do

S:=S+A[j]*X[j];

End; {Конец вычисления скалярного произведения}

Begin {Начало основной программы}

Writeln ('Введите n'); Readln (п);

For i=l to n do

Readln (A[i], X[i]); {Ввод элементов массива}

SK (A, X, n, S); {Обращение к процедуре}

Write ('Скалярное произведение S=', S:6:2);

End. {Конец основной программы}

Рассмотрим еще один пример использования процедуры. Для этого предварительно, используя блок-схему алгоритма (рис. 1.49, 1.50), составим программу всех перестановок п чисел.

Следуя точно алгоритму рис. 1.49 и его фрагментам рис. 1.50, получим

Program Per; {Построение перестановок п чисел}

Lable

ml, m2;

Var

Р: Array f 1,20] of byte; {Массив, хранящий перестановку} i,k,m,n: byte;

Begin

Writeln ('Введите n'); Readln (n);

For i= 1 to n do Begin

P[i]:=i; Write (i); {Первая перестановка и ее вывод}

End;

ml:

m:=n;

m2:

k:=P[l]; {Начало вращения}

For i=l to m do {Первых m чисел}

P[i-l]:=P[i]; {В перестановке}

P[m]:=k; Writeln;

If kom Then Begin

For i=l to n do {Вывод очередной}

Write (Pfi]); Goto ml; {Перестановки и переход к ml}

End;

If m<>2 Then Begin

n:=m-l; Goto m2;

End;

End.

В этой программе в двух местах имеем повторяющиеся действия: вывод на экран первой и очередных перестановок. Этот процесс выполняется в цикле, для этого используется оператор типа For — do.

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

Program Perl;

Lable

ml, m2;

Type

MASS=Array [1,20] of real;

Var

P: MASS; i,k,m,n: byte;

Procedure Vivod (P: MASS; n: byte)

Var

Begin

For i=l to n do Write P[i];

End;

Begin {Начало основной программы}

Writeln ('Введите n'); Readln (n);

For i=l to n do {Формирование первой перестановки}

P[i]:=i;

Vivod (P,n); {Обращение к процедуре}

ml:

m:=n;

m2:

k:=P[l];

For i=2 to m do P[i-l]:=P[i];

P[m]:=k; Writeln;

Tf kom Then Begin

Vivod (P, n); Goto ml; {Обращение к процедуре}

End;

If m<>2 Then Begin

m:=m-l; Goto m2;

End;

End.

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

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

В заключение отметим, что при оформлении подпрограмм как функций, так и процедур используются термины «глобальные и локальные переменные». Следует помнить, что глобальные переменные — это переменные, объявленные в основной программе до объявления подпрограмм.

Локальные переменные объявляются в подпрограммах. Они действуют только в теле подпрограмм. Глобальные переменные воспринимаются, говорят — доступны, не только в основной программе, но и в подпрограммах. При этом формальные параметры подпрограмм запрещено объявлять в качестве локальных переменных.

В языке Turbo Pascal 7.0 предусмотрена возможность создания рекурсивных подпрограмм. Это такие подпрограммы, которые для вызова обращаются сами к себе.

Слово рекурсия происходит от лат. recurrere — возвращаться. Поэтому все объекты математики — рекурсивные функции, рекуррентные последовательности, рекуррентные соотношения или вычисления — используют самих себя.

Типичным представителем рекурсивной функции является факториальная функция п. Она определяется так: 0! = 1,

п = {п-)п. Таким образом, для вычисления п необходимо предварительно вычислить (п - 1)!. Для вычисления (п - 1)! прежде необходимо вычислить (п - 2)! и т. д. Всего для вычисления п необходимо п раз обратиться к способу его вычисления 1 • 2 • 3 • ... • п. Количество обращений к самому себе принято называть глубиной рекурсии.

Известным представителем рекуррентной последовательности является последовательность Фибоначчи 1, 1, 2, 3, 5, 8, 13, полученная итальянским математиком в XIII в. при изучении закона размножения кроликов. Рекуррентное соотношение, позволяющее вычислять каждый очередной член последовательности на основании предшествующих ее членов, такое: Щ = 1, «2= 1, ..., Un=Un_x + w„_2.

В качестве примера составим программу вычисления функции п с использованием рекурсивной подпрограммы-функции.

Program Fact R; {Вычисление п! с использованием рекурсии}

Var

n: byte;

F: Longint; {Значение факториала}

Function Fact (k: byte): Longint; {Заголовок функции}

Begin {Тело функции}

If (k < 0) Or (k=l) Then Fact:=l Else Fact:=k*Fact(k-l);

End;

Begin {Начало основной программы}

Write ('Введите n'); Readln (n);

F:=Fact(n);

Writeln ('Факториал', n, ' =', F);

End.

Таким образом, можно заметить, что имя функции Fact при рекурсивном обращении получает значение в процессе выполнения подпрограммы, и ее имени не требуется присваивание вычисленного значения функции, как это требовалось раньше при вычислении функции. Окончательное значение, которое получает функция в результате п рекурсивных обращений, — Fact(n). Это значение и присваивается F для вывода, хотя последний мог быть осуществлен и так: Writeln ('Факториал', п, ' =', Fact(n)). Тогда бы в разделе объявлений не понадобилось бы объявлять переменную F: Longint.

Аналогичным образом можно составить рекурсивную подпрограмму для вычисления любого члена ряда Фибоначчи.

Program Fib; {Вычисление n-го члена ряда Фибоначчи}

Var

n: byte;

Function Fib (к: byte): Integer;

Begin

Tf (k=l) Or (k=2) Then Fib:=l Else

Fib:=Fib(k-l)+ Fib(k-2);

End;

Begin

Writeln ('Введите n'); Readln (n);

Write ('n-й член ряда Фибоначчи', =Fib(n));

End.

Раньше (гл. 4, п. 4.2) мы говорили о том, что в языке Turbo Pascal 7.0 используется много стандартных модулей — специальным образом оформленных программных единиц. Эти модули предназначены для расширения возможностей языка и стандартизации часто используемых в программах действий. Модули содержат константы, типы, переменные, функции и процедуры. Для того, чтобы эти объекты модулей были доступны инструкциям программы, составляемой программистом, модули должны быть объявлены в разделе деклараций Uses. Некоторые модули к любой разрабатываемой программе подключаются автоматически в процессе компиляции, в частности модуль System.

Частично мы знакомились с процедурами модуля Crt, которые позволяют управлять выводом информации на экран и работой клавиатуры. Теперь расскажем о том, как выводить информацию на принтер.

Для того чтобы выполнять операции вывода данных на печатающее устройство, в разделе объявлений Uses необходимо прежде всего указать имя модуля Printer, процедуры которого осуществляют эти действия. После этого в операторах вывода Write (список вывода), Writeln (список вывода) для печати информации на принтере перед списком вывода необходимо указать аббревиатуру Lst. Например, Write (Lst, 'Я научился программировать') или Writeln (Lst, 'х=', х:6:2, 'у=', у:6:2). В результате принтер напечатает: Я научился программировать х=..., у=..., где х, у — выводимые числа.

Turbo Pascal предоставляет возможность программисту составлять свои собственные модули и использовать их совершенно так же, как и стандартные модули языка.

Для этого необходимо следовать такой последовательности записей.

Unit Имя модуля; {Объявление модуля}

Interface {Объявления констант, типов, переменных, функций

и процедур, которые могут применяться в программах, использующих модуль}

Implementation {Объявления констант, типов, переменных, которые

используются функциями и процедурами модуля} {Предложения реализации функций и процедур модуля}

Begin {Предложения инициализации (задания начальных значений) переменных модуля}

End.

Модуль в обязательном порядке начинается словом Unit, что в переводе с англ, означает «единица». Имя модуля выбирается программистом произвольно согласно правилам выбора имен переменных. В разделе Implementation (выполнение) записываются программы, реализующие функции и процедуры. Раздел Begin может отсутствовать.

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

Заметим, что степень х" вычисляется как умножение числа х п раз само на себя. Поэтому процесс вычисления х" должен содержать п циклов. Кроме того, если показатель степени равен нулю, то х°= 1, если же он отрицательный, т. е. необходимо вычислить х", то по известной формуле х“" = 1/х" легко определить и степень с этим ее показателем. С учетом сказанного составим функцию, назвав ее Step.

Function Step (х: real; n: word): real;

Begin

If n=0 Then Step:=l Else

If n>0 Then Step:=x*Step(x, n-1) Else

Begin

n:=Abs(n); Step:=x*Step(x, n-1);

Step:=l/Step;

End;

End.

В этой программе осуществляются проверки знака показателя степени п и рекурсивное вычисление х" по двум ветвям: п > О и п < 0.

Теперь составим процедуру. Для этого обратимся к программе MR, где рассмотрена технология генерации массивов случайных чисел на основании функции Random.

Формальными параметрами процедуры должны быть границы а, b, а < Ь, интервала генерации чисел, размер массива п и непосредственно сгенерированный массив чисел Mass. При этом а, Ь, п — входные параметры процедуры, a Mass — параметр ее выхода. На этом основании получаем

Procedure Gmass (a,b: real; mbyte; Var m: Mass);

Var

i: byte;

Begin

Randomize;

For i=l to n do

M(i):=Int(a+Random*(b-a));

End;

После этого остается включить полученные программы в модуль. Назовем его MyUnit.

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

Unit MyUnit; {Модуль программиста}

Interface

Function Step (х: real; n: Word): real; {Возведение x в целую}

{степень}

Procedure Gmass (a,b: real; mbyte; Var m: Mass); {Генерация}

{массива}

Implementation

Function Step (x: real; n: word): real;

Begin

If n=0 Then Step:=l Else

If n>0 Then Step:=x*Step(x, n-1);

Step:=l/Step;

End;

End;

Procedure Gmass (a,b: real; n: Word; Var m: Mass);

Var i: byte;

Begin

Randomize;

For i= 1 to n do M(i):=Int(a+Random*(b-a));

End;

End;

End.

Для использования программных единиц модуля MyUnit в программах пользователя при составлении этих программ в разделах объявлений наряду с другими модулями необходимо указать имя этого модуля так

Uses

MyUnit;

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

Program Primer; {Использование программ модуля}

Uses

MyUnit, Crt;

Type

Mass=Array [ 1.. 1000] of real;

Var

x,y: real; i,mbyte; a,b: real;

M: Mass;

Begin

ClrScr;

Writeln ('Вычислить 1,253'); Readln (x, n); y:=Step(x,n);

Writeln (T,253=', y:4:2); Readln;

Writeln ('Сгенерируйте массив чисел в инте');

Writeln ('рвале [I, 300] размером п=50');

Readln (a,b,n);

Gmass (a,b,n,m);

Writeln ('Выведите массив на экран'); Readln;

For i=l to n do

Write (Mfi]>; Readln;

End.

 
< Пред   СОДЕРЖАНИЕ     След >