Подпрограммы
При создании программ часто появляется некоторая последовательность команд (инструкций), которую необходимо выполнять в нескольких местах.. Можно эту последовательность переписать несколько раз в нужных местах, но, во первых, это удлинит текст программы и уменьшит ее читабельность (последнее, несомненно, хуже для программиста), но и существенно увеличит вероятность внесения ошибок в программу. Можно попытаться обойти эту проблему с помощью GO TO. Но это потребует дополнительной логики, реализованной с помощью флагов, что опять повышает вероятность ошибок.
В большинстве языков программирования для решения этой проблемы введено понятие подпрограммы (название зависит от языка). Подпрограмма – это «кусок» кода, который вынесен из основной программы и которому дано уникальное имя. Тогда в месте, где необходимо выполнить эту последовательность кода, необходимо просто сослаться на имя подпрограммы. Это называется вызовом подпрограммы (процедуры, функции,…).
Например, мы хотим несколько раз напечатать несколько одинаковых строчек.
print *,’---------------------------‘
print *,’*********************‘
print *,’---------------------------‘
Вместо того, чтобы несколько раз в теле программы набирать (или копировать, что несколько лучше:) эти 3 строки, можно оформить их в виде подпрограммы:
subroutine PrtSomething() ! имя подпрограммы
print *,’---------------------------‘
print
*,’*********************‘
print *,’---------------------------‘
end
Теперь в любом месте программы, где должна производиться соответствующая печать, нужно вызвать эту подпрограмму по ее имени:
call PrtSomething()
Теперь усложним задачу. Нужно выполнить печать значения разных переменных опять-таки в нескольких местах программы.
Важно запомнить одно правило: подпрограмма НИЧЕГО не знает о том, кто и когда ее вызывает! Для нее не существует ничего вне ее кода. Она НИЧЕГО не знает о переменных вне ее. Можно представить ее живущей в тюремной камере без окон. Все, что ей нужно, передается через окошечки в стене, над которыми написано названия формальных параметров. Подпрограммы общается с внешним миров через ИНТЕРФЕЙС. Это правила общения подпрограммы с внешним миров. Для того, чтобы передать какое-то значение в подпрограмму, его кладут в окошечко с названием какого-то формального параметра. Подпрограмма берет это значение по имени формального параметра (надпись над окошком).
Другими словами, чтобы сделать доступными какие-то данные подпрограмме, их надо передать в списке параметров, который стоит сразу за именем подпрограммы в скобках: subroutine PrеValues(a,b,c). Параметры, записанные в заголовке подпрограммы, называются формальными параметрами (аргументами). Для подпрограммы это просто переменные. Параметры, подставленные в месте вызова подпрограммы, называются фактическими параметрами. В качестве фактических параметров могут быть константы, переменные или (неявные) результаты выражений.
subroutine PrеValues(a,b,c) ! имя подпрограммы
!
описание переменных – формальных параметров
integer a,b
real c
!
print *,’a= ‘,a
print *,’b= ‘,b
print
*,’c= ‘,c
return
end
В подпрограмму PrеValues(a,b,c) передаются значения 3-х переменных, которые и печатаются. Формальные параметры – переменные, существующие ТОЛЬКО в подпрограмме. Они ВИДНЫ ТОЛЬКО в подпрограмме. Если вне подпрограммы есть переменные с такими же именами, они не имеют ничего общего!
Вызов этой подпрограммы может выглядеть так:
integer a1/4/,a2/7/
real pi/3.14159/
call PrеValues(a1,a2,pi) !напечатает 4
7 3.14159
call PrеValues(1,2,7.) ! 1 2 7.
call PrеValues(1,2.,7.) ! ERROR!
При первом вызове подпрограммы в переменную (форм. пар-ер) а будет положено значение из переменной a1(4), в b – из a2 (7), в c – из pi (3.14…). Соответственно подпрограмма и распечатает их в таком виде:
a=4
b=7
c=3.14159
Параметры, с которыми вызывается подпрограмма, называются фактическими. a1,a2,pi фактические параметры при первом вызове, 1,2,7. фактические параметры при втором.
Почему неправильный последний пример? Интерфейс подпрограммы предписывает, что подпрограмма ДОЛЖНА вызываться с 3-мя фактическими параметрами, имеющими соответствующий тип. Соответственно она и распределяет память. Если вызвать с фактическими параметрами, имеющими другой тип, значение положится в вызывающей программе в память по одному, а внутри подпрограммы будет взято из той-же памяти по другому. Результат становится полностью неопределен! Отсюда вытекает следующее правило:
Список формальных и фактических параметров должен совпадать по количеству и характеристикам аргументов.
Другими словами, сколько параметров описано в заголовке подпрограммы, столько же должно стоять в операторе вызова (за некоторыми исключениями), тип и длина каждого фактического параметра должны совпадать с типом и длиной соответствующего по порядку формального.
Параметры
могут служить не только для передачи значений внутрь подпрограммы, но и в
обратном направлении. При этом значение переменной–формального
параметра кладется в соответствующее окошко, а вызывающая сторона забирает его
и кладет в переменную – фактический параметр.
subroutine Sum(a,b,c)
real a,b,c
c=a+b
return
end
…
call Sum(1.,3.,s)
В переменную s поместится результат сложения фактических параметров – констант 1 и 3. Важно обратить внимание, что если формальный параметр в подпрограмме является [in] – входящим (вводящим значение в подпрограмму), то в качестве фактического параметра может быть переменная или константа (первые 2 параметра Sum). Если же формальный параметр в подпрограмме является [out], или [in/out] – выходящим (возвращающем значение из подпрограммы), то в качестве фактического параметра может быть только переменная (3 параметр Sum).
Если подпрограмма возвращает только одно значение, как в последнем случае, лучше использовать другой тип подпрограмм – подпрограмму-функцию. Формально он описывается так:
FUNCTION < имя функции>(
[< форм.арг >])
< объявление форм.арг >
< объявление локальных объектов
>
...
< вычисляемые операторы,
присваивание результата >
END [ FUNCTION [ < имя функции > ] ]
Пример:
function Sum_(a,b)
real a,b,Sum_
Sum_=a+b
return
end
…
s=Sum_(1.,3.)
Результат полностью аналогичен пред.
На что надо обратить внимание: имя функции как бы является формальным параметром, имеет тип и ему должно быть присвоено какое-то значение. Имя функции выбрано Sum_, а не Sum, т.к. последнее часто является зарезервированным в разных языках.
Теперь рассмотрим задачу – составить подпрограмму нахождения мин в одномерном массиве. Т.к. поиск мин/макс – частая задача, совершенно логично заключить ее в виде подпрограммы-функции. Теперь об интерфейсе. Подпрограмма должна принимать в качестве входных данных массив. Далее в цикле она будет искать мин. Но подпрограмма не знает размер массива! Следовательно, вторым параметром надо передать подпрограмме кол-во элементов в массиве, которые надо обработать.
program sub_min
implicit none
real a(10),min,GetMin
integer i,n
a=(/(50/i,i=1,5),(i*10,i=1,5)/) !
50.00000 25.00000 16.00000 12.00000 ! 10.00000 10.00000 20.00000 30.00000 ! 40.00000 50.00000
n=10
min=GetMin(a,n)
print *,a
print *,min
end program sub_min
real function GetMin(ar,n)
real ar(*)
integer n
GetMin=ar(n)
do n=n-1,1,-1
if (ar(n)<GetMin)
GetMin=ar(n)
end do
return
end
Тип значения, возвращаемого функцией, должен быть описан. Для этого имя функции появляется в секции описания переменных.
В подпрограмме используется один спорный прием. Для уменьшения кол-ва переменных в качестве переменной цикла используется формальный параметр, через который был передан размер массива. Но если при вызове в качестве фактич. параметра использовалась константа (min=GetMin(a,10)), то программа вызовет AV ошибку обращения к памяти, т.к. внутри подпрограммы попытается положить по адресу константы новое значение. Для надежности лучше такую «оптимизацию» не допускать. Более того, даже если при вызове подпрограммы фактическим параметров являлась переменная, хранящее кол-во элементов массива, то после вызова GetMin ее значение будет равно 0! Пользователь вашей подпрограммы может ничего не знать об этом.
Как это работает: при вызове первым параметром является массив a, вторым – размер массива. Затем происходит переход на выполнение подпрограммы. Значения формальных переменных – ar – является массивом а, n =10.
После выполнения оператора return переход обратно в вызывающую программу. Там вместо подпрограммы уже подставляется число – результат, который и помещается в переменную s.
min=GetMin(a(1:4),n)
Еще одно замечание: внутри подпрограммы можно объявлять любые переменные. Но, как уже говорилось, подпрограмма отделена от остального мира. Поэтому эти переменные существуют только внутри подпрограммы и не видны остальному миру! Кроме того, после выхода из подпрограммы (по оператору return или end) и повторного ее вызова значения локальных переменных внутри подпрограммы не сохраняются. (Этого можно достичь с помощью оператора/атрибута SAVE).
Когда использовать подпрограммы? Есть мнение, что если в какой-то программе больше 10 строк кода, то ее надо разбить на подпрограммы. Общие правила написания структурированных, хорошо читаемых программ – если некоторый участок кода имеет какое-то обособленное смысловое значение – выделяйте его в виде подпрограммы. Это дает несколько преимущества: удобство чтения, уменьшение вероятности внесения ошибок, уменьшения кол-ва переменных, удобство отладки.
Вызов подпрограмм может быть вложенным (рис. 5). Вообще говоря, выполнение начинается с главной программы. Во многих языках она называется MAIN. В Fortran-е – она не имеет названия и не имеет заголовка в отличие от всех остальных подпрограмм.
Более того, подпрограмма subroutine2 может вызвать опять subroutine1:
………………………………
n=1
call subroutine1(n)
………………………………
subroutine subroutine1(n)
integer n
if (n<5) then
n=n+1
call subroutine2(n)
end if
return
end
subroutine subroutine2(n)
integer n
if (n<5) then
n=n+1
call subroutine1(n)
end if
return
end

рис. 5
Сведения из стандарта для справки.
Фортран программа состоит из одной или нескольких программных блоков (units).
Программный блок (ПБ) – это, обычно,
последовательность операторов, определяющих данные и действия, которые
необходимо предпринять для выполнения вычислений. Завершается ПБ оператором END.
ПБ может быть:
Главной программой (main program);
Внешней подпрограммой (external subprograms);
Модулем (module);
Блоком Данных (block data).
Исполняемая
программа
содержит 1 главную программу и,
возможно, любое количество любых ПБ. ПБ могут компилироваться отдельно.
Внешняя
подпрограмма
– это function или subroutine, которая не содержится внутри главной программы,
модуля, или другой подпрограммы. Она определяет алгоритм, который должен быть
выполнен, и может быть вызвана из других ПБ.
Модули и Блоки Данных не исполняемы. (Modules can contain module procedures, though, which are executable.)
Модули содержат определения,
которые могут быть сделаны доступными другим ПБ: определения типов и данных,
определения процедур (module subprograms) и
интерфейсы процедур (procedure interfaces)
Module subprograms могут быть или
functions или subroutines. Они могут быть вызваны другими module subprograms
в модуле, или другими ПБ, которые имеют доступ к модулю.
Блок данных (block data)
определяет начальные значения объектов данных в именованном common blocks. В Fortran 95/90 блок данных может быть заменен модулем.
Главная программа, внешние подпрограммы, и
подпрограммы модулей (module subprograms)
могут содержать внутренние подпрограммы (internal subprograms).
Объект, содержащий внутренние подпрограммы называется хозяином (host). Внутренние подпрограммы
могут быть вызваны только своим хозяином или другими внутренними подпрограммами
в том же хозяине. Внутренние подпрограммы не могут содержать внутри себя другие
внутренние подпрограммы (т.е. не иерархичны).
Последовательность
выполнения
Если программа содержит фортрановскую главную программу, выполнение начинается с
первой исполняемой конструкции главной программы. Выполнение любой программы
заключается в выполнении исполняемых конструкций в области программы. Когда
программы вызвана выполнение начинается
с первой исполняемой конструкции после вызванной точки входа. За исключением
приведенных ниже условий эффект выполнения как если бы исполняемые конструкции
выполняются в том порядке, в котором они записаны в программе до тех пор, пока
не будет выполнен один из операторов STOP, RETURN, or END.
Исключения:
1 – Выполнение оператора
ветвления изменяет последовательность выполнения. Эти операторы явно определяют новую точку исполнения;
2 - CASE, DO, IF, или SELECT TYPE конструкция содержит структуру внутренних
операторов, и выполнение этих конструкций приводит к неявному внутреннему
ветвлению;
3 - END=, ERR=, and EOR=
спецификаторы могут приводить к ветвлению;
4 Альтернативный Return
может приводить к ветвлению.
Внутренняя подпрограмма может
находиться до оператора END. Последовательность выполнения исключает все
подобные определения.
Нормальное прекращение
выполнения программы происходит если будет выполнен или оператор STOP или
оператор конца программы.
Встроенные функции
| НА ГЛАВНУЮ | ДАЛЕЕ |