Подпрограммы_часть 1

advertisement
Подпрограммы
Подпрограмма — это абстрактное действие, определяемое программистом. Подпрограммы
являются теми базовыми блоками, из которых строится большинствопрограмм, и почти в каждом языке
можно найти средства для их объявления и вызова. Возможны два взгляда на подпрограммы. На уровне
проектирования программы она интересна тем, что представляет абстрактное действие,
определенноепрограммистом, — в противоположность элементарным операциям и
операторам,встроенным в язык. На уровне разработки языка нас интересует разработка и реализация
общих средств объявления и вызова подпрограммы. Хотя эти два взглядаи пересекаются, удобнее
рассматривать их отдельно.
Разновидности подпрограмм
Подпрограмма — программный сегмент многократного применения, задает определяемое
программистом действие.
Различают две формы подпрограмм:
‰. функции — они расширяют встроенные в язык операции;
‰. процедуры — они расширяют встроенные в язык операторы.
Для использования подпрограммы ее необходимо вызвать (рис.1).
Рис. 1. Вызовы функций и процедур
Вызовы функций записываются внутри выражений. Например, вызов функциивсегда
помещается в правую часть оператора присваивания:
у := r∗sin (angle) + … ; (∗здесь вызывается функция для вычисления синуса ∗)
Вызов процедуры рассматривают как атомарный оператор:
read (x);
Вызов любой подпрограммы записывается в префиксной нотации:
<имя_подпрограммы> (<фактические параметры>);
Параметры в операторе вызова подпрограммы называют фактическими.
Скобки вокруг параметров — это синтаксический признак вызова подпрограммы. Иногда
необходимо указывать скобки в вызове подпрограммы, даже еслипараметров нет.
Оператор
begin while EOLN do readln; read (ch) end;
содержит три вызова:
1. EOLN — вызов функции без параметров (проверяет наличие признака концастроки при
вводе);
2. readln — вызов процедуры без параметров (пропускает все оставшиеся элементытекущей
строки ввода);
3. read (ch) — вызов процедуры с одним параметром ch.
1
Фактический параметр — это объект данных, совместно используемый вызывающей
программой и вызываемой подпрограммой. Различают три разновидностифактического параметра:
‰. локальный объект данных, принадлежащий вызывающей программе;
‰. нелокальный объект данных, видимый из вызывающей программы;
‰. результат вычисления функции, вызванной из вызывающей программы,
которыйвозвращается в точку вызова.
В точке вызова подпрограммы фактический параметр может представлятьсявыражением
фактического параметра. Это обычное выражение, допустимоев языке программирования. Например,
подпрограмма Subprog может бытьвызвана с любой из разновидностей выражения фактического
параметра, приведенной в табл. 1.
Таблица 1. Разновидности фактического параметра подпрограммы
Если при вызове подпрограммы ей передается параметр в форме выражения, тово время вызова,
но перед входом в подпрограмму, вычисляется значение этого выражения. Объект данных, полученный
в итоге вычисления выражений, становитсяфактическим параметром, передаваемым подпрограмме.
Фактические параметры несут в себе исходные данные, которые фактическиобрабатываются
подпрограммой. Этим и объясняется их название.
Еще раз повторим: функция играет роль операции, а процедура — роль оператора. Они похожи,
хотя функция возвращает результат, а процедура — нет. Неудивительно, что в языке С обе
разновидности подпрограмм вызываются с помощьюфункций, а в языке Модула 2 — с помощью
процедур.
Объявление подпрограммы
Объявление подпрограммы описывает ее содержание и включает следующиеэлементы:
‰. имя;
‰. формальные параметры (обозначают места для подстановки фактических параметров);
‰. тип результата (отсутствует в случае процедуры);
‰. тело (объявления локальных переменных и операторы).
Обсудим функцию вычисления квадрата: square(2) ⇒4; square(4) ⇒16.
При использовании имени х для обозначения места подстановки фактическогопараметра
вычисление в функции можно записать в виде:
square(x) = x∗x;
Формальный параметр х обычно называется меткой-заполнителем (placeholder).
2
Таким названием подчеркивают обязанность формального параметра: чаще всего
он принимает значение фактического параметра, существующего в точке вызова.
Объявление подпрограммы 323
Формальный параметр — это разновидность локального объекта данных в подпрограмме. Имя
формального параметра представляет собой простой идентификатор,а в его объявлении обычно задают
тип и другие атрибуты, как и в случае объявленийобычных локальных переменных. Например, в
заголовке процедуры на языке C
voidSubprog(int А; char В)
задается два формальных параметра A и B и объявляется, что один из них принадлежит к целому типу, а
другой — к символьному. В общем случае объявлениеформального параметра несколько отличается от
объявления переменной.
В зависимости от выбранного способа передачи формальный параметр можетстановиться
псевдонимом фактического параметра, перенимать его имя (собственное имя формального параметра
заменяется именем фактического параметра).
В классическом варианте формальный параметр считается хранителем значенияфактического
параметра.
Пример объявления функции square (в синтаксисе языка Pascal) приведен на
рис. 2.
Рис. 2. Объявление функции square
Элементами объявления функции square являются:
‰. имя функции — square;
‰. формальный параметр — x типа integer;
‰. тип результата — integer;
‰. тело функции — возврат значения х ∗х.
Первые три элемента входят в состав заголовка функции. В языке Pascal заголовок функции
начинается с ключевого слова function, а заголовок процедуры — сослова procedure. Заголовок часто
называют спецификацией, а тело — реализациейфункции.
Ту часть заголовка, которая задает типы формальных параметров, порядок ихследования, а
также тип данных результата, принято называть сигнатурой (илипрототипом) функции.
На языке С спецификация какой-нибудь функции записывается в виде:
floatFun(float A, intB)
который определяет сигнатуру функции как
Fun : вещественное × целое → вещественное
Процедура на языке С синтаксически оформляется как функция, которая невозвращает
результат, ее заголовок должен начинаться с ключевого слова void:
voidProc(float A, intB, float ∗C, int∗D)
Здесь формальные параметры C и D определены как указатели.
Эта же спецификация в языке Ada более наглядно выражает возможные различия в смысле
параметров:
procedureProc (A: in float; B: in integer; C: in out float; D: out boolean)
3
Данный заголовок определяет процедуру со следующей сигнатурой:
Proc : вещественное1 × целое × вещественное2 → вещественное3 × булево значение
Ключевые слова in, out и inout указывают на направление передачи информации между
формальным и фактическим параметром (in — передача из фактического в формальный параметр, out
— из формального в фактический параметр,inout — двунаправленная передача).
Иначе говоря, через параметры A и B информация поступает в процедуру, черезпараметр C
возможна двухсторонняя пересылка, а через параметр D информациятолько выводится из процедуры.
Нетрудно догадаться, что здесь ≪обойден запрет≫на возврат процедурой результата: фактически
результаты работы процедуры поступают во внешнюю среду через параметры C и D (!).
При вызове функции выполняются операторы ее тела. В большинстве языковфункция
возвращает результат с помощью оператора return:
return х ∗х;
В языке Pascal для возвращения результата используется необычный синтаксис — возвращаемая
величина присваивается имени функции. При этом в операторетела имя функции рассматривается как
переменная.
Заметим, что при попытке выяснить, какую математическую функцию вычисляетподпрограмма,
возможны некоторые проблемы:
‰. в подпрограмме могут использоваться неявные параметры в виде ссылок нанелокальные
переменные;
‰. в итоге работы подпрограммы могут появиться неявные результаты (побочныеэффекты).
Они возвращаются в виде изменения значений нелокальных переменных или в виде изменений
значений ее параметров (категории inout);
‰. для некоторых значений параметров подпрограмма может быть не определена.
В подобных ситуациях она не выполняет свои обычные вычисления, а завершается аварийно.
Работа подпрограммы определяется ее телом, которое состоит из объявленийлокальных данных,
описывающих используемые структуры данных, и операторов,задающих выполняемые действия.
Объявления и операторы тела скрыты от пользователя. Пользователь может лишь вызвать
подпрограмму с конкретным наборомфактических параметров и получить вычисленные результаты.
Синтаксис языка Cи С++ для тела подпрограммы достаточно прост:
floatFun (floatА, intВ)
// спецификация подпрограммы
{floatM(10); intС;
// - объявления локальных объектов данных
...
// последовательность операторов,
}
// определяющая действия подпрограммы
В некоторых языках (например, в языках Pascal и Ada, но не в C) в тело подпрограммы могут
входить объявления других подпрограмм, работающих тольков пределах объемлющей их
подпрограммы. Эти локальные подпрограммы такжескрыты, так что извне к ним обратиться нельзя.
Каждый вызов подпрограммы должен передать ей параметры правильного типа,в соответствии с
ее спецификацией. Кроме того, требуется определить тип результата, возвращаемого подпрограммой.
Контроль типов для подпрограмм аналогиченконтролю типов для простых операций. Он может
выполняться статически во времякомпиляции, если для типов фактических параметров и результатов
всех подпрограмм определены объявления. Альтернативой считается динамический контрольтипов,
происходящий в период выполнения программы. Возможно и автоматическое (неявное) приведение
параметра к правильному типу, если подобное действиеразрешено реализацией языка. Специфика
только в том, что в случае подпрограммпрограммист должен явно объявлять информацию о типах
аргументов и результатов,в то время как для простых операций эта информация передается неявным
образом.
Вызов подпрограммы
При вызове подпрограммы создается ее активация. Активации подпрограмм существуют только
в период вычислений. После окончания выполнения подпрограммыактивация разрушается. Если
4
подпрограмма вызывается еще раз, создается новаяактивация. В ходе вычислений по единственному
объявлению подпрограммы можетбыть создано много активаций. Их характеристики определяются
содержаниемобъявления подпрограммы.
При создании активации ей должна быть выделена область памяти, котораяосвобождается при
ее разрушении. Таким образом, период жизни активации —это промежуток времени между создающим
ее вызовом подпрограммы и выходомиз подпрограммы, когда активация разрушается.
В качестве примера рассмотрим объявление функции, написанное на языке С:
floatFun (float A, intB)
{
const min = 10;
#define max 70
floatM [20]; intC;
C = min;
if(C < max) {…}
return(M [C] + 30 ∗A);
}
В нем определены все компоненты, необходимые для активации функции вовремя выполнения:
‰. Заголовок функции предоставляет информацию о памяти, необходимой дляее параметров
(под объект данных типа float и объект данных типа int) и длявычисляемого ею результата (под объект
данных типа float).
‰. Объявления тела определяют область памяти под локальные переменные —массив M и
переменную С.
‰. Рассчитывается память под хранение литералов и именованных констант: min —это
именованная константа со значением 10, max — именованная константасо значением 70, а 70 и 30
являются литералами.
‰. Определяется емкость памяти для исполняемого кода, который генерируетсяпри компиляции
операторов, содержащихся в теле подпрограммы.
В объявлении функции Fun предложение const сообщает компилятору, чтообъект данных min —
это числовая константа со значением 10. Строка же #definemax 70 является макрокомандой
препроцессору, который преобразует каждое имяmax, появившееся в тексте функции, в символ 70.
Следовательно, компилятор языкаC даже не встретится с именем max, так как его везде заменит
числовой литерал 70.
С точки зрения вычислений оба способа задания констант равнозначны, но смыслу
предложений const и #define совершенно разный. Объект данных min обладаетl-значением, чьеrзначение равно 10. У объекта max есть только r-значение, равное 70.
Объявление подпрограммы позволяет еще в период компиляции организоватьпредставление в
памяти всех используемых объектов данных, а также определитьисполняемый код.
Итогом компиляции становится шаблон, используемый для создания активациикаждый раз,
когда вызывается подпрограмма. Для рассматриваемой функции этотшаблон представлен на рис. 3.
5
Рис. 3. Шаблон активации для функции Fun
Для создания конкретной активации функции на основе ее шаблона следует скопировать весь
этот шаблон в новую область памяти. Однако вместо полного копированияцелесообразнее разбить
шаблон на две части, выделив в нем статическую и динамическую составляющие и расширив его за счет
исполняемого кода подпрограммы.
Статическая составляющая называется сегментом кода и состоит из константи выполняемого
кода подпрограммы. Как показано на рис. 4, дополнительносегмент включает в себя две секции:
1) код для создания записи активации;
2) код для удаления записи активации.
Когда вызывается подпрограмма, осуществляется ряд служебных действий,связанных с
созданием записи активации, передачей параметров, формированием ссылок на нелокальные
переменные и другими вспомогательными задачами.Эти действия должны быть произведены до того,
как начнет выполняться код,представляющий операторы тела подпрограммы. Поэтому
вспомогательный кодинициализации компилятор вставляет перед началом кода самой подпрограммы.По
завершении выполнения подпрограммы тоже требуется осуществить ряд вспомогательных действий.
Они связаны с возвращением результатов и освобождениемпамяти, выделенной под запись активации.
Соответствующий код финализациивставляется в конец исполняемого кода подпрограммы.
6
Рис. 4. Сегмент кода для функции Fun
Поскольку сегмент кода не меняется в ходе выполнения любой копии подпрограммы, он может
быть использован повторно и для других активаций.
Динамическая составляющая шаблона называется записью активации и состоит из параметров, результатов функций и локальных данных, а также других
вспомогательных данных, зависящих от реализации языка (временных областей
памяти, адресов возврата из подпрограммы и связей для ссылок на нелокальные
переменные). Структура записи активации также одинакова для всех активаций
данной подпрограммы, но значения данных отличаются. Следовательно, для каждой
активации требуется своя собственная запись активации.
Итоговую структуру кода для поддержки работы подпрограммы иллюстрирует
рис. 13.5.
___________ ______ ____________ ______
Рис. 13.5. Общий сегмент кода и различные записи активации для обеспечениявызовов
подпрограмм
7
В период вычислений для каждой подпрограммы в памяти хранится только одинсегмент кода.
Записи активации динамически создаются при обращении к подпрограмме и уничтожаются каждый раз
при завершении ее выполнения.
Управление памятью при вызове подпрограммы и выходе из нее сводится к выделению под
запись активации блока памяти и его освобождению после завершенияподпрограммы.
Рекурсия — множественные выполненияподпрограммы
Процедура (функция) рекурсивна, если она запускается из собственного тела (этоможет быть
косвенный или прямой вызов). Рекурсия может быть запущена многократно.
Рассмотрим рекурсивную форму вычисления факториала:
functionf (n : integer) : integer;
begin
ifn = 0 then f := 1 else f := n ∗ f(n – 1)
end;
Функция f( ) является рекурсивной, так как f(n) вычисляется с помощьюf(n – 1), f(n – 1) — с
помощью f(n – 2) и так далее до вычисления f(0) = 1.
Последовательность запусков при вызове функции f(3) отобразим графически:
f(3) = 3 ∗f(2) /∗приостановка выполнения f(3) ∗/
f(2) = 2 ∗f(1) /∗приостановка выполнения f(2) ∗/
f(1) = 1 ∗f(0) /∗приостановка выполнения f(1) ∗/
f(0) = 1
f(1) = 1 /∗возобновление выполнения f(1) ∗/
f(2) = 2 /∗возобновление выполнения f(2) ∗/
f(3) = 6 /∗возобновление выполнения f (3) ∗/
В ответ на вызов f(3) тело функции выполняется с n = 3:
if 3 = 0 then … else …
Так как результат сравнения 3 = 0 равен false, начинает вычисляться выражение
3 ∗f(3 – 1) = 3 ∗f(2).
Теперь для выполнения умножения требуется значение f(2), поэтому для вычисления f(2)
рекурсивно вызывается f( ). Аналогично в ходе запуска для f(2)вызывается f(1). Далее, в ходе запуска
f(1) вызывается f(0).
Недостатком рекурсивных подпрограмм для обычных компьютеров являетсясущественное
увеличение расхода памяти, ведь под каждый вызов создается дополнительная активация
подпрограммы. Хотя, конечно, математикам эта формаочень нравится — она соответствует
естественной форме записи вычислений.
Преимущества подпрограмм
Подпрограмма — это ≪черный ящик≫: известно≪что она делает≫, а не ≪как _______она
делает свою работу≫. Это отделение понятия ≪что≫ от понятия ≪как≫, или поведенияот реализации,
позволяет разделить программу на части, каждая из которых можетрассматриваться независимо.
К основным преимуществам подпрограмм следует отнести следующее:
1. Уменьшение сложности программирования (за счет повышения уровня абстракции).
Использование только имени подпрограммы (sort) позволяет абстрагироваться от деталей реализации,
думать в терминах операций, относящихсяк решаемой проблеме.
2. Закрытость реализации — модификация алгоритма внутри подпрограммы невоздействует на
остальную часть программы.
3. Модульность программ — подпрограммы используют для разбиения программына
небольшие куски, которые можно рассматривать отдельно. Поэтому подпрограммы позволяют нам
управлять более крупными программами, чем те, которыемы можем мысленно обозревать.
8
4. Расширение возможностей языков программирования — создание библиотек(стандартных
коллекций полезных подпрограмм). Операции типа + и ∗встраиваются в язык, но математические
функции типа sin( ), log( ) обеспечиваютсябиблиотекой, как и процедуры ввода-вывода.
Методы передачи параметров
Формальные параметры в объявлении подпрограммы — это места для подстановки фактических
параметров. Фактические параметры используют в вызовахподпрограмм. Например, в операторе вызова
F(2) двойка является фактическимпараметром. Рассмотрим формальный параметр х в следующем
объявлении
функции:
intsquare (intx)
{
returnx∗x
};
Вызов square(2) возвращает 4, вычисляя x∗x, где вместо х подставляется 2.Вызов square(3)
возвращает 9, вычисляя то же выражение x∗x с 3 вместо х.
Методы передачи параметров — это способы, которыми параметры передаютсяв
подпрограмму и/или возвращаются из нее.
Сопоставление между фактическими и формальными параметрами в вызовеподпрограммы
называют передачей параметров. Если параметров в подпрограмменесколько, то возникает
необходимость корректного связывания соответствующих пар.
Опишем два способа, которые применяются для установления такого связывания.
Позиционное сопоставление. Соответствие между фактическими и формальными параметрами
устанавливается на основе их позиций в списках фактическихи формальных параметров: два параметра,
которые занимают одинаковые позиции в списках, образуют пару. Таким образом, первый фактический
и первыйформальный параметр составляют пару, затем — вторые параметры из обоихсписков и т. д.
Сопоставление по имени. В языке Ada и в некоторых других языках при вызовеподпрограммы
можно явно указать, какой формальный параметр должен соответствовать данному фактическому
параметру. Например, в языке Ada можно вызватьподпрограмму Sub следующим образом:
Sub(X =>A, Y => 27);
Здесь при вызове подпрограммы Sub фактический параметр A объединяетсяв пару с
формальным параметром X, а фактический параметр 27 — с формальнымпараметром Y.
В языке Python функции тоже можно вызывать подобным способом:
adder (size = the_size,
list = the_list,
sum = the_sum)
Здесь size, list и sum — имена формальных параметров из объявления функции adder.
Главный недостаток сопоставления по имени заключается в том, что пользователь
подпрограммы должен знать имена формальных параметров.
В большинстве языков используется исключительно позиционное сопоставление, поэтому в
наших примерах также применяется этот способ. Обычно количествоформальных и фактических
параметров должно совпадать, чтобы соответствиемежду ними было взаимно однозначным. Тем не
менее в некоторых языках это требование не выполняется, и в этом случае используются специальные
соглашения,интерпретирующие отсутствие или избыток фактических параметров.
В языках Python, Ruby, Fortran 95 (и выше), PHP, С++ и Ada формальные параметры могут
иметь значения по умолчанию. Значение по умолчанию используется,если формальному параметру,
указанному в спецификации подпрограммы, непередается никакого фактического параметра.
Рассмотрим следующий заголовок функции на языке Python:
defsalary(hours, tax_free = 1, hour_rate)
9
В этом языке все объявления функций начинаются с ключевого слова def. Привызове функции
salary фактический параметр, соответствующий формальномупараметру tax_free, можно пропустить. В
этом случае вместо него используетсязначение 1 (значение по умолчанию). В вызовах функций на языке
Python вместопропущенного параметра запятая не ставится. Такая запятая должна указывать напозицию
следующего параметра, а в этом нет никакой необходимости: все последующие фактические параметры
должны сопоставляться по имени, как показанов следующем вызове функции:
my_money = salary(120, hour_rate = 10.0)
В языке С++ сопоставления по имени не предусмотрены, поэтому правила использования
параметров по умолчанию совсем другие. Параметры по умолчаниюдолжны указываться в конце
списка, поскольку предусмотрено только позиционное сопоставление элементов списка. Если параметр
со значением по умолчаниюв вызове пропущен, то все последующие формальные параметры должны
иметьзначения по умолчанию. Заголовок функции salary на языке С++ следует записать иначе:
floatsalary (inthours, floathour_rate, inttax_free = 1)
Видим, что параметры в этой функции перечислены в другом порядке: параметрсо значением по
умолчанию записан последним. Теперь оператор вызова принимаетследующий вид:
my_money = salary(120, 10.0);
Методы в языке C# могут иметь переменное количество параметров, если этопараметры одного
и того же типа. Подобные формальные параметры помеченымодификатором params. В операторе вызова
может быть записан массив или списоквыражений, чьи значения компилятор помещает в массив и
предоставляет вызываемому методу. Обсудимследующийметод:
public void PutList(paramsint[] list) {
foreach(intnext in list) {
Console.WriteLine("Next value {0}", next);
}
}
Если метод PutList размещен в классе TheClass и имеются следующие объявления
TheClasstheObject = new TheClass;
int[] littleList = newint[10] {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};
то метод PutList можно будет вызвать любым из следующих операторов:
theObject.PutList(littleList);
theObject.PutList(5, 47 ∗ a + 1, 114);
Язык Ruby предлагает сложную, но очень гибкую схему конфигурации фактических
параметров. Здесь выделяют три категории фактических параметров, каждаяиз которых обслуживается
соответствующим формальным параметром.
К первой категории относят выражения, чьи значения в виде объектов передаются в обычные
формальные параметры.
Вторую категорию образует группа параметров, которые могут быть упакованы в список пар
≪ключ => значение≫. Этот список помещается в анонимный хеш,ссылка на который передается в
единичный формальный параметр, называемыйхеш-элементом. Данный механизм заменяет
сопоставление по имени, которое непосредственно в Ruby не поддерживается.
Третью категорию представляют фактические параметры-массивы. Элементымассива могут
иметь разные типы. Имя фактического параметра-массива должно предваряться звездочкой, например
∗list. Такому массиву сопоставляетсяспециальный формальный параметр-массив с именем, которое
тоже начинаетсясо звездочки, например ∗p4. Параметр-массив должен быть последним в
спискепараметров метода. При вызове метода в формальный параметр-массив заноситсяссылка на
новый объект класса Array. Все оставшиеся фактические параметрыприсваиваются элементам данного
объекта.
Таким образом, механизм обеспечения переменного количества параметровв Ruby похож на
механизм, реализованный в C#. Поскольку массив в Ruby можетхранить значения разных типов, здесь
нет требования, чтобы фактические параметры, передаваемые через массив, были однотипными.
10
Проиллюстрируем механизм передачи параметров в какую-то функцию Rubyследующим
примером:
list = [1, 2, 3, 4]
deffuncR(p1, p2, p3, ∗p4)
...
end
...
funcR('teacher', growth => 175, weight => 70, age => 27, ∗list)
При выполнении этого оператора, который вызывает подпрограмму funcR,формальные
параметры получат следующие значения:
p1хранит 'teacher'.
p2хранит {growth => 175, weight => 70, age => 27}.
p3 хранит 1.
p4 хранит [2, 3, 4].
Передача параметров в языке Python похожа на схему, реализованную в Ruby.
Язык Lua использует простой механизм для поддержки переменного количествапараметров —
такие параметры представляются многоточием (. . .). Это многоточиерассматривается как массив, или
список значений, которые могут быть присвоенысписку переменных.
К примеру, рассмотрим две следующие функции:
functionadd (. . .)
localsum = 0
fori, next in ipairs{. . .} do
sum = sum + next
end
returnsum
end
Здесь ipairs является итератором для массивов (возвращает индекс и значениеэлементов
массива, по одному элементу за раз). Фигурными скобками {. . .} обозначен массив фактических
параметров.
functionmake (. . .)
locala, b, c = . . .
...
end
Допустим, что оператор вызова функции make имеет вид:
make(5, 9, 2)
В этом случае a, b и c будут инициализированными в функции значениями 5,9 и 2
соответственно.
Параметру ≪три точки≫ не требуется быть единственным — он может появитьсяв конце
списка именованных формальных параметров.
Вернемся к основной теме. Использование различных характеристик параметраприводит к
различным методам передачи параметров. Например, при вызове процедуры Р(А[j]) возможны
следующие варианты:
‰. Передача по значению. Передается значение A[j].
‰. Передача по ссылке. Передается место размещения A[j].
‰. Передача по имени. Передается сам текст A[j].
Параметр, передаваемый по значению, называют параметром-значением.
Параметр, передаваемый по ссылке, называется параметром-ссылкой.
В языке Pascal параметры обычно передаются по значению. Если нужно передать данные по
ссылке, то перед именем формального параметра указывается словоvar. В С используется только
передача по значению. В языке С++ применяют обаварианта.
11
Передача параметров по значению
При передаче по значению формальный параметр (placeholder) х подпрограммы Р(х)получает
значение фактического параметра. Иначе говоря, если параметр передается по значению, то r-значение
фактического параметра передается формальномупараметру вызванной подпрограммы.
При вызове Р(Е) используют следующую схему вычислений:
1. Для формального параметра х выделяется место во временной памяти подпрограммы.
2. Вычисляется значение фактического параметра (выражения Е), оно заноситсяв ячейку
формального параметра х := Е.
3. Связь между формальным и фактическим параметром разрывается.
4. Выполняется тело подпрограммы, в котором используется формальный параметр.
5. Если Р(х) — функция, то в точку вызова возвращается результат.
Следствия:
1. Фактический параметр может быть выражением.
2. С помощью формального параметра нельзя вернуть результат в точку вызова.
Вызов square (Е) имеет следующий эффект.
Передача по значению используется чаще всего. В языках Pascal и С она рассматривается как
основной метод передачи параметров. В этом случае формальныйпараметр содержит собственно
значение фактического параметра (на момент вызова подпрограммы), которое и применяется для
вычислений. Недостаток этогометода: итоги вычислений подпрограммы не отражаются на значениях
фактическихпараметров. Любые изменения значения формального параметра, произошедшиево время
выполнения подпрограммы, теряются, когда подпрограмма завершаетсвое выполнение.
Пример. Рассмотрим процедуру обмена двух переменных swap(x, y), котораядолжна
обеспечивать обмен значениями между х и у.
Эту процедуру будем использовать как индикатор, который ≪высвечивает≫ всеособенности
обсуждаемого метода передачи параметров. Используем передачу позначению:
procedureswap (x, y : T);
varz : T;
begin
z := x; x := y; y := z
end;
Шаги вычислений при вызове swap(a, b) по значению имеют вид:
Видим, что, увы, обмен не состоялся. Несмотря на обмен значениями междуформальными
параметрами х и у, процедура не изменила значения фактическихпараметров а и b. Именно это
обстоятельство и подвигнуло на придумывание дополнительных методов передачи параметров.
Передача параметров по ссылке
Передача по ссылке является, вероятно, наиболее распространенным механизмомпередачи
параметров. При передаче по ссылке формальный параметр превращаетсяв синоним места размещения
фактического параметра. Иными словами, передачаобъекта данных с помощью этого механизма
12
означает, что подпрограмме становитсядоступным указатель на местоположение этого объекта (то есть
его l-значение). Приэтом расположение объекта данных в памяти не изменяется. В начале
выполненияподпрограммы l-значения всех фактических параметров используются для инициализации
локальных областей памяти, отведенных под формальные параметры.
Передача параметров по ссылке осуществляется в два этапа:
1. В вызывающей программе определяются указатели на объекты данных, соответствующие
фактическим параметрам (то есть их l-значения). Список такихуказателей сохраняется в общей области
памяти, доступной также и вызываемойподпрограмме (обычно этой областью служит набор регистров
или программныйстек). Затем управление передается вызываемой подпрограмме.
2. В вызванной подпрограмме список указателей на фактические параметры используется для
вычислений, с целью получения требуемых r-значений этих параметров.
Во время выполнения подпрограммы ссылки на имена фактических
параметроврассматриваются как ссылки на обычные локальные переменные. По окончанииработы
вызванной подпрограммы результаты возвращаются в вызывающую программу также через объекты
данных, соответствующие фактическим параметрам.
Рассмотрим процедуру, в которой применяется и передача по ссылке, и передача по значению:
procedureP ( x : Tx; vary : Ty ) … end;
Здесь для формального параметра x указана передача по значению. Напротив, дляформального
параметра y указана передача по ссылке (об этом говорит служебноеслово var, поставленное перед
именем параметра).
Вызов P(a + b, c) имеет следующий эффект:
ПРИМЕЧАНИЕ
В этой таблице мы использовали обозначения языка С для операций взятияадреса и
разыменования указателя.
Следствия:
1. Фактический параметр-значение может быть выражением, фактический параметр-ссылка
выражением быть не может (так как у выражения нет местарасположения), а должен быть именем
переменной.
2. Место размещения фактического параметра-ссылки вычисляется и передаетсяпо ссылке до
того, как начнет выполняться тело процедуры.
Изменим реализацию процедуры swap — используем для ее параметров толькопередачу по
ссылке:
procedureswap ( varx : integer; vary : integer);
z : integer;
begin
z := x; x := y; y := z;
end;
Рассмотрим пример, где одним из фактических параметров является A[i]и значение iизменяется
в процессе работы процедуры. Передается в процедуруто место размещения, которое элемент A[i] имеет
13
на начало вызова. Так как и х,и у являются параметрами-ссылками, в ходе вычислений по вызову swap(i,
A[i])выполняется следующее.
Таким образом, вычисления в теле процедуры производятся не над формальными, а над
фактическими параметрами.
Кстати, обратите внимание, что адреса фактических параметров зафиксированына момент
вызова процедуры, а после этого не меняются. Именно поэтому значения Iв левой и правой части
оператора i:= A[i] разные.
Фактически здесь указывается: занести в ячейку с именем iсодержимое тойячейки массива A,
индекс которой зафиксирован на момент вызова swap-процедуры.
Если i = 2, A[2] = 99, тогда последняя строка приведет к следующим действиям:
z := 2; i := 99; A[2] := z;
Вывод: обмен значениями iи A[i] происходит.
Эффект передачи параметров по ссылкес помощью указателей языка С
В языке С единственный метод передачи параметров — передача по значению.
Эффект передачи по ссылке можно получить с помощью указателей.
Поскольку операция взятия адреса & в С создает указатель на переменную, тофактическими
параметрами в вызове процедуры swapc(&a, &b) являются адресапеременных а и b.
Тело процедуры swapc() может менять значения а и b, используя косвеннуюадресацию, то есть
разыменование.
Модифицируем процедуру swapc() следующим образом:
voidswapc ( int∗px, int∗py ) {
intz;
z = ∗px; ∗px = ∗py; ∗py = z;
}
В языке С префикс ∗является операцией разыменования указателя. Если ра —это указатель на а,
то ∗ра можно поместить вместо а. Это означает:
‰. в правой части присваивания ∗ра обозначает значение а;
‰. в левой части присваивания ∗ра обозначает место размещения а.
Повторим, что здесь фактические параметры — адреса переменных, записанныхв операторе
вызова процедуры.
Вызов swapc(&a, &b) приводит к следующим действиям:
Вывод: обмен значениями между фактическими параметрами а и b происходит.
14
Достоинством метода передачи по ссылке является высокая эффективностьс точки зрения как
времени, так и памяти. Не требуется копировать значенияфактических параметров, а затем отдельно
обновлять их.
Обсудим недостатки передачи по ссылке:
1. Доступ к обрабатываемым параметрам замедляется, поскольку используетсякосвенная
адресация.
2. Понижается безопасность хранения фактических параметров, поскольку двусторонний канал
связи с ними открыт на все время работы подпрограммы. Могутвозникнуть неумышленные и
ошибочные изменения фактических параметров.
3. Появляется возможность возникновения псевдонимов (алиасов) фактическихпараметров.
Известно, что псевдонимы снижают читабельность и надежностьпрограмм, а также усложняют их
проверку.
Когда возникают псевдонимы? Чем чревато их появление? Рассмотрим этивопросы подробнее.
Если имеется процедура на языке Сс двумя параметрами-ссылками
voidproc(int∗px, int∗py)
то вызов proc(&arr[j], &arr[j]) приводит к тому, что px и py становятся алиасамиэлемента
массива arr[j]. Следовательно, три имени px, py и arr[j] указываютна одну и ту же ячейку памяти.
Последствия могут оказаться непредсказуемыми.
Кроме того, псевдонимы могут быть следствиями противоречий между формальными
параметрами и глобальными переменными, которые становятся видимымив подпрограмме. В качестве
примера приведем следующий фрагмент на языке С:
int∗global;
voidmain() {
...
proc(global);
...
}
voidproc(int∗arg) {
...
}
Видим, чтовнутрипроцедурыproc() именаargиglobalстановятсяпсевдонимами (алиасами).
15
Download