Назад | Вперед

Раздел 3. Структурное программирование на Паскале

Тема 8 Подпрограммы как основа нисходящего проектирования приложений

Понятие подпрограммы
Методика нисходящего проектирования
Процедуры и функции
Операторы выхода
Элементы теории структурного программирования

Понятие подпрограммы

В предыдущем разделе рассматривались основные операторы и типы данных, необхо­димые для составления программ. При этом предполагалось, что текст программы представляет собой линейную последовательность операторов присваивания, цикла и условных операторов. Таким способом можно решать не очень сложные задачи и составлять программы, содержащие несколько сот строк кода. После этого понятность исходного текста резко падает из-за того, что общая структура алгоритма теряется за конкретными операторами языка, выполняющими слишком детальные, элемен­тарные действия. Возникают многочисленные вложенные условные операторы и операторы циклов, логика становится совсем запутанной, при попытке исправить один ошибочный оператор вносится несколько новых ошибок, связанных с осо­бенностями работы этого оператора, результаты выполнения которого нередко учитываются в самых разных местах программы. Поэтому набрать и отладить длин­ную линейную последовательность операторов практически невозможно.
При создании средних по размеру приложений (несколько тысяч строк исходного кода) используется структурное программирование, идея которого заключается в том, что структура программы должна отражать структуру решаемой задачи, чтобы алгоритм решения был ясно виден из исходного текста. Для этого надо иметь средства для создания программы не только с помощью трех простых операторов, но и с помощью средств, более точно отражающих конкретную структуру алгоритма. С этой целью в программирование введено понятие подпрограммы — набора операторов, выполняющих нужное действие и не зависящих от других частей исходного кода. Программа разбивается на множество мелких подпрограмм (занимающих до 50 операторов — критический порог для быстрого понимания цели подпрограммы), каждая из которых выполняет одно из действий, предусмотренных исходным заданием. Комбинируя эти подпрограммы, удается формировать итоговый алгоритм уже не из простых операторов, а из законченных блоков кода, имеющих определенную смысловую нагрузку, причем обращаться к таким блокам можно по названиям. Получается, что подпрограммы — это новые операторы или операции языка, опреде­ляемые программистом.
Возможность применения подпрограмм относит язык программирования, в котором имеется возможность их формирования, к классу процедурных языков.


Наверх

Методика нисходящего проектирования

Наличие подпрограмм позволяет вести проектирование и разработку приложения сверху вниз — такой подход называется нисходящим проектированием. Сначала выделяется несколько подпрограмм, решающих самые глобальные задачи (например, инициализация данных, главная часть и завершение), потом каждый из этих модулей детализируется на более низком уровне, разбиваясь в свою очередь на небольшое число других подпрограмм, и так происходит до тех пор, пока вся задача не ока­жется реализованной.
Такой подход удобен тем, что позволяет человеку постоянно мыслить на предмет­ном уровне, не опускаясь до конкретных операторов и переменных. Кроме того, появляется возможность некоторые подпрограммы не реализовывать сразу, а временно откладывать, пока не будут закончены другие части. Например, если имеется необходимость вычисления сложной математической функции, то выделяется отдель­ная подпрограмма такого вычисления, но реализуется она временно одним опера­тором, который просто присваивает заранее выбранное значение (например, 5). Когда все приложение будет написано и отлажено, тогда можно приступить к реализации этой функции.
Немаловажно, что небольшие подпрограммы значительно проще отлаживать, что существенно повышает общую надежность всей программы.
Очень важная характеристика подпрограмм — это возможность их повторного исполь­зования. С интегрированными системами программирования поставляются большие библиотеки стандартных подпрограмм, которые позволяют значительно повысить производительность труда за счет использования чужой работы по созданию часто применяемых подпрограмм.
Рассмотрим пример, демонстрирующий методику нисходящего проектирования. Имеется массив Ocenki, состоящий из N (N > 2) судейских оценок (каждая оценка положительна). В некоторых видах спорта принято отбрасывать самую большую и самую маленькую оценки, чтобы избежать влияния необъективного судейства, а в зачет спортсмену идет среднее арифметическое из оставшихся оценок. Решим эту задачу, постепенно детализируя алгоритм (без привязки к конкретному языку программирования).
1. Процесс решения наиболее просто описывается подпрограммами:

  1. ввести_оценки_в_массив;
  2. удалить_самую_большую_оценку;
  3. удалить_самую_маленькую оценку;
  4. рассчитать_среднее_арифметическое_оставшихся_оценок;
  5. вывести_результаты.

Теперь можно приступить к детализации каждой их этих подпрограмм.
2. Удалить_самую_большую_оценку.
Как удалить самую большую оценку из статического массива? Вместо нее можно просто записать значение 0, а при подсчете среднего арифметического нулевые значения не учитывать:                                       *
  I = Номер caмoгo_бoлыuoгo_элeмeнrra_в_мaccивe;
  Ocenki[ I ] = 0;
3. Удалить_самую_маленькую_оценку.
  I = Номер самого маленького_элемента_в массиве;
  Ocenki( I ) = 0;

При реализации подпрограммы «Номер_самого_маленького_элемента_в_массиве» надо учесть, что искать придется самое маленькое из положительных значений (т.е. значений больших нуля).
4. Рассчитать_среднее_арифметическое_оставшихся_оценок;
Здесь потребуется оператор цикла, вычисляющий сумму всех элементов мас­сива Ocenki.
 SUM = 0
 FOR I - 1 ТО N
  SUM = SUM + Ocenki( I ) NEXT SUM - SUM / (N - 2)
В последнем операторе происходит вычисление среднего арифметического всех оценок. Сумма элементов массива делится на число элементов, уменьшенное на 2, потому что две оценки, самую большую и самую маленькую, учитывать не надо.
Если бы эта задача решалась последовательно, то уже на этапе удаления оценок могли возникнуть определенные проблемы.
Реализацию подпрограмм Номер_самого_большого_элемента_в_массиве и Номер_ самого_маленького_элемента_в_массиве выполните самостоятельно.


Наверх

Процедуры и функции

Подпрограммы бывают двух видов — процедуры и функции. Отличаются они тем, что процедура просто выполняет группу операторов, а функция вдобавок вычисляет некоторое значение и передает его обратно в главную программу (возвращает значение). Это значение имеет определенный тип (говорят, что функция имеет такой-то тип).
Процедура может содержать такие же разделы описаний, что и программа, а именно: разделы описания модулей, меток, констант, типов, переменных, процедур и функций.
В Си++ понятия «процедура» нет — там имеются только функции, а если никакого значения функция не вычисляет, то считается, что она возвращает значение типа «никакое» (void).
Чтобы работа подпрограммы имела смысл, ей надо получить данные из внешней программы, которая эту подпрограмму вызывает. Данные передаются подпрограмме в виде параметров или аргументов, которые обычно описываются в ее заголовке так же, как переменные.

Управление последовательностью вызова подпрограмм

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

Структура подпрограммы

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

 

Бейсик

Паскаль

Си++

Заголовок функции

FUNCTION имя (список_параметров)
Тип возвращаемого значения определяется специальным символом после имени функции

function имя  (список_параметров): тип_функции;

Тип_функции имя(список_параметров)

Тело

Последовательность операторов

begin
последовательность операторов
end;

{
последовательность операторов
};

Завершение

END FUNCTION

нет

Нет

Локальные переменные, объявленные внутри подпрограммы, имеют областью дей­ствия только ее тело.
Процедуры

 

Бейсик

Паскаль

Си++

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

SUB имя (список_параметров)

procedure имя (список_ параметров);

void имя(список_параметров)

Тело

Последовательность операторов

begin
последовательность операторов
end;

{
последовательность операторов
};

Завершение

END SUB

нет

нет

 

Как функция возвращает значение

После того как функция рассчитала нужное значение, ей требуется явно вернуть его в вызывающую программу. Для этого может использоваться специальный оператор (return в Си++) или особая форма оператора присваивания, когда в левой части указывается имя функции, а справа — возвращаемое значение. Далее приведены примеры функции, вычисляющей значение квадрата аргумента.
Бейсик:
  FUNCTION SQR (X AS INTEGER) SQR = X*X END FUNCTION
Паскаль:
function SQR(X: integer): integer;
begin
  SQR := X*X
end;
Си++:
int SQR(int x)
{
  return x*x;
};

Формальные и фактические параметры

Во время создания подпрограммы заранее неизвестно, какие конкретно параметры она может, и будет получать. Поэтому в качестве переменных, выступающих в роли ее аргументов в заголовке, могут использоваться произвольные допустимые назва­ния, даже совпадающие с уже имеющимися. Компилятор все равно поймет, что это не одно и то же.
Параметры, которые указываются в заголовке подпрограммы, называются формаль­ными. Они нужны только для описания тела подпрограммы. А параметры (конкрет­ные значения), которые указываются в момент вызова подпрограммы, называются фактическими параметрами. При выполнении операторов подпрограммы формаль­ные параметрыг ак бы временно заменятся на фактические.
Пример.
  int а, у;
  а = 5;
  у = SQR(a);
Программа вызывает функцию SQR() с одним фактическим параметром а. Внутри подпрограммы формальный параметр х получает значение переменной а и возво­дится в квадрат. Результат возвращается обратно в программу и присваивается переменной у.
В программе geron_sqrt, предназначенной для вычисления квадратного корня из числа (в данном примере из двойки) методом Герона, используется подпро­грамма-функция geron. Метод Герона — что метод последовательных прибли­жений. Если задано число а и из него требуется приближенно вычислить квадратный корень, то вначале выбирается произвольное начальное прибли­жение х0. Затем задается точность вычислений e > 0 и строится последователь­ность хn+1 = (хn + а / хn) / 2. Вычисления прекращаются при выполнении условия | хn+1 - хn | < e.
Алгоритму Герона можно придать следующий геометрический смысл (рис. 1.2). Изобразим график функции у = х2 и проведем прямую у = а. Если в точке (хn ,  хn2) провести касательную, то ее пересечение с прямой у = а даст значение хn+1 . Обозначая Dx = хn  - хn+1 и Dy = хn2 - а, получаем Dy/Dx=tga. По формулам дифференциального исчисления tga=dy/dx=2 хn , поэтому (хn2-a)/ = хn  - хn+1=2 хn , откуда и получается алгоритм формулы Герона.

img1
Рис. 3.1. Геометрическая иллюстрация алгоритма Герона

Цикл while в подпрограмме-функции geron выполняется до тех пор, пока не нарушится заданное в нем условие (неравенство). Невозможно заранее преду­гадать, когда это произойдет, и, следовательно, в такой ситуации цикл for был бы бесполезен. Подпрограмма geron вызывается в операторе WriteLn. Такой вызов функции в операторе вывода допускается. Во втором операторе вывода содержится обращение к встроенной функции вычисления квадратного корня. Таким образом, можно сравнить оба результата.
  {$N+}
  progrdin    geron__sqrt;
  function     geron(x:    Real):   Extended;
  const
  eps    =   l.0E-100;
  var
  y,    z:   Real;
  begin
  у    := 1.0;  
  {Цикл заданная   to-ihoctl    вычисления   квадратного   корня} 
  while    Absfz   -  у)  >= eps do
  begin
  z    := y;
  у   •-  !y + x / у)  /  2:  cnti:
  end;
  

Передача имен процедур и функций в качестве параметров

Во многих задачах, особенно в задачах вычислительной математики, необходимо передавать имена процедур и функций в качестве параметров. Для этого в TURBO PASCAL введен новый тип данных - процедурный или функциональный, в зависимости от того, что описывается.
Описание процедурных и функциональных типов производится в разделе описания типов:
type
  FuncType = Function(z: Real): Real;
  ProcType = Procedure (a,b: Real; var x,y: Real);
Функциональный и процедурный тип определяется как заголовок процедуры и функции со списком формальных параметров, но без имени. Можно определить функциональный или процедурный тип без параметров, например:
type
  Proc = Procedure;
После объявления процедурного или функционального типа  его  можно использовать для  описания  формальных  параметров  - имен процедур и функций. Кроме того, необходимо написать те реальные процедуры или функции, имена которых будут передаваться как фактические параметры. Эти процедуры и  функции должны компилироваться в режиме дальней адресации с ключом {$F+}.

Пример. Составить программу для вычисления определенного интеграла

img2

по методу Симпсона. Вычисление  подинтегральной функции реализовать с помощью функции, имя которой передается как параметр.  Значение определенного интеграла по формуле Симпсона вычисляется по формуле:
ISimps=2*h/3*(0.5*F(A)+2*F(A+h)+F(A+2*h)+2*F(A+3*h)+2*F(B-h)+0.5*F(B)),
где:        A и B - нижняя и верхняя границы интервала интегрирования,
       N - число разбиений интервала интегрирования,
       h=(B-A)/N, причем N должно быть четным.
Программа решения данной задачи может иметь следующий вид:
Program INTEGRAL;
 type
    Func= function(x: Real): Real;
 var
    I,TN,TK:Real;
    N:Integer;
{$F+}
 Function Q(t: Real): Real;
   begin
     Q:=2*t/Sqrt(1-Sin(2*t));
   end;
{$F-}
 Procedure Simps(F:Func; a,b:Real; N:Integer; var INT:Real);
   var
      sum, h: Real;
      j:Integer;
   begin
     if Odd(N) then N:=N+1;
     h:=(b-a)/N;
     sum:=0.5*(F(a)+F(b));
     for j:=1 to N-1 do
       sum:=sum+(j mod 2+1)*F(a+j*h);
       INT:=2*h*sum/3
   end;
 begin
   WriteLn(' ВВЕДИ TN,TK,N');
   Read(TN,TK,N);
   Simps(Q,TN,TK,N,I);
   WriteLn('I=',I:8:3)
 end.


Наверх

Операторы выхода

Для завершения  работы программ,  процедур и функций без предварительного перехода по меткам к закрывающему end в TURBO PASCAL введены процедуры Exit и Halt.
Вызов Exit завершает работу своего программного блока  и  передает управление вызывающей программе.  Если Exit выполняется в подпрограмме, то выполнение этой подпрограммы прекратится, и далее будет выполняться следующий за вызовом этой подпрограммы оператор. Если Exit выполняется в основной программе,  выход из нее будет  эквивалентен  ее нормальному завершению.
Вызов процедуры Halt,  где бы она не находилась,  завершает работу программы и передает управление операционной системе.
Процедура Halt имеет структуру Halt(n),  где n - код возврата, который может  быть проанализирован операционной системой с помощью команды IF ERRORLEVEL.  Значение n=0 соответствует нормальному завершению работы программы. Вызов процедуры Halt без параметра эквивалентен вызову Halt(0).


Наверх

Элементы теории структурного программирования

Базовые конструкции программы

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

Применяя только эти три конструкции, можно реализовать алгоритм решения любой задачи.
Следованием называется конструкция, представляющая собой последовательное выполнение двух или более операций,.
Конструкция, состоящая из развилки, двух операций и слияния, называется ветвлением. Одна из операций может отсутствовать.
Конструкция, имеющая линии управления, ведущие к предыдущим операциям или развилкам, называется циклом.
В свою очередь, конструкции следование,   ветвление  и  цикл можно представить как операции, так  как они имеют единственный вход и единственный  выход.
Произвольную последовательность операций можно представить как одну операцию.
Операция может быть реализована  любым  оператором языка ПАСКАЛЬ (простым или составным), либо группой операторов, за исключением оператора перехода GOTO.
В языке ПАСКАЛЬ количество базовых конструкций увеличено до шести, это:
-следование;
-ветвление;
-цикл с предусловием;
-цикл с постусловием;
-цикл с параметром;
-вариант.

Модули в Паскале

Модуль (UNIT) в TURBO PASCAL - это особым образом оформленная библиотека подпрограмм, которая представляет собой отдельно хранимую и независимо компилируемую программную единицу.
Однако в отличие от программы модуль не может быть запущен на выполнение самостоятельно, он может только участвовать в построении программ и других модулей.
Модули позволяют создавать личные библиотеки процедур и функций  и строить программы практически любого размера.
В общем случае модуль -  это  совокупность  программных  ресурсов, предназначенных для использования другими программами, под которыми понимаются любые элементы языка TURBO PASCAL: константы, типы,  переменные,  подпрограммы. Таким образом, модуль сам по себе не является выполняемой программой, а его элементы используются другими программными единицами.
Все программные элементы модуля можно разбить на две части:
  - программные элементы,  предназначенные для использования другими программами или модулями,  такие элементы называют видимыми вне модуля;
  - программные элементы, необходимые только для работы самого модуля, их называют невидимыми или скрытыми.
В соответствии с этим модуль, кроме заголовка, содержит две основные части, называемые интерфейсом и реализацией.
В общем случае модуль имеет следующую структуру:
 unit <имя модуля>;         {заголовок модуля}
 interface
 { описание видимых программных элементов модуля }
 { описание скрытых программных элементов модуля }
 begin
  { операторы инициализации элементов модуля }
end.
В частном случае модуль может не содержать части реализации и части инициализации, тогда структура модуля будет такой:
 unit <имя модуля>;         {заголовок модуля}
 interface   { описание видимых программных элементов модуля }
 implementation
 end.
Использование в модулях процедур и функций имеет свои особенности.
Заголовок подпрограммы содержит все сведения,  необходимые для ее вызова: имя, перечень и тип параметров, тип результата для функций, эта информация должна быть доступна для других программ и модулей. С другой стороны,   текст подпрограммы,  реализующий ее алгоритм,  другими программами и модулями не может быть использован.  Поэтому  заголовок процедур и функций помещают в интерфейсную часть модуля,  а текст - в часть реализации.
Интерфейсная часть  модуля  содержит только видимые (доступные для других программ и модулей)  заголовки процедур и функций (без служебного слова   forward).  Полный текст процедуры или функции помещают в часть реализации, причем заголовок может не содержать список формальных параметров.
Исходный текст модуля должен быть откомпилирован с помощью  директивы Make  подменю Compile и записан на диск.  Результатом компиляции модуля является файл с расширением .TPU (Turbo Pascal Unit). Основное имя модуля берется из заголовка модуля.
Для подключения  модуля  к  программе необходимо указать его имя в разделе описания модулей, например:
  uses  CRT, Graph;
В том случае,  если имена переменных в интерфейсной части модуля и в программе,   использующей этот модуль,  совпадают,  обращение будет происходить к переменной,  описанной в программе. Для обращения к переменной, описанной  в  модуле,   необходимо применить составное имя, состоящее из имени модуля и имени переменной, разделенных точкой.
Например, пусть имеется модуль, в котором описана переменная К:
unit M;
interface
  var K: Integer;
implementation
   .................
end.
Пусть программа, использующая этот модуль, также содержит переменную К:
Program P;
 uses M;
 var K: Char;
begin
     .............
end.
Для того, чтобы в программе P иметь доступ к переменной K из модуля M, необходимо задать составное имя M.K.
Использование составных имен применяется не только к именам  переменных, а ко всем именам, описанным в интерфейсной части модуля.
Рекурсивное использование модулей запрещено.
Если в модуле имеется раздел инициализации,  то операторы из этого раздела будут выполнены перед началом выполнения программы, в которой используется этот модуль.


Наверх

Назад | Вперед