ПРОЦЕДУРЫ И ФУНКЦИИ
В предыдущих главах мы убедились в преимуществах разделения больших программ на более мелкие и удобные в ра- боте модули. Для выполнения такой декомпозиции языки программирования предоставляют много различных средств. Язы- ки, поддерживающие функциональную парадигму, естественным образом требуют разделения программы на отдельные
функции. Языки, поддерживающие объектно-ориентированную парадигму, позволяют создавать программные модули, представляющие отдельные объекты.
В этом разделе мы сосредоточим внимание на методах модульного представления алгоритмов. Этот подход состоит в объединении шагов алгоритма в короткие, простые фрагменты с последующим их применением в качестве абстрактных ин- струментов для достижения желаемой цели. В результате образуется некоторая структура из подпрограмм, каждая из кото- рых кодируется на нашем псевдокоде как отдельная процедура.
Процедуры. Процедура (procedure) – это модуль программы, написанный независимо от других модулей и связанный с ними с помощью процедуры передачи и возврата управления (рис. 5.9). Управление передается процедуре (посредством команды перехода машинного языка) в тот момент, когда возникает необходимость в получении предоставляемых ей услуг, и воз- вращается в модуль вызывающей программы после завершения работы процедуры. Процесс передачи управления часто на- зывают вызовом процедуры. Мы будем называть модуль программы, который обращается к процедуре, вызывающим моду- лем (calling unit) или вызывающей программой.
![]() |
Рис. 5.9. Передача управления после вызова процедуры
В рассматриваемых языках программирования процедуры определяются почти так же, как и в нашем псевдокоде, обсуждав- шемся в главе 4. Определение процедуры начинается с оператора, называемого заголовком процедуры, который, помимо все- го прочего, содержит ее имя. За заголовком следуют операторы, детально описывающие данную процедуру. Во многих от- ношениях процедура – это миниатюрная программа. Она содержит как объявления используемых переменных и констант, так и выполняемые операторы, описывающие шаги алгоритма, для реализации которого эта процедура предназначена.
Как правило, переменные, объявленные в процедуре, являются локальными (local variable). Это означает, что на них можно ссылаться только внутри данной процедуры. Подобный подход исключает возможные недоразумения, которые могут возникнуть, когда две процедуры, написанные независимо друг от друга, используют переменные с одинаковыми именами. Однако иногда требуется, чтобы некоторые данные были доступны всем модулям внутри программы. Переменные, пред- ставляющие собой такие данные, называются глобальными (global variable). Многие языки программирования имеют средст- ва для описания как локальных, так и глобальных переменных.
Событийно-управляемое программное обеспечение. В этой главе рассматриваются случаи, когда процедуры явно вызываются операторами, расположенными в различных местах программы. Однако иногда процедуры должны активи- зироваться неявно, при наступлении какого-то события. Например, в графическом интерфейсе пользователя (GUI) про- цедура, описывающая реакцию программы на щелчок на командной кнопке, вызывается не из другого программного модуля, а активизируется непосредственно в ответ на щелчок мышью на данном элементе. Программное обеспечение, содержащее подобные процедуры, называется событийно-управляемым. Короче говоря, такое программное обеспече- ние состоит из процедур, описывающих реакцию системы на различные события. При функционировании системы эти процедуры бездействуют, пока не наступит требуемое событие; после этого они активизируются, выполняют свою за- дачу и вновь возвращаются в состояние покоя.
Синтаксические конструкции, используемые для вызова процедур из другой части программы, в разных языках не- сколько отличаются. В языке FORTRAN используется оператор CALL. В языках Ada, С, C++, Java и Pascal просто указывает- ся имя процедуры. Таким образом, если GetNames, SortNames и WriteNames – это процедуры для получения, сортиров- ки и печати списка имен (определенного как глобальная переменная), то мы могли бы использовать их при написании при- веденной ниже программы на языке FORTRAN, предназначенной для получения, сортировки и печати списков.
CALL GetNames CALL SortNames CALL WriteNames
В языках Ada, С, C++, Java и Pascal эта же программа выглядит следующим образом:
GetNames;
SortNames;
WriteNames;
Обратите внимание, что в этом случае программа состоит из трех команд, каждая из которых обращается к абстрактной
процедуре. Детали того, как именно каждая процедура выполняет свою задачу, скрыты от главной программы.
Параметры. Обычно не рекомендуется предоставлять информацию для совместного использования с помощью гло- бальных переменных, поскольку при этом трудно определить, какая часть программы использует эти данные. Лучше всего в явном виде определить, какие данные используются в каждом модуле программы. Для этого нужно перечислить данные, которые передаются процедуре оператором вызова. В свою очередь, заголовок процедуры содержит список переменных, которым будут присвоены значения, полученные при вызове процедуры, как и в нашем псевдокоде, описанном в главе 4. Элементы обоих списков называют параметрами (parameters).
При вызове процедуры параметры, перечисленные в вызывающем программном модуле, ставятся в однозначное соот- ветствие параметрам, перечисленным в заголовке процедуры; первый параметр в вызывающем модуле соответствует перво- му параметру в заголовке процедуры и так далее. Затем значения параметров из вызывающего модуля присваиваются соот- ветствующим параметрам процедуры, и процедура выполняется. Таким образом, как и в нашем псевдокоде, параметры, пе- речисленные в заголовке процедуры, указывают, где будут размещены конкретные данные при вызове процедуры. Поэтому такие параметры часто называют формальными (formal parameters), тогда как параметры, перечисленные в вызывающем мо- дуле, именуют фактическими (actual parameters), поскольку они представляют реальные данные.
В некоторых языках программирования передача данных от фактических параметров к формальным осуществляется посредством копирования. При этом процедура может манипулировать лишь предоставленными ей копиями. Говорят, что такие параметры передаются по значению (passed value). Передача данных по значению защищает переменные в вызываю- щем модуле от ошибочного изменения плохо разработанной процедурой. Например, если вызывающий модуль передает процедуре номер социального страхования работника, то крайне нежелательно, чтобы она этот номер изменила.
К сожалению, передача параметров по значению неэффективна, особенно когда параметрами являются большие блоки данных. Более эффективный способ передачи параметров процедуре состоит в предоставлении ей прямого доступа к факти- ческим параметрам посредством указания их адресов. В этом случае говорят, что параметры передаются по ссылке (passed by reference). Напомним, что передача параметров по ссылке позволяет вызываемой процедуре модифицировать данные в вы- зывающем модуле. Такой подход был бы желателен, например, при сортировке списка. И действительно, в этом случае вы- зов процедуры сортировки имел бы результатом переупорядочивание исходного списка.
Например, пусть процедура Demo описана так, как показано ниже:
procedure Demo(Forma1) Formal ← Formal+1;
Кроме того, предположим, что переменной Actual присвоено значение 5, после чего процедура Demo вызывается с помощью следующего оператора:
Demo(Actual)
(Здесь использована синтаксическая конструкция, более характерная для языков программирования, чем для нашего псевдокода, в котором этот оператор имел бы вид "вызвать процедуру Demo с параметром Actual".) В этом слу- чае, если параметры передаются по значению, то изменения, внесенные в переменную Formal при выполнении процедуры Demo, не отразятся на значении переменной Actual (рис. 5.10). Однако если параметры передаются по ссылке, значение переменной Actual увеличится на единицу (рис. 5.11).
В разных языках программирования передача параметров осуществляется различными методами, но в любом случае использование параметров позволяет писать процедуры в абстрактном виде и применять их к конкретным данным в нужный момент.
Функции. Обычно назначение программного модуля состоит в выполнении действия и/или вычислении значения. Когда упор делается на вычислении значения, программный модуль может быть реализован в виде функции. В данном случае тер- мин функция относится к программному модулю, который во всем похож на процедуру, за исключением того, что он воз- вращает в вызывающий модуль не список параметров, а единственное значение, которое называется "значением функции". Значение функции связано с именем функции так же, как значение переменной связано с именем переменной. Отличие за- ключается лишь в усилиях, которые нужно приложить, чтобы получить значение. При ссылке на переменную соответствующее значение извлекается из основной памяти, при использовании функции значение вычисляется путем выполнения инструк- ций, содержащихся в теле функции.
Например, если Total – это переменная, которой присвоена общая стоимость единицы продукции (цена плюс налог на продажу), то стоимость двух таких единиц можно найти, вычислив следующее выражение:
2 * Total
Рис. 5.10. Выполнение процедуры Demo с передачей
параметра по значению:
а – при вызове процедуры ей передается копия переменной; б – процедура манипулирует этой копией; в – когда процедура заканчивает работу, переменная в вызывающем модуле не изменяется
В противоположность этому, если TotalCost – функция, которая вычисляет стоимость единицы продукции на основе ее цены и установленного налога на продажу, то стоимость двух единиц продукции можно найти, вычислив такое выраже- ние:
2 * TotalCost(Price, TaxRate)
Рис. 5.11. Выполнение процедуры Demo с передачей параметра по ссылке:
а – при вызове процедуры формальный параметр становится ссылкой на фактический параметр; б – поэтому изменения, производимые процедурой, выполняются над фактическим параметром; в – следовательно, они сохраняются и после окончания работы процедуры
В первом случае значение переменной Total просто извлекается из памяти и умножается на число 2. Во втором случае
функция TotalCost выполняется для переданных ей на вход значений Price и TaxRate, после чего полученное значение умножается на 2.
Функции определяются почти так же, как процедуры. Различие состоит в том, что заголовок функции обычно начинает- ся с описания типа возвращаемого этой функцией значения, а в теле функции присутствует оператор возврата, операндом которого является возвращаемое значение.
Операторы ввода/вывода. Процедуры и функции расширяют возможности языков программирования. Если язык не предусматривает некоторую операцию в качестве предопределенной языковой конструкции, для выполнения этой задачи можно написать процедуру или функцию, а затем вызвать ее из того программного модуля, где эта операция потребуется. Подобным же образом во многих языках программирования осуществляются и операции ввода/вывода, но за исключением того, что вызываемые процедуры и функции в конечном счете представляют собой подпрограммы, выполняемые операци- онной системой компьютера.
Например, чтобы ввести значение с клавиатуры и присвоить его переменной под именем Value, в языке Pascal необхо- димо использовать оператор
readln(Value);
Для вывода переменной Value на экран дисплея используется другой оператор:
writeln(Value);
Заметим, что синтаксис этих операторов не отличается от вызова процедуры со списком параметров.
Аналогично в языке С для выполнения операций ввода и вывода предназначены функции scanf и printf, соответ- ственно. Эти функции используют параметры как для определения вводимых данных, так и для определения вида этих дан- ных в напечатанном виде. Этот подход называется форматированным вводом и выводом (formatted I/O). Например, чтобы напечатать в отдельной строке значения переменных Value1 и Value2 в десятичном виде, в языке С можно использовать следующий оператор:
printf("%d %d \n", Value1, Value2);
Здесь строка в кавычках определяет формат данных, а остальные параметры задают выводимые значения. Каждая пара символов "%d" определяет позицию, которая должна быть заполнена очередным значением в десятичной нотации, задавае- мым соответствующим параметром. Пара символов "\n" означает, что после вывода значений следует перейти на новую строку. Предположим, что значения переменных Age1 и Age2 равны 16 и 25, соответственно, и что выполняется оператор
printf("The ages are %d and %d. \n", Agel, Age2);
В этом случае на экран дисплея будет выведено следующее сообщение:
The ages are 16 and 25
Поскольку языки C++ и Java являются объектно-ориентированными, они трактуют операции ввода/вывода как передачу данных от объекта к объекту. В частности, в язык C++ встроены готовые объекты с именами cin и cout, предназначенные для представления стандартных устройств ввода (возможно, клавиатуры) и вывода (возможно, экрана дисплея), соответст- венно. Данные, которые вводятся с клавиатуры или выводятся на экран дисплея, передаются этим объектам или поступают от них в виде сообщений. Например, с помощью следующего оператора можно ввести с клавиатуры некоторое значение и присвоить его переменной Value:
cin >> Value;
Для того чтобы вывести значение переменной Value на экран, следует послать его на устройство вывода с помощью сле- дующего оператора:
cout << Value;
1. Чем отличаются локальные переменные от глобальных?
2. Чем отличается процедура от функции?
3. Почему многие языки программирования реализуют операторы ввода/вывода так, будто они представляют собой вы- зов процедуры?
4. Чем отличаются формальные параметры от фактических?
Материалы на данной страницы взяты из открытых источников либо размещены пользователем в соответствии с договором-офертой сайта. Вы можете сообщить о нарушении.