Процедурный параметр

редактировать

В вычислениях процедурный параметр является параметр процедуры процедуры, которая сама по себе является процедурой.

Эта концепция представляет собой чрезвычайно мощный и универсальный инструмент программирования, поскольку он позволяет программистам изменять определенные шаги библиотечной процедуры произвольно сложными способами, не имея необходимости понимать или измените код этой процедуры.

Этот инструмент особенно эффективен и удобен для языков, которые поддерживают определения локальных функций, таких как Pascal и современный диалект GNU из С. Тем более, когда доступны закрытие функций. Такие же (и другие) функциональные возможности предоставляются объектами в объектно-ориентированных языках программирования, но по значительно более высокой цене.

Процедурные параметры в некоторой степени связаны с концепциями функции первого класса и анонимной функции, но отличаются от них. Эти две концепции больше связаны с тем, как определяются функции, а не с тем, как они используются.

Содержание
  • 1 Основная концепция
    • 1.1 Синтаксические детали
    • 1.2 Область действия
  • 2 Пример: Общая сортировка вставкой
    • 2.1 Сортировка чисел с плавающей запятой
    • 2.2 Сортировка строк матрицы
    • 2.3 Процедура векторной сортировки
  • 3 Пример: объединение двух последовательностей
    • 3.1 Объединение связанных списков
    • 3.2 Объединение векторов
  • 4 Пример: Определенный интеграл
    • 4.1 Интегрирование по интервалу
    • 4.2 Интегрирование по диск
  • 5 История
  • 6 См. также
Базовая концепция

На большинстве языков, которые предоставляют эту функцию, процедурный параметр f подпрограммы P может быть вызван внутри тела P, как если бы это была обычная процедура:

процедура P (f): return f (6,3) * f (2,1)

При вызове подпрограммы P, нужно дать ему один аргумент, который должен быть некоторой ранее определенной функцией, совместимой с тем, как P использует свой параметр f. Например, если мы определим

процедуру plus (x, y): return x + y

, тогда мы можем вызвать P (plus), и результат будет плюс (6,3) * плюс (2,1) = (6 + 3) * (2 + 1) = 27. С другой стороны, если мы определим

процедуру quot (u, v): return u / v

, тогда вызов P (quot) вернет quot (6,3) * quot (2,1) = (6/3) * (2/1) = 4. Наконец, если мы определим

процедуру evil (z) return z + 100

, тогда вызов P (evil) не будет иметь особого смысла и может быть помеченным как ошибка.

Синтаксические детали

Некоторые языки программирования, в которых есть эта функция, могут разрешать или требовать полное объявление типа для каждого процедурного параметра f, включая количество и тип его аргументов, а также тип его результата, если есть. Например, на языке программирования C приведенный выше пример можно записать как

int P (int (* f) (int a, int b)) {return f (6,3) * f (2,1); }

В принципе, фактическая функция actf, которая передается в качестве аргумента при вызове P, должна быть совместима по типу с объявленным типом параметра процедуры f. Обычно это означает, что actf и f должны возвращать результат одного и того же типа, должны иметь одинаковое количество аргументов и соответствующие аргументы должны иметь один и тот же тип. Имена аргументов не обязательно должны быть одинаковыми, как показано приведенными выше примерами плюсов и кавычек. Однако некоторые языки программирования могут быть более ограничительными или более либеральными в этом отношении.

Область действия

В языках, допускающих процедурные параметры, правила области действия обычно определяются таким образом, что процедурные параметры выполняются в своей собственной области. Точнее, предположим, что функция actf передается как аргумент P, как ее процедурный параметр f; и затем f вызывается из тела P. Во время выполнения actf он видит среду своего определения.

Реализация этих правил области видимости нетривиальна. К моменту окончательного выполнения actf запись активации , где находятся ее переменные среды, может быть сколь угодно глубоко в стеке. Это так называемая проблема , направленная вниз..

Пример: Общая сортировка вставкой

Идея процедурного параметра лучше всего поясняется на примерах. Типичным приложением является следующая общая реализация алгоритма сортировки вставкой, который принимает два целочисленных параметра a, b и два процедурных параметра prec, swap:

procedure isort (a, b, prec, поменять местами): целое число i, j; i ← a; пока i ≤ ​​b do j ← i; while j>a и prec (j, j − 1) do swap (j, j − 1); j ← j − 1; я ← я + 1;

Эта процедура может использоваться для сортировки элементов с x [a] по x [b] некоторого массива x произвольного типа в указанном пользователем порядке. Параметры Prec и swap должны быть двумя функциями, определенными с помощью, обе принимают два целых числа r, s между a и b. Функция prec должна возвращать true тогда и только тогда, когда данные, хранящиеся в x [r], должны предшествовать данным, хранящимся в x [s], в порядке, определяемом клиентом. Функция подкачки должна обменивать содержимое x [r] и x [s] и не возвращать никакого результата.

При правильном выборе функций prec и swap одну и ту же процедуру isort можно использовать для переупорядочивания массивов любого типа данных, хранимых на любом носителе и организованных в любую структуру данных, которая обеспечивает индексированный доступ к отдельным элементам массива.. (Обратите внимание, однако, что существуют алгоритмы сортировки, которые намного более эффективны, чем сортировка вставкой для больших массивов.)

Сортировка чисел с плавающей запятой

Например, мы можем сортировать массив z из 20 чисел с плавающей запятой, от z [1] до z [20] в порядке возрастания путем вызова isort (1, 20, zprec, zswap), где функции zprec и zswap определены как

procedure zprec (r, s): return (z [r] 

Сортировка строк матрицы

В другом примере, пусть M будет целыми числами с 10 строками и 20 столбцами, с индексами, начинающимися с 1. Следующий код будет переставьте элементы в каждой строке так, чтобы все четные значения располагались перед всеми нечетными значениями:

integer i procedure eoprec (r, s): return (M [ i, r] mod 2) <(M [i, s] mod 2); процедура eoswap (r, s): целое число t; t ← M [i, r]; M [i, r] ← M [i, s]; M [i, s] ← t; для i от 1 до 10 do isort (1, 20, eoprec, eoswap);

Обратите внимание, что эффекты eoprec и eoswap зависят от номера строки i, но процедуре isort это не обязательно знать.

Процедура векторной сортировки

В следующем примере isort используется для определения процедуры vecsort, которая принимает целое число n и целочисленный вектор v с элементами от v [0] до v [n − 1] и сортирует их в порядке возрастания или убывания, в зависимости от того, является ли третий параметр incr true или false соответственно:

procedure vecsort (n, v, incr) : procedure vprec (r, s): if incr thenreturn v [r] v [s]; процедура vswap (r, s): целое число t; t ← v [r]; v [r] ← v [s]; v [s] ← t isort (0, n − 1, vprec, vswap);

Обратите внимание на использование вложенных определений функций для получения функции vprec, эффект которой зависит от параметра incr, переданного в vecsort. В языках, которые не допускают определения вложенных функций, таких как стандартный C, для получения этого эффекта потребуется довольно сложный и / или небезопасный для потоков код.

.

Пример: объединение двух последовательностей

В следующем примере показано использование процедурных параметров для обработки абстрактных структур данных независимо от их конкретной реализации. Проблема состоит в том, чтобы объединить две упорядоченные последовательности записей в единую отсортированную последовательность, при этом характер записей и критерий упорядочения могут быть выбраны клиентом. Следующая реализация предполагает только то, что на каждую запись можно ссылаться по адресу памяти, и что существует «нулевой адрес» Λ, который не является адресом какой-либо действительной записи. Клиент должен предоставить адреса A, B первых записей в каждой последовательности, а также функции prec, next и append, которые будут описаны позже.

процедура merge (A, B, prec, nextA, appendA, nextB, appendB): адрес ini, fin, t ini ← Λ; fin ← Λ, а A ≠ Λ или B ≠ Λ doifB = Λ или (A ≠ Λ и B ≠ Λ и prec (A, B)) then t ← nextA (A) fin ← appendA (A, fin); если ini = Λ, то ini ← fin A ← t else t ← nextB (B) fin ← appendB (B, fin); если ini = Λ, то ini ← fin B ← t return ini

Функция prec должна принимать адреса r, s двух записей, по одной из каждой последовательности, и вернуть true, если первая запись должна предшествовать другой в выходной последовательности. Функция nextA должна брать адрес записи из первой последовательности и возвращать адрес следующей записи в той же последовательности или Λ, если ее нет. Функция appendA должна добавить первую запись из последовательности A к выходной последовательности; его аргументы - это адрес A записи, которую нужно добавить, и адрес fin последней записи в выходном списке (или Λ, если этот список все еще пуст). Процедура appendA должна возвращать обновленный адрес последнего элемента выходного списка. Процедуры nextB и appendB аналогичны для другой входной последовательности.

Объединение связанных списков

Чтобы проиллюстрировать использование общей процедуры объединения, вот код для объединения двух простых связанных списков, начиная с узлов по адресам R, S Здесь мы предполагаем, что каждая запись x содержит целочисленное поле x.INFO и поле адреса x.NEXT, указывающее на следующий узел; где информационные поля в каждом списке расположены в порядке возрастания. Списки ввода разбираются слиянием, и их узлы используются для построения списка вывода.

процедура listmerge (R, S): процедура prec (r, s): return r.INFO 

Слияние векторов

Следующий код демонстрирует независимость общей процедуры слияния от фактического представления последовательностей. Он объединяет элементы двух обычных массивов от U [0] до U [m − 1] и от V [0] до V [n − 1] чисел с плавающей запятой в порядке убывания. Входные массивы не модифицируются, и объединенная последовательность значений сохраняется в третьем векторе от W [0] до W [m + n-1]. Как и в языке программирования C, мы предполагаем, что выражение «V» дает адрес переменной V, «* p» дает переменную, адрес которой является значением p, и что «(X [i])» эквивалентно в "(X [0]) + i" для любого массива X и любого целого числа i.

процедура arraymerge (U, m, V, n, W): процедура prec (r, s): return (* r)>(* s) процедура nextU (x): if x = (U [m − 1]) тогдаreturn Λ else return x + 1 procedure nextV (x): if x = (V [n − 1]) then return Λ elsereturn x + 1 procedure append (x, fin) if fin = Λ тогда fin ← (W [0]) (* fin) ← (* x) return fin + 1 если m = 0, то U ← Λ если n = 0, затем V ← Λ return merge (U, V, prec, nextU, append, nextV, append)
Пример: определенный интеграл

Интегрирование в интервале

Следующая процедура вычисляет приблизительное целое ∫ ab {\ displaystyle \ textstyle \ int _ {a} ^ {b}}\ textstyle \ int_a ^ b f (x) dx данной действительной функции f на заданном интервале [a, b] вещественной линии . Используемый числовой метод - это правило трапеции с заданным числом шагов n; действительные числа аппроксимируются числами с плавающей запятой.

процедура Intg (f, a, b, n): float t, x, s; целое число i ifb = a затемreturn 0 x ← a; s ← f (a) / 2; для i из 1 ton − 1 do t ← i / (n + 1); х ← (1 - t) * a + t * b; s ← s + f (x) s ← f (b) / 2 return (b - a) * s / n

Интегрирование по диску

Теперь рассмотрим задача интегрирования заданной функции g с двумя аргументами по кругу D с заданным центром (xc, yc) и заданным радиусом R. Эта проблема может быть сведена к двум вложенным интегралам с одной переменной заменой переменных

∫ ∫ D g (x, y) dxdy знак равно ∫ 0 R Z (∫ 0 2 π g (xc + z cos ⁡ t, yc + z sin ⁡ t) dt) dz {\ displaystyle \ int \! \ Int _ { D} g (x, y) \, \ mathrm {d} x \, \ mathrm {d} y = \ int _ {0} ^ {R} z \ left (\ int _ {0} ^ {2 \ pi } g ({\ mathit {xc}} + z \ cos t, {\ mathit {yc}} + z \ sin t) \, \ mathrm {d} t \ right) \, \ mathrm {d} z}\ int \! \ Int_D g (x, y) \, \ mathrm {d} x \, \ mathrm {d} y = \ int_0 ^ R z \ left (\ int_0 ^ {2 \ pi} g (\ mathit {xc} + z \ cos t, \ mathit {yc} + z \ sin t) \, \ mathrm {d} t \ right) \, \ mathrm {d} z

Следующий код реализует правую формулу :

процедуру DiskIntg (g, xc, yc, R, n) процедуру gring (z): процедура gpolar (t): float x, yx ← xc + z * cos (t) y ← yc + z * sin (t) return g (x, y) целое m ← round (n * z / R) return z * Intg (gpolar, 0, 2 * π, m) return Intg (gring, 0, R, n)

Thi Код s использует процедуру интеграции Intg на двух уровнях. Внешний уровень (последняя строка) использует Intg для вычисления интеграла gring (z) для z, изменяющегося от 0 до R. Внутренний уровень (предпоследняя строка) определяет gring (z) как линейный интеграл . из g (x, y) по окружности с центром (xc, yc) и радиусом z.

История

Процедурные параметры были изобретены математиком Алонзо Черчем как часть его лямбда-исчисления, еще до появления электронных компьютеров. модель вычисления.

Процедурные параметры как функция языка программирования были введены в АЛГОЛ 60. Фактически, АЛГОЛ 60 имел мощный механизм передачи параметров «вызов по имени », который мог упростить некоторые способы использования процедурных параметров; см. Устройство Дженсена.

. Процедурные параметры были важной особенностью языка программирования LISP, который также ввел концепцию закрытия функции или funarg. Язык программирования C позволяет передавать указатели на функции в качестве параметров, которые достигают той же цели, и часто используются как обратные вызовы в , управляемом событиями. программирование и как обработчики ошибок. Однако только несколько современных компиляторов C допускают определения вложенных функций, поэтому другие его применения относительно редки. Процедурные параметры также были предоставлены в Паскале вместе с определениями вложенных процедур; однако, поскольку стандартный Паскаль не допускал раздельной компиляции, эта функция также мало использовалась в этом языке.

См. Также
Последняя правка сделана 2021-06-02 07:24:02
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте