Описание и вызов процедур и функций
Структура описания процедур и функций до некоторой степени похожа на структуру Паскаль-программы: у них также имеются заголовок, раздел описаний и исполняемая часть. Раздел описаний содержит те же подразделы, что и раздел описаний программы: описания констант, типов, меток, процедур, функций, перменных. Исполняемая часть содержит собственно операторы процедур.
Формат описания процедуры имеет вид:
procedure имя процедуры (формальные параметры); раздел описаний процедурыbegin исполняемая часть процедуры end;Формат описания функции:
function имя функции (формальные параметры):тип результата; раздел описаний функцииbegin исполняемая часть функции end;Формальные параметры в заголовке процедур и функций записываются в виде:
var имя праметра: имя типаи отделяются друг от друга точкой с запятой. Ключевое слово var может отсутствовать (об этом далее). Если параметры однотипны, то их имена можно перечислять через запятую, указывая общее для них имя типа. При описании параметров можно использовать только стандартные имена типов, либо имена типов, определенные с помощью команды type.Список формальных параметров может отсутствовать.
Вызов процедуры производится оператором, имеющим следующий формат:
имя процедуры(список фактических параметров);Список фактических параметров - это их перечисление через запятую. При вызове фактические параметры как бы подставляются вместо формальных, стоящих на тех же местах в заголовке. Таким образом происходит передача входных параметров, затем выполняются операторы исполняемой части процедуры, после чего происходит возврат в вызывающий блок. Передача выходных параметров происходит непосредственно во время работы исполняемой части.
Вызов функции в Турбо Паскаль может производиться аналогичным способом, кроме того имеется возможность осуществить вызов внутри какого-либо выражения. В частности имя функции может стоять в правой части оператора присваивания, в разделе условий оператора if и т.д.
Для передачи в вызывающий блок выходного значения функции в исполняемой части функции перед возвратом в вызывающий блок необходимо поместить следующую команду:
имя функции := результат;
При вызове процедур и функций необходимо соблюдать следущие правила:
- количество фактических параметров должно совпадать с количеством формальных;
- соответствующие фактические и формальные параметры должны совпадать по порядку следования и по типу.
Заметим, что имена формальных и фактических параметров могут совпадать. Это не приводит к проблемам, так как соответствующие им переменные все равно будут различны из-за того, что хранятся в разных областях памяти. Кроме того, все формальные параметры являются временными переменными - они создаются в момент вызова подпрограммы и уничтожаются в момент выхода из нее.
Рассмотрим использование процедуры на примере программы поиска максимума из двух целых чисел.
var x,y,m,n: integer; procedure MaxNumber(a,b: integer; var max: integer);begin if a>b then max:=a else max:=b;end; begin write('Введите x,y '); readln(x,y); MaxNumber(x,y,m); MaxNumber(2,x+y,n); writeln('m=',m,'n=',n);end.Аналогичную задачу, но уже с использованием функций, можно решить так:
var x,y,m,n: integer; function MaxNumber(a,b: integer): integer; var max: integer;begin if a>b then max:=a else max:=b; MaxNumber := max;end; begin write('Введите x,y '); readln(x,y); m := MaxNumber(x,y); n := MaxNumber(2,x+y); writeln('m=',m,'n=',n);end.Параметры-константы
На этом шаге мы рассмотрим использование параметров-констант.
При описании параметров-констант в заголовках процедур/функций перед идентификаторами таких параметров ставится ключевое const.
Заголовок процедуры с описанными параметрами-константами имеет такой вид:
procedure MyProc (const Par1, Par2 : Type1; const РаrЗ, Раr4 : Туре2);
Приведем упрощенную схему и описание механизма работы параметров-констант:
Рис.1. Механизм передачи параметров
При вызове процедуры/функции:
выполняется выделение памяти только для локальных данных и для сохранения адресов фактических параметров-констант;
выполняется копирование адресов (но не значений!) фактических параметров в выделенную для них память;
Во время работы процедуры/функции:
запрещено изменение значений формальных параметров-констант;
использовать значения формальных параметров разрешено только в качестве исходных данных;
значения формальных параметров, используя скопированные адреса, выбираются непосредственно из памяти фактических параметров.
При окончании процедуры/функции:
влияние вызываемой процедуры/функции на вызывающую через параметры-константы отсутствует;
память, выделенная для работы процедуры/функции, очищается.
В качестве фактического параметра-константы могут использоваться как переменные, так и константы различных типов. Не допускаются только файловые типы и типы, опирающиеся на файловый. Кроме того, запрещается выполнять присваивание формальным параметрам-константам и формальные параметры-константы не могут передаваться в качестве фактических параметров другим процедурам/функциям.
Параметры-константы целесообразно использовать в тех случаях, когда требуется передавать структуры данных, занимающие большой размер памяти, но изменять исходные значения параметров с алгоритмической точки зрения недопустимо. В результате экономно используется оперативная память и одновременно гарантируется целостность исходных данных.
На следующем шаге мы рассмотрим безтиповые параметры.
Параметры-значения
На этом шаге мы рассмотрим использование параметров-значений.
При описании параметров-значений в заголовках процедур/функций перед идентификаторами таких параметров дополнительные ключевые слова не ставятся.
Заголовок процедуры с описанными параметрами-значениями имеет следующий вид:
procedure MyProc (Par1, Par2 : Type1; РаrЗ, Раr4 : Туре2);
Приведем упрощенную схему и описание механизма работы параметров-значений:
Рис.1. Механизм передачи параметров
При вызове процедуры/функции:
выполняется выделение памяти под формальные параметры-значения и локальные данные соответственно их типу;
выполняется копирование значений фактических параметров в память, выделенную для формальных параметров.
Во время работы процедуры/функции:
никаких ограничений на использование параметров данного вида не накладывается;
изменение значений формальных параметров не оказывает никакого влияния на содержимое ячеек памяти фактических параметров.
При окончании процедуры/функции:
память, выделенная под формальные параметры-значения и локальные данные, очищается;
новые значения формальных параметров, полученные в процессе работы процедуры, теряются вместе с очисткой памяти.
В качестве фактического параметра-значения могут использоваться как переменные, так и константы различных типов. Не допускаются только файловые типы и типы, опирающиеся на файловый.
На следующем шаге мы рассмотрим передачу параметров по ссылке.
Параметры-переменные
На этом шаге мы рассмотрим передачу параметров по ссылке.
При описании параметров-переменных, то есть в случае передачи параметров по ссылке, в заголовках процедур/функций перед идентификаторами таких параметров ставится ключевое слово var.
Заголовок процедуры с описанными параметрами-переменными имеет следующий вид:
procedure MyProc (var Par1, Par2 : Type1; var РаrЗ, Раr4 : Туре2);
Приведем упрощенную схему и описание механизма работы параметров-переменных:
Рис.1. Механизм передачи параметров
При вызове процедуры/функции:
выполняется выделение памяти только для локальных данных и для сохранения адресов фактических параметров-переменных;
выполняется копирование адресов (но не значений!) фактических параметров в выделенную для них память;
использовать в качестве фактических параметров константы запрещено.
Во время работы процедуры/функции:
никаких ограничений на использование параметров данного вида не накладывается;
изменение значений формальных параметров, используя скопированные адреса, выполняется непосредственно на ячейках памяти соответствующих фактических параметров.
При окончании процедуры/функции:
специального копирования результата не требуется, поскольку все действия с формальными параметрами выполнялись непосредственно над ячейками памяти фактических параметров;
память, выделенная для работы процедуры/функции, очищается.
В качестве фактического параметра-переменной могут использоваться переменные любых типов, включая файловые и опирающиеся на файловый, но зато использование констант не допускается.
.
Открытые параметры-массивы для передачи параметров одинакового типа
На этом шаге мы рассмотрим использование параметров-массивов для передачи параметров.
В заголовке процедуры открытые параметры-массивы описываются следующим образом:
procedure OpenVector (Vector: array of TypeVector) ;
Открытый параметр-массив может быть параметром-значением, параметром-константой или параметром-переменной.
Передаваемые фактические параметры должны по типу совпадать с описанным типом TypeVector, но по размерности они могут быть различными: как простой переменной типа TypeVector, так и массивом любой мерности.
Гибкость при передаче массивов различной размерности получена за счет единообразия представления этих массивов как формальных параметров. Все формальные открытые параметры-массивы в рамках процедуры автоматически описываются как массивы с нулевой базой (нулевой нижней границей) указанного в заголовке типа:
array [ 0..N-1] of TypeVector;
где N - число элементов в фактическом параметре.
Другими словами, истинный диапазон изменения индекса фактического параметра-массива отображается в диапазон изменения индекса от 0 до N - 1.
Для определения характеристик переданного фактического параметра-массива в теле процедуры используются стандартная функция Low, которая всегда возвращает 0, стандартная функция High, которая возвращает индекс последнего элемента в формальном параметре-массиве, и функция SizeOf, которая возвращает размер фактического параметра-массива.
С помощью открытых параметров-массивов можно решать проблемы, аналогичные рассмотренной в предыдущем примере. Однако открытые параметры-массивы обладают меньшей гибкостью, чем бестиповые параметры, поскольку в данном случае в качестве фактических параметров могут быть массивы только одного типа.
Для сравнения с использованием бестиповых параметров, приведем для открытых параметров-массивов такую же процедуру, как в предыдущем примере:
const
m = 10;
n = 15;
type
TVector1 = array [1..m] of Byte;
TVector2 = array [1..n] of Byte;
var
Vector1 : TVector1;
Vector2 : TVector2;
{--------------------------------------------------}
procedure SortVector ( var Vector : array of Byte ) ;
var
Min : Byte;
Imin : Word;
i, j : Word;
begin
for i := 0 to High (Vector) do
begin
Min := Vector[i];
Imin := i;
for j := i+1 to High (Vector) do
if Vector [j] < Min then
begin
Min := Vector[j];
Imin := j
end;
Vector[Imin] := Vector[i];
end;
end;
{--------------------------------------------------}
begin
. . . . .
SortVector (Vector1);
SortVector (Vector2);
. . . . .
end.
Процедурные типы
В стандартном Паскале процедуры и функции рассматриваются только как части программы, которые можно выполнять с помощью вы- зова процедуры или функции. В Borland Pascal процедуры и функции трактуются гораздо шире: здесь допускается интерпретация процедур и функций, как объектов, которые можно присваивать переменным и передавать в качестве параметров. Такие действия можно выполнять с помощью процедурных типов. В описании процедурного типа задаются параметры, а для функ- ции - результат функции. процедурный тип ¦ ¦ ---------- LT>¦procedure+-T-----------------------------------------------> ¦ L---------- ¦ ----------------------------- ^ ^ ¦ L->¦список формальных параметров+-- ¦ -- L----------------------------- L- ¦ --------- ---- ----------¦ L>¦function+T-------------------------------->¦ : +>¦результат+- L---------¦ -----------------------------^ L---- L---------- L>¦список формальных параметров+- L----------------------------- Характерно, что синтаксис записи процедурного типа в точнос- ти совпадает с записью заголовка процедуры или функции, только опускается идентификатор после ключевого слова procedure или function. Приведем некоторые примеры описаний процедурного типа: type Proc = procedure; SwapProc = procedure(var X, Y: Integer); StrProc = procedure(S: String); MathFunc = function(X: Real): Real; DeviceFunc = function(var F: text): Integer; MaxFunc = function(A, B: Real; F: MathFunc): Real; Имена параметров в описании процедурного типа играют чисто декоративную роль - на смысл описание они не влияют. Borland Pascal не позволяет описывать функции, которые возв- ращают значения процедурного типа. Результат функции должен быть строкового, вещественного, целого, символьного, булевского типа, указателем или иметь перечислимый тип, определенный пользовате- лем.Процедурные значения
Переменной процедурного типа можно присвоить процедурное значение. Процедурные значения могут быть следующими: * значениями nil; * ссылкой на переменную процедурного типа; * идентификатором процедуры или функции. В контексте процедурных значений описание процедуры или функции можно рассматривать как специальный вид описаний конс- тант, когда значением константы является процедура или функция. Рассмотрим, например, следующее описание: var P: SwapProc; F: MathFunc; procedure Swap(var A, B: Integer); far; var Temp: Integer; begin Temp := A; A := B; B := Temp; end; function Tan(Angle: Real); far; begin Tan := Sin(Angle) / Cos(Angle); end; Переменным P и F можно присвоить значения следующим образом: P := Swap; F := Tan; а вызовы с помощью P и F можно выполнить так: P(I, J); { эквивалентно Swap(I, J) } X := F(X); { эквивалентно X := Tan(X) } Использование процедурных переменных, которым в операторе вызова процедуры или функции присваивается значение nil, приводит к ошибке. Значение nil предназначено для указания того, что про- цедурная переменная не присвоена, и, так где процедурная перемен- ная может получить значение nil, участвующие в этой процедурной переменной вызовы процедур и функций следует подвергать проверке: if @P <> nil then P(I, J); Обратите внимание на использование операции @ для указания того, что P проверяется, а не вызывается.Модули.
Модули предназначены для поддержки принципов модульного программирования при разработке программ, основным из которых является принцип скрытия информации (information hiding). Согласно этому принципу, взаимовлияние логически независимых фрагментов программы должно быть сведено к минимуму. Принцип скрытия информации, поддерживаемый модулями, позволяет создавать надежно работающие и легко модифицируемые программы. В языке Турбо Паскаль модули используются преимущественно для создания библиотек процедур, функций и объектов, которые затем могут использоваться в программах, разрабатываемых пользователем.
Используя модули, важно правильно указывать их имена.
При включении стандартных модулей достаточно корректно записать их идентификаторы в предложении uses.
При разработке собственных модулей необходимо помнить некоторые особенности:
не допускается одновременное использование модулей с одинаковыми именами;
идентификатор модуля, указанный в заголовке (unit), должен совпадать с именами файлов, содержащих исходный (.pas) и объектный (.tpu, .tpp, .tpw) коды;
если идентификатор модуля длиннее восьми символов, то он должен совпадать с именами файлов по первым восьми символам.
Рассмотрит общую структуру модуля.
Unit имямодуля;{ Интерфейсный отдел }interface{ Описывается взаимодействие модуля с другими модулями и основной программой }{ Список импорта интерфейсного раздела }uses{ Через запятую перечисляются имена модулей, информация которых должна быть доступна в данном модуле}{ Список экспорта интерфесного раздела}consttypevarprocedurefunction{ Список всех типов переменных, которые определены в данном модуле, но использовать которые разрешено во всех других модулях и программах, в которых есть описание данного модуля в uses}{ Раздел реализаций }implementation{ Внутренняя часть описаний данного модуля}{ Список импорта раздела реализаций }uses{ Через запятые перечисляются имена модулей, информация интерфейсных частей которых должна быть доступна в данном модуле}{ Подразделы внутренних для модуля описаний }labelconsttypevarprocedurefunction{ Описываются все типы переменных, которые описывают алгоритмические действия, выполняемые данным модулем. Эти описания недоступны ни одному другому модулю}{ Раздел инициализаций}begin{ Указываются операторы начальных установок, необходимых для запуска корректной работы модуля }end.Традиционные правила сферы действия глобальных и локальных переменных для модулей не работают. Если возникает необходимость создать общие описания для нескольких модулей, то это можно сделать только созданием модуля глобальных объявлений.
Приведем пример модуля, содержащего описания процедуры и функции, рассмотренных ранее.
Unit primer;Interface Procedure line(n:integer; c:char); Function factor(n:integer): integer;ImplementationUses crt;Procedure line(n:integer; c:char); var i:integer;begin for i:=1 to n do write( c); writeln;end;Function factor(n:integer): integer; var i, f: integer;begin f:=1; for i:=2 to n do f:=f*i; factor:=f;end; end.Приемочные тесты (ранее их также называли Функциональные) пишутся на основе User Story. Они рассматривают систему как черный ящик. Заказчик ответственен за проверку корректности функциональных тестов. Эти тесты используются для проверки работоспособности системы перед выпуском ее в производство. Функциональные тесты автоматизируются так, чтобы имелась возможность их часто запускать. Результат сообщается команде и команда отвечает за планирование исправлений функциональных тестов.
Принципы и методы структурного программирования. Восходящее и нисходящее проектирование
На этом шаге мы рассмотрим методы восходящего и нисходящего проектирования.
Другой метод улучшения качества программирования заключается в применении нисходящего проектирования (Top-Down Programming - программирование "сверху вниз").
В методе нисходящего проектирования Вы вначале пишете основную программу, используя средства вызова подпрограмм, причем в качестве подпрограмм вначале Вы вводите "заглушки" вида:
Вызвали подпрограмму номер ... .
Затем, будучи уверенным в правильности логического построения основной программы, Вы детально "расписываете" каждую подпрограмму, вызывая по мере необходимости подпрограммы более низкого уровня. Этот последовательный процесс продолжается, пока программа не будет завершена и проверена.
При другом методе - восходящем проектировании (программировании "снизу вверх") - Вы вначале пишете подпрограммы нижнего уровня и тщательно их тестируете и отлаживаете. Далее Вы добавляете подпрограммы более высокого уровня, которые вызывают подпрограммы нижнего уровня, и так до тех пор, пока Вы не достигнете программы самого верхнего уровня. Метод проектирования "снизу вверх" пригоден при наличии больших библиотек стандартных подпрограмм.
Учтите, что иногда лучшим является гибрид двух методов. Однако в обоих случаях каждая подпрограмма должна быть небольшой, так чтобы можно было охватить одним взглядом всю ее логику (для персональных компьютеров желательно, чтобы и основная программа, и подпрограммы целиком помещались в пределах 20-30 строк экрана дисплея!)
Всякий велосипедист хорошо знает, что ехать сверху вниз быстрее и удобнее, чем снизу вверх. В программировании дело обстоит примерно так же: "сверху вниз" писать программы удобнее потому, что при таком методе мы точно знаем, какие подпрограммы описывать.
Но есть у этого метода и недостаток: на верхнем уровне не всегда видно, куда спускаться, то есть как разделить решение задачи на такие части, каждую из которых было бы легко описать отдельной процедурой. У опытных программистов вырабатывается своеобразное чутье: они сразу видят, какие нужны процедуры, а новичкам иногда приходится туго.
Метод "снизу вверх", хотя и требует большого труда, бывает очень полезен на первых порах. Пусть даже половину составленных Вами подпрограмм придется потом "выбросить", но зато Вы хорошо почувствуете, какие подпрограммы для исходной задачи необходимы. Да и отлаживать каждую написанную подпрограмму можно сразу: ведь все, что "под ней", уже описано (а обычно и отлажено). Словом, любишь кататься "сверху вниз" - люби и саночки возить (в обратном направлении). Опытные программисты иногда применяют метод "снизу вверх" для того, чтобы заранее заготовить для новой задачи набор подпрограмм, которые могут понадобиться в различных случаях. Так что "возить саночки" приходится не только новичкам!
Описание констант
Раздел описания CONST позволяет ввести в программу идентификаторы, являющиеся синонимами (представителями) определенных значений. Описание константы представляет собой пару "идентификатор - значение", разделителем пары служит символ "=".
Синтаксис описания констант:
CONST
ИмяКонстанты1 = Значение1;
ИмяКонстанты2 = Значение2;
или
ИмяКонстанты = ЗначениеВыражения;
Примеры описания констант:
CONST
Min = 0; { константа - целое число }
Max = 100;{ константа - целое число }
e = 2.7; { константа - вещественное число }
SpecChar = '\'; {константа - символ }
HelpStr = 'Нажмите клавишу F1'; {константа - строка }
OK = True; { логическая константа "истина" }
Обратите внимание, что тип константы не указывается никоим образом. Он определяется автоматически при анализе значения константы.
Турбо Паскаль дает возможность определять константы как значения выражений из чисел, некоторых арифметических функций языка и ранее определенных констант. Так приведенный выше список констант можно продолжить
CONST
Interval = Max - Min + 1;
e2 = e*e;
BigHelpStr = HelpStr + ' для подсказки';
В выражениях могут использоваться все математические операции (+, -, /, *, div, mod), логические операции (not, or, and, xor) и операции отношения.
Описанные ранее константы можно использовать для построения индексов массивов, например:
CONST
N = 100;
VAR
V : array[1..N] of Byte;
Описание переменных
Переменные вводятся в программу для хранения и передачи данных внутри нее. Все переменные, которые предполагается использовать в программе должны, прежде всего, быть определены в разделе описания переменных. Описание переменных начинается со служебного слова VAR, вслед за которым располагается последовательность самих определений переменных. Определение переменной заключается в указании ее имени (идентификатора) и типа. Имя и тип разделяются двоеточием. После объявления и описания переменной должен стоять символ ";". Концом блока описания будет начало какого-либо другого блока программы или описание процедур и функций.
Синтаксис описания переменных:
VAR
ИмяПеременной1 : ИмяТипа1;
ИмяПеременной2 : ИмяТипа2;
. . .
ИмяПеременнойN : КонструкцияТипа;
или
VAR
ИмяПеременной1,
ИмяПеременной2,
ИмяПеременной3 : ИмяТипа;
Имя типа может быть именем стандартного типа языка или введенного программистом в предшествующем блоке описания типов TYPE.
Однотипные переменные могут перечисляться через запятую перед объявлением их типа.
Примеры описания переменных:
VAR
X : Real; { вещественная переменная }
i, j, k : Integer; { три целочисленных переменных }
T : MyType; { переменная определенного ранее типа MyType }
D : 1..10; { целочисленная переменная ограниченного типа }
M : array[1..5] of Byte; { переменная типа "массив" }
Data : RECORD
X, Y : Byte;
TextStr : String { переменная тпиа "запись" }
END;