Программирование с использованием подпрограмм в пайтоне

В компьютерном программированииподпрограмма-это последовательность команд программы , выполняющая определенную задачу. Упакованную в единое целое. Затем это устройство можно использовать в программах. Где бы ни выполнялась конкретная задача. Подпрограммы могут быть определены в программах или отдельно в библиотеках, которые могут использоваться многими программами. В различных языках программирования подпрограмма может называться рутиной, подпрограммой, функцией, методомили процедурой. Технически все эти термины имеют разные определения. Иногда используется общий, обобщающий термин [1]

Название подпрограммы предполагает. Что подпрограмма ведет себя примерно так же. Как компьютерная программа. Которая используется в качестве одного шага в более крупной программе или другой подпрограмме.

Подпрограмма часто кодируется так. Что она может запускаться несколько раз и из нескольких мест в течение одного выполнения программы. В том числе из других подпрограмм . А затем ветвиться назад (возвращаться) к следующей инструкции после вызова, как только задача подпрограммы будет выполнена. Идея подпрограммы была первоначально задумана Джоном Мочли во время его работы над ENIAC,[2] и записан на январском Гарвардском симпозиуме 1947 года на тему

Морису Уилксу, Дэвиду Уилеруи Стэнли Гиллу обычно приписывают формальное изобретение этой концепции. Которую они назвали закрытой подпрограммой,в отличие от открытой подпрограммы или макроса. Однако Тьюринг обсуждал подпрограммы в статье 1945 года о проектных предложениях для NPL ACE, зайдя так далеко . Что изобрел концепцию стека обратных адресов.]

Подпрограммы-это мощный инструмент программирования[8], и синтаксис многих языков программирования включает в себя поддержку их написания и использования. Разумное использование подпрограмм (например, с помощью

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

В методе компиляции, называемом потоковым кодом, исполняемая программа в основном представляет собой последовательность вызовов подпрограмм.

Основные понятия

Содержание подпрограммы-это ее тело. Которое является частью программного кода. Выполняемого при вызове или вызове подпрограммы.

Подпрограмма может быть написана так. Что она ожидает получить одно или несколько значений данных от вызывающей программы (для замены ее параметров или формальных параметров). Вызывающая программа предоставляет фактические значения для этих параметров. Называемых аргументами. Различные языки программирования могут использовать различные соглашения для передачи аргументов:

СоглашениеОписаниеОбщее использование
Вызов по значениюАргумент вычисляется и копия значения передается подпрограмме

По умолчанию используется в большинстве алгол-подобных языков после Алгола 60, таких как Pascal, Delphi, Simula, CPL, PL/M, Modula, Oberon, Ada и многих других. C, C++, Java (Ссылки на объекты и массивы также передаются по значению)
Звоните по ссылкеСсылка на аргумент, как правило, передается по его адресуВыбирается в большинстве алголоподобных языков после Алгола 60, таких как Алгол 68, Паскаль, Дельфи, Симула, CPL, PL/M, Модуль, Оберон, Ада и многие другие. C++, Fortran, PL/I
Вызов по результатуЗначение параметра копируется обратно в аргумент при возврате из подпрограммы

Параметры Ada OUT
Вызов по значению-результатЗначение параметра копируется обратно при входе в подпрограмму и снова при возвратеAlgol, Swift in-out параметры
Звоните по имениКак макрос – замените параметры на неосознаваемые выражения аргументовАлгол, Скала
Вызов по постоянному значениюКак вызов по значению, за исключением того. Что параметр обрабатывается как константаPL/I НЕНАЗНАЧАЕМЫЕ параметры, Ada В параметрах

Подпрограмма может возвращать вычисленное значение своему вызывающему объекту (его возвращаемое значение) или предоставлять различные результирующие значения или выходные параметры.

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

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

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

Подпрограмму можно закодировать так . Чтобы она могла рекурсивно вызывать себяв одном или нескольких местах для выполнения своей задачи.

Этот метод позволяет непосредственно реализовать функции. Определенные математической индукцией и рекурсивными алгоритмами деления и завоевания.

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

Подпрограмму. Которая не возвращает никакого значения или возвращает нулевое значение. Иногда называют процедурой.

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

Поддержка языков

Высокоуровневые языки программирования обычно включают в себя специальные конструкции. Позволяющие:

  • Разделите ту часть программы (тело). Которая составляет подпрограмму
  • Присвоить подпрограмме идентификатор (имя)
  • Укажите имена и типы данных его параметров и возвращаемых значений
  • Предоставьте частную область именования для своих временных переменных
  • Определите переменные вне подпрограммы доступные внутри нее
  • Вызовите подпрограмму
  • Предоставьте значения его параметрам

  • Основная программа содержит адрес подпрограммы
  • Подпрограмма содержит адрес следующей команды вызова функции в основной программе
  • Укажите возвращаемые значения из его тела
  • Вернитесь к вызывающей программе
  • Утилизируйте значения возвращаемые вызовом
  • Справляйтесь с любыми исключительными условиями возникшими во время разговора
  • Упакуйте подпрограммы в модуль, библиотеку, объектили класс

Некоторые языки программирования, такие как Pascal, Fortran, Ada и многие диалекты BASIC, различают функции или подпрограммы функций. Которые предоставляют явное возвращаемое значение вызывающей программе. И подпрограммы или процедуры. Которые этого не делают. В этих языках вызовы функций обычно встроены в

выражения (например, sqrtфункция может быть вызвана как y = z + sqrt(x)). Вызовы процедур либо ведут себя синтаксически как операторы (например, printпроцедура может быть вызвана как if x > 0 then print(x)или явно вызывается оператором типа CALLили GOSUB(например,call print(x)). Другие языки. Такие как C и Lisp, не различайте функции и подпрограммы.

В строго функциональных языках программирования . Таких как Haskell, подпрограммы не могут иметь побочных эффектов, а это означает. Что различные внутренние состояния программы не изменятся.

Функции всегда возвращают один и тот же результат при повторном вызове с одними и теми же аргументами. Такие языки обычно поддерживают только функции. Поскольку подпрограммы, не возвращающие значения, бесполезны. Если только они не могут вызвать побочный эффект.

В таких языках программирования . Как C, C++и C#, подпрограммы также могут называться просто функциями (не путайте их с математическими функциями или функциональным программированием, которые являются разными понятиями).

Компилятор языка обычно переводит вызовы процедур и возвращает их в машинные инструкции в соответствии с четко определенным

соглашением о вызове, так что подпрограммы могут быть скомпилированы отдельно от вызывающих их программ. Последовательности команд. Соответствующие операторам call и return. Называются прологом и эпилогом процедуры.

Преимущества разбиения программы на подпрограммы заключаются в следующем:

  • Декомпозиция сложной задачи программирования на более простые шаги: это один из двух основных инструментов структурированного программирования, наряду со структурами данных
  • Сокращение количества дубликатов кода в программе
  • Возможность повторного использования кода в нескольких программах
  • Разделение большой задачи программирования между различными программистами или различными этапами проекта

  • Скрытие деталей реализации от пользователей подпрограммы
  • Улучшение читабельности кода путем замены блока кода вызовом функции. Где описательное имя функции служит для описания блока кода. Это делает вызывающий код кратким и читабельным. Даже если функция не предназначена для повторного использования.
  • Улучшение прослеживаемости (то есть большинство языков предлагают способы получения трассировки вызовов. Которая включает в себя имена задействованных подпрограмм и, возможно. Даже больше информации. Такой как имена файлов и номера строк); если бы код не был разложен на подпрограммы. Отладка была бы серьезно затруднена

По сравнению с использованием встроенного кода вызов подпрограммы накладывает некоторые вычислительные издержки в механизме вызова.]

Подпрограмма обычно требует стандартного кода обслуживания – как при входе в функцию. Так и при выходе из нее (пролог и эпилог функции – обычно сохранение регистров общего назначения и обратный адрес как минимум).

Идея подпрограммы была разработана после того. Как вычислительные машины уже существовали в течение некоторого времени.

Инструкции арифметики и условного перехода были запланированы заранее и изменились относительно мало. Но специальные инструкции. Используемые для вызова процедур. Сильно изменились за эти годы. Самые ранние компьютеры и микропроцессоры, такие как Manchester Baby и RCA 1802, не имели единой команды вызова подпрограммы. Подпрограммы можно было реализовать. Но они требовали. Чтобы программисты использовали последовательность вызовов—последовательность инструкций—в каждом месте вызова.

Подпрограммы были реализованы в Z4 Конрада Цузе в 1945 году.

В 1945 году Алан М. Тьюринг использовал термины [10][11]

В январе 1947 года Джон Мочли представил общие заметки на

Здесь он обсуждает последовательную и параллельную работу.

..структура машины не должна быть сложной ни на йоту. Поскольку имеются все логические характеристики. Необходимые для этой процедуры. Можно разработать кодирующую инструкцию для размещения подпрограмм в памяти в местах. Известных машине. И таким образом. Чтобы их можно было легко использовать.

Другими словами. Подпрограмму А можно обозначить как деление. А подпрограмму В-как сложное умножение. А подпрограмму С-как оценку стандартной ошибки последовательности чисел и так далее по списку подпрограмм. Необходимых для конкретной задачи. …

Все эти подпрограммы затем будут храниться в машине, и все. Что нужно сделать. Это сделать краткую ссылку на них по номеру. Как они указаны в коде.[3]

Кей Макналти тесно сотрудничала с Джоном Мочли в команде ENIAC и разработала идею подпрограмм для компьютера ENIAC. Который она программировала во время Второй мировой войны.[12] Она и другие программисты ENIAC использовали эти подпрограммы для расчета траекторий ракет.[12]

Гольдстайн и фон Нейман написали статью от 16 августа 1948 года. В которой обсуждалось использование подпрограмм.

[13]

Некоторые очень ранние компьютеры и микропроцессоры, такие как IBM 1620, Intel 4004 и Intel 8008, а также микроконтроллеры PIC, имеют вызов подпрограммы с одной инструкцией. Который использует выделенный аппаратный стек для хранения обратных адресов-такое оборудование поддерживает только несколько уровней вложенности подпрограмм. Но может поддерживать рекурсивные подпрограммы. Машины до середины 1960—х годов-такие как UNIVAC I, PDP —1и IBM 1130-обычно использовали соглашение о вызовах который сохранил счетчик команд в первой ячейке памяти вызываемой подпрограммы.

Это позволяет произвольно глубокие уровни вложенности подпрограмм. Но не поддерживает рекурсивные подпрограммы. PDP-11 (1970)-один из первых компьютеров с инструкцией вызова подпрограммы выталкивания стека; эта функция поддерживает как произвольно глубокую вложенность подпрограмм. Так и рекурсивные подпрограммы.[14]

Поддержка языков

В самых ранних ассемблерах поддержка подпрограмм была ограничена. Подпрограммы не были явно отделены друг от друга или от основной программы. И действительно. Исходный код подпрограммы мог быть перемежен с кодом других подпрограмм.

Некоторые ассемблеры предлагают предопределенные макросы для генерации последовательностей вызова и возврата. К 1960-м годам ассемблеры обычно имели гораздо более сложную поддержку как встроенных. Так и отдельно собранных подпрограмм. Которые могли быть связаны друг с другом.

Подпрограммы библиотеки

Даже при таком громоздком подходе подпрограммы оказались очень полезными. Во-первых, они допускали использование одного и того же кода во множестве различных программ. Кроме того, память была очень дефицитным ресурсом на ранних компьютерах. А подпрограммы позволяли значительно экономить размер программ.

Многие ранние компьютеры загружали инструкции программы в память с перфоленты. Затем каждая подпрограмма может быть обеспечена отдельным куском ленты. Загруженным или сплайсированным до или после основной программы (или [15]); и одна и та же лента подпрограммы может использоваться многими различными программами. Аналогичный подход применялся в компьютерах. Которые использовали перфокарты для своего основного ввода. Название библиотека подпрограмм первоначально означало библиотеку в буквальном смысле. Которая хранила индексированные коллекции лент или колод карт для коллективного использования.

Возвращение непрямым прыжком

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

На этих компьютерах вызывающая программа. Вместо того чтобы изменять обратный переход подпрограммы. Сохраняла обратный адрес в переменной, так что. Когда подпрограмма завершалась. Она выполняла косвенный переход. Который направлял выполнение в место. Заданное предопределенной переменной.

Переход к подпрограмме

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

В IBM System/360, например. Инструкции ветвления BAL или BALR. Предназначенные для вызова процедуры. Сохраняли бы обратный адрес в регистре процессора. Указанном в инструкции. По условию регистр 14. Чтобы вернуться. Подпрограмме нужно было только выполнить косвенную инструкцию ветвления (BR) через этот регистр. Если подпрограмме нужен этот регистр для какой-то другой цели (например. Для вызова другой подпрограммы). Она сохранит содержимое регистра в закрытой ячейке памяти или

в стеке регистров.

В таких системах , как HP 2100, инструкция JSB выполняла бы аналогичную задачу. За исключением того. Что обратный адрес хранился в ячейке памяти. Которая была целью ветви. Выполнение процедуры фактически начнется в следующей ячейке памяти. Например, на ассемблере HP 2100 можно было бы написать

 ... JSB MYSUB (Вызывает подпрограмму MYSUB.) BB ... (Вернется сюда после того. Как МАЙСУБ закончит.) 

вызвать подпрограмму MYSUB из основной программы. Подпрограмма будет закодирована следующим образом

 MYSUB NOP (Хранение обратного адреса MYSUB.) AA... (Начало тела МАЙСУБА.) ... JMP MYSUB,I (Возвращается к вызывающей программе.) 

Инструкция JSB помещает адрес СЛЕДУЮЩЕЙ инструкции (а именно. BB) в место. Указанное в качестве ее операнда (а именно, MYSUB). А затем разветвляется на СЛЕДУЮЩЕЕ место после этого (а именно. AA = MYSUB + 1). Затем подпрограмма могла вернуться к основной программе. Выполнив косвенный переход JMP MYSUB. Который разветвлялся на местоположение. Хранящееся в местоположении MYSUB.

Компиляторы для Fortran и других языков могли бы легко использовать эти инструкции. Если бы они были доступны. Этот подход поддерживал несколько уровней вызовов; однако. Поскольку адрес возврата. Параметры и возвращаемые значения подпрограммы были назначены фиксированными ячейками памяти. Он не допускал рекурсивных вызовов.

Кстати, аналогичный метод использовался компанией Lotus 1-2-3в начале 1980-х годов для обнаружения зависимостей пересчета в электронной таблице. А именно, в каждой ячейке было зарезервировано место для хранения обратного адреса. Поскольку циклические ссылки не допускаются для естественного порядка пересчета. Это позволяет обходить дерево без резервирования места для стека в памяти. Что было очень ограничено на небольших компьютерах. Таких как IBM PC.

Стек вызовов

Большинство современных реализаций вызова подпрограммы используют стек вызовов, частный случай структуры данных стека, для реализации вызовов и возвратов подпрограмм. Каждый вызов процедуры создает новую запись. Называемую кадром стека, в верхней части стека; когда процедура возвращается. Ее кадр стека удаляется из стека. И его пространство может быть использовано для других вызовов процедур. Каждый кадр стека содержит личные данные соответствующего вызова. Которые обычно включают параметры процедуры и внутренние переменные. А также адрес возврата.

Последовательность вызовов может быть реализована последовательностью обычных инструкций (подход. Все еще используемый в архитектуре сокращенных наборов команд (RISC) и очень длинных командных слов (VLIW)). Но многие традиционные машины. Разработанные с конца 1960-х годов. Включали специальные инструкции для этой цели.

Стек вызовов обычно реализуется как непрерывная область памяти. Это произвольный выбор дизайна. Является ли нижняя часть стека самым низким или самым высоким адресом в этой области. Так что стек может расти вперед или назад в памяти; однако многие архитектуры выбрали последнее.]

Некоторые проекты. Особенно некоторые реализации Forth. Использовали два отдельных стека. Один в основном для управляющей информации (например. Обратные адреса и счетчики циклов). А другой-для данных. Первый был или работал как стек вызовов и был только косвенно доступен программисту через другие языковые конструкции. В то время как последний был более непосредственно доступен.

Когда впервые были введены вызовы процедур на основе стека. Важной мотивацией была экономия драгоценной памяти. С помощью этой схемы компилятору не нужно резервировать отдельное пространство в памяти для частных данных (параметров. Адреса возврата и локальных переменных) каждой процедуры. В любой момент времени стек содержит только личные данные вызовов. Которые в данный момент активны (а именно. Которые были вызваны. Но еще не вернулись). Из-за того. Что программы обычно собирались из библиотек. Было (и до сих пор остается) не редкостью найти программы. Включающие тысячи подпрограмм. Из которых только несколько активны в данный момент. Для таких программ механизм стека вызовов может сэкономить значительные объемы памяти. Действительно. Механизм стека вызовов можно рассматривать как самый ранний и простой метод автоматического управления памятью.

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

Отложенная укладка

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

Эти накладные расходы наиболее очевидны и нежелательны в листовых процедурах или листовых функциях, которые возвращаются без выполнения каких-либо вызовов процедур сами.[16][17][18] Чтобы уменьшить эти накладные расходы. Многие современные компиляторы пытаются отложить использование стека вызовов до тех пор. Пока он действительно не понадобится. Например, вызов процедуры Р может хранить обратный адрес и параметры вызываемой процедуры в определенных регистрах процессора и передавать управление телу процедуры простым переходом. Если процедура P возвращается без выполнения какого-либо другого вызова. Стек вызовов не используется вообще. Если P необходимо вызвать другую процедуру Q, затем он будет использовать стек вызовов для сохранения содержимого любых регистров (таких как обратный адрес). Которые будут необходимы после возврата Q.

Примеры C и C++

В языках программирования C и C++ подпрограммы называются функциями (далее классифицируются как функции-члены, когда они связаны с классом, или свободные функции[19], когда их нет). Эти языки используют специальное ключевое voidслово. Чтобы указать. Что функция не возвращает никакого значения. Обратите внимание. Что функции C/C++ могут иметь побочные эффекты. Включая изменение любых переменных. Адреса которых передаются в качестве параметров. Примеры:

void Function1() { /* некоторый код */ } 

Функция не возвращает значения и должна вызываться как автономная функция. Например, Function1();

int Function2() { return 5; } 

Эта функция возвращает результат (число 5). И вызов может быть частью выражения, например, x + Function2()

char Function3(int number) { char selection[] = {'S', 'M', 'T', 'W', 'T', 'F', 'S'}; return selection[number]; } 

Эта функция преобразует число от 0 до 6 в начальную букву соответствующего дня недели. А именно от 0 до Результат его вызова может быть присвоен переменной. Например num_day = Function3(number);.

void Function4(int* pointer_to_var) { (*pointer_to_var)++; } 

Эта функция не возвращает значение. Но изменяет переменную. Адрес которой передается в качестве параметра; она будет вызываться с Function4(&variable_to_increment);помощью .

Небольшой Базовый пример

Example() 'Вызывает подпрограмму Sub Example ' Начинает подпрограмму TextWindow.WriteLine("Это пример подпрограммы в Microsoft Small Basic.") 'Что делает подпрограмма EndSub ' Заканчивает подпрограмму 

В приведенном выше примере Example()вызывается подпрограмма.[20] Чтобы определить саму подпрограмму, Subнеобходимо использовать ключевое слово с последующим именем подпрограммы Sub. После того. Как содержание последовало, EndSubоно должно быть напечатано.

Примеры Visual Basic 6

В языке Visual Basic 6 подпрограммы называются функциями или подразделами (или методами, если они связаны с классом). Visual Basic 6 использует различные термины. Называемые типами, для определения того. Что передается в качестве параметра. По умолчанию неопределенная переменная регистрируется как тип variant и может быть передана как ByRef (по умолчанию) или ByVal. Кроме того, при объявлении функции или подфункции ей присваивается публичное. Частное или дружеское обозначение. Которое определяет. Можно ли получить к ней доступ за пределами модуля или проекта. В котором она была объявлена.

  • По значению [БыВал] – способ передачи значения аргумента процедуре путем передачи копии значения. А не передачи адреса. В результате фактическое значение переменной не может быть изменено процедурой. Которой она передается.
  • По ссылке [ByRef] – способ передачи значения аргумента в процедуру путем передачи адреса переменной вместо передачи копии ее значения. Это позволяет процедуре получить доступ к фактической переменной. В результате фактическое значение переменной может быть изменено процедурой. В которую она передается. Если не указано иное. Аргументы передаются по ссылке.
  • Public (необязательно) – указывает. Что процедура функции доступна для всех других процедур во всех модулях. Если используется в модуле. Содержащем параметр Private. Процедура недоступна вне проекта.
  • Private (необязательно) – указывает. Что процедура функции доступна только другим процедурам в модуле. В котором она объявлена.
  • Friend (необязательно) – используется только в модуле класса. Указывает. Что процедура функции видна во всем проекте. Но не видна контроллеру экземпляра объекта.
Private Function Function1() ' Некоторый Код Здесь End Function 

Функция не возвращает значения и должна вызываться как автономная функция. Например, Function1

Частная функция Function2() как Целочисленная функция Function2 = 5 Конечная функция 

Эта функция возвращает результат (число 5). И вызов может быть частью выражения, например, x + Function2()

Частная функция Function3(ByVal intValue as Integer) as String Dim strArray(6) as String strArray = Array("M", "T", "W", "T", "F", "S", "S") Function3 = strArray(intValue) End Function 

Эта функция преобразует число от 0 до 6 в начальную букву соответствующего дня недели. А именно от 0 до ‘M’. От 1 до ‘T’, …. От 6 до ‘S’. Результат его вызова может быть присвоен переменной. Например,num_day = Function3(number).

Частная функция Function4(ByRef intValue as Integer) intValue = intValue + 1 Конечная функция 

Эта функция не возвращает значение. Но изменяет переменную. Адрес которой передается в качестве параметра; она будет вызываться с помощью Function4(variable_to_increment)

PL/I пример

В PL/I вызываемой процедуре может быть передан дескриптор, предоставляющий информацию об аргументе. Такую как длина строки и границы массива. Это позволяет сделать процедуру более общей и избавляет программиста от необходимости передавать такую информацию. По умолчанию PL/I передает аргументы по ссылке. (Тривиальная) подпрограмма для изменения знака каждого элемента двумерного массива может выглядеть следующим образом:

 change_sign: процедура(массив); declare array(*,*) float; array = -массив; end change_sign; 

Это можно было бы вызвать с помощью различных массивов следующим образом:

 /* границы первого массива варьируются от -5 до +10 и от 3 до 9 */ объявить array1 (-5:10, 3:9)float; /* второй массив граничит с 1 до 16 и с 1 до 16 */ объявить array2 (16,16) float; вызов change_sign(array1); вызов change_sign(array2); 

Локальные переменные. Рекурсия и реентранс

Подпрограмма может найти полезным использовать определенное количество свободного пространства. То есть памяти, используемой во время выполнения этой подпрограммы для хранения промежуточных результатов. Переменные . Хранящиеся в этом пространстве скретча, называются локальными переменными, а пространство скретча-записью активации. Запись активации обычно имеет обратный адрес, который сообщает ей. Куда передать управление обратно. Когда подпрограмма завершится.

Подпрограмма может иметь любое количество и характер сайтов вызовов. Если рекурсия поддерживается. Подпрограмма может даже вызвать саму себя. В результате чего ее выполнение приостанавливается. В то время как происходит другое вложенное выполнение той же подпрограммы. Рекурсия является полезным средством для упрощения некоторых сложных алгоритмов и разбиения сложных задач. Рекурсивные языки обычно предоставляют новую копию локальных переменных при каждом вызове. Если программист хочет. Чтобы значение локальных переменных оставалось неизменным между вызовами. Они могут быть объявлены статическими в некоторых языках могут использоваться либо глобальные значения. Либо общие области. Вот пример рекурсивной подпрограммы в C/C++ для поиска чисел Фибоначчи:

int Fib(int n) { if (n  1) { return n; } return Fib(n - 1) + Fib(n - 2); } 

Ранние языки. Такие как Фортран, изначально не поддерживали рекурсию. Поскольку переменные были статически распределены. А также расположение обратного адреса. Большинство компьютеров до конца 1960-х годов, таких как PDP-8, не имели поддержки аппаратных стековых регистров.]

Современные языки после АЛГОЛЯ, такие как PL/I и C, почти всегда используют стек. Обычно поддерживаемый большинством современных компьютерных наборов команд. Чтобы обеспечить свежую запись активации для каждого выполнения подпрограммы. Таким образом. Вложенное выполнение может свободно изменять свои локальные переменные. Не заботясь о влиянии на другие приостановленные выполняемые операции. По мере накопления вложенных вызовов формируется структура стека вызовов. Состоящая из одной записи активации для каждой приостановленной подпрограммы. На самом деле. Эта структура стека практически повсеместна. И поэтому записи активации обычно называются кадрами стека.

Некоторые языки . Такие как Pascal, PL/I и Ada , также поддерживают вложенные подпрограммы, которые являются подпрограммами. Вызываемыми только в рамках внешней (родительской) подпрограммы. Внутренние подпрограммы имеют доступ к локальным переменным внешней подпрограммы. Которая их вызвала. Это достигается путем хранения дополнительной контекстной информации в записи активации. Также называемой дисплеем.

Если подпрограмма может быть выполнена правильно. Даже когда другое выполнение той же подпрограммы уже выполняется. Эта подпрограмма называется реентерабельной. Рекурсивная подпрограмма должна быть реентерабельной. Реентерабельные подпрограммы также полезны в многопоточных ситуациях. Поскольку несколько потоков могут вызывать одну и ту же подпрограмму. Не опасаясь вмешательства друг в друга. В системе обработки транзакций IBM CICSquasi-reentrant был немного менее ограничительным . Но аналогичным требованием для прикладных программ. Которые были разделены многими потоками.

В многопоточной среде обычно существует более одного стека. Среда, полностью поддерживающая сопрограммы или ленивую оценку, может использовать структуры данных. Отличные от стеков. Для хранения записей активации.

Перегрузка

В строго типизированных языкахиногда желательно иметь несколько функций с одинаковым именем . Но работающих с разными типами данных или с разными профилями параметров. Например, функция квадратного корня может быть определена для работы с реалами. Комплексными значениями или матрицами. Алгоритм, который будет использоваться в каждом случае, отличается. И результат возврата может быть разным. Написав три отдельные функции с одним и тем же именем. Программист имеет удобство не запоминать разные имена для каждого типа данных. Далее, если для реалов можно определить подтип. Чтобы разделить положительные и отрицательные реалы. То для реалов можно записать две функции: одну для возврата вещественного. Когда параметр положительный. И другую для возврата комплексного значения. Когда параметр отрицательный.

В объектно-ориентированном программировании, когда ряд функций с одинаковыми именами может принимать различные профили параметров или параметры различных типов. Каждая из функций называется перегруженной.

Вот пример перегрузки подпрограмм в C++:

#include  double Area(double h, double w) { return h * w; } double Area(double r) { return r * r * 3.14; } int main() { double rectangle_area = Area(3, 4); double circle_area = Area(5); std::cout    rectangle_area  std::endl; std::cout    circle_area  std::endl; } 

В этом коде есть две функции с одинаковыми именами. Но они имеют разные параметры.

В качестве другого примера подпрограмма может сконструировать объект, который будет принимать направления. И проследить его путь до этих точек на экране. Существует множество параметров. Которые могут быть переданы конструктору (цвет трассы. Начальные координаты x и y. Скорость трассы). Если программист хотел. Чтобы конструктор мог принимать только параметр color. То он мог бы вызвать другой конструктор. Принимающий только color, который. В свою очередь. Вызывает конструктор со всеми параметрами. Передающими набор значений по умолчанию для всех остальных параметров (X и Y обычно центрируются на экране или помещаются в начало координат. А скорость устанавливается на другое значение по выбору кодера).

PL/I имеет GENERICатрибут для определения общего имени для набора ссылок на записи. Вызываемых с различными типами аргументов. Пример:

 ОБЪЯВИТЕ gen_name GENERIC( name WHEN(FIXED BINARY). Flame WHEN(FLOAT). Pathname В ПРОТИВНОМ СЛУЧАЕ );

Для каждой записи может быть указано несколько определений аргументов. Вызов Если аргумент не совпадает ни с одним из вариантов выбора. То будет вызван

Замыкание-это подпрограмма вместе со значениями некоторых ее переменных. Взятыми из среды. В которой она была создана. Замыкания были заметной особенностью языка программирования Lisp. Введенного Джоном Маккарти. В зависимости от реализации замыкания могут служить механизмом побочных эффектов.

Разработано большое количество условных обозначений для кодирования подпрограмм. Что касается их именования. Многие разработчики приняли подход. Согласно которому имя подпрограммы должно быть глаголом, когда она выполняет определенную задачу, и прилагательным, когда она делает некоторый запрос, и существительным, когда она используется для замены переменных.

Некоторые программисты предполагают. Что подпрограмма должна выполнять только одну задачу. И если подпрограмма выполняет более одной задачи. Она должна быть разделена на несколько подпрограмм. Они утверждают . Что подпрограммы являются ключевыми компонентами в обслуживании кода, и их роли в программе должны оставаться различными.

Сторонники модульного программирования (modularizing code) утверждают. Что каждая подпрограмма должна иметь минимальную зависимость от других частей кода. Например, использование глобальных переменных обычно считается неразумным сторонниками этой точки зрения. Поскольку оно добавляет тесную связь между подпрограммой и этими глобальными переменными. Если такая связь не нужна. Их совет состоит в том. Чтобы рефакторировать подпрограммы. Чтобы вместо этого принимать переданные параметры. Однако увеличение числа параметров. Передаваемых подпрограммам. Может повлиять на читаемость кода.

Коды возврата

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

В IBM System/360, где код возврата ожидался от подпрограммы. Возвращаемое значение часто было рассчитано как кратное 4—так. Чтобы его можно было использовать в качестве прямого индекса таблицы ветвей в таблицу ветвей. Часто расположенную сразу после инструкции вызова. Чтобы избежать дополнительных условных тестов. Что еще больше повышает эффективность. Например , на ассемблере System/360можно было бы написать:

 BAL 14, SUBRTN01 перейти к подпрограмме. Хранящей обратный адрес в R14 B TABLE(15) используйте возвращаемое значение в reg 15 для индексации таблицы ветвей. * ветвление в соответствующую ветвь. 

Оптимизация вызовов подпрограмм

При вызове подпрограммы возникают значительные накладные расходы во время выполнения. Включая передачу аргументов. Ветвление в подпрограмму и ответвление обратно вызывающему. Накладные расходы часто включают в себя сохранение и восстановление определенных регистров процессора. Выделение и восстановление памяти кадров вызоваи т. Д. В некоторых языках каждый вызов подпрограммы также подразумевает автоматическое тестирование кода возврата подпрограммы или обработку исключений, которые она может вызвать. В объектно-ориентированных языках значительным источником накладных расходов является интенсивно используемая динамическая диспетчеризация вызовов методов.

Есть некоторые. Казалось бы. Очевидные оптимизации вызовов процедур. Которые не могут быть применены. Если процедуры могут иметь побочные эффекты. Например, в выражении (f(x)-1)/(f(x)+1)функция fдолжна быть вызвана дважды . Потому что два вызова могут возвращать разные результаты. Кроме того, значение xдолжно быть извлечено снова перед вторым вызовом. Так как первый вызов может изменить его. Определить, может ли подпрограмма иметь побочный эффект. Очень сложно (действительно, неразрешимо в силу теоремы РайсаТаким образом. Хотя эти оптимизации безопасны в чисто функциональных языках программирования. Компиляторы типичного императивного программирования обычно должны предполагать худшее.

Встраивание

Метод, используемый для устранения этих накладных расходов. — это встроенное расширение или встраивание тела подпрограммы в каждый узел вызова (в отличие от ветвления к подпрограмме и обратно). Это не только позволяет избежать накладных расходов на вызов. Но и позволяет компилятору более эффективно оптимизировать тело процедуры, принимая во внимание контекст и аргументы при этом вызове. Вставленное тело может быть оптимизировано компилятором. Однако инлайнинг обычно увеличивает размер кода. Если только программа не содержит только один вызов подпрограммы.

  1. ^
  2. ^ Субрата Дасгупта (7 января 2014). Все началось с Oxford University Press. ISBN 978-0-19-930943-6.
  3. ^ Jump up to: a b J. W. Mauchly,
  4. ^ Уилер, Д. Дж. (1952). (PDF). Труды национального собрания ACM 1952 года (Питсбург) on — ACM ’52. p. 235. doi:10.1145/609784.609816.
  5. ^ Уилкс, М. В.; Уилер, Д. Дж.; Гилл, С. (1951). Подготовка программ для Электронно-цифровой вычислительноймашины . Эддисон-Уэсли.
  6. ^ Дейнит, Джон (2004). откройте подпрограмму.Словарь вычислительной техники. Encyclopedia.com. Извлечено 14 января 2013года .
  7. ^ Тьюринг, Алан М. (1945), Доклад д-ра А. М. Тьюринга о предложениях по разработке автоматического вычислительного двигателя (АИС): Представлен Исполнительному комитету НПЛ в феврале 1946 г. Copeland, B. J., ed. (2005). Автоматическая вычислительная машина Алана Тьюринга. Oxford: Oxford University Press. p. 383. ISBN 0-19-856593-3.
  8. ^ Дональд Э. Кнут (1997). Искусство компьютерного программирования, Том I: Фундаментальные алгоритмы. Эддисон-Уэсли. ISBN 0-201-89683-4.
  9. ^ Даль; Э. В. Дейкстра; К. А. Р. Хоар (1972). Структурированное программирование. Академическая пресса. ISBN 0-12-200550-3.
  10. ^ Тьюринг, Алан Мэтисон (1946-03-19) [1945], Предложения по разработке в Математическом отделе Автоматического вычислительного двигателя (ACE) (NB. Представлен в 1946-03-19 годах Исполнительному комитету Национальной физической лаборатории (Великобритания).)
  11. ^ Карпентер. Брайан Эдвард; Доран, Роберт Уильям (1977-01-01) [октябрь 1975 г.]. . Компьютерный журнал. 20 (3): 269-279. doi:10.1093/comjnl/20.3.269. (11 страниц)
  12. ^ b Айзексон. Уолтер (18 сентября 2014 года). . Удача. Архивирован с оригинала 12 декабря 2018года . Проверено в 2018-12-14 годах.
  13. ^ Планирование и кодирование задач для электронного вычислительного прибора, Pt 2, Vol. 3 https://library.ias.edu/files/pdfs/ecp/planningcodingof0103inst.pdf (см. стр. 163 pdf для соответствующей страницы)
  14. ^ Guy Lewis Steele Jr . AI Memo 443. . Раздел Почему Вызовы процедур имеют плохую репутацию
  15. ^ Фрэнк, Томас С. (1983). Введение в PDP-11 и его язык ассемблера. Серия программ Прентиса-Холла. Прентис-Холл, стр. ISBN 9780134917047. Извлечено 2016-07-06. Мы могли бы снабдить нашего сборщика копиями исходного кода для всех наших полезных подпрограмм, а затем. Представляя ему основную программу для сборки. Сказать ему. Какие подпрограммы будут вызваны в основной программе…]
  16. ^ . Infocenter.arm.com… Проверено 2013-09-29.
  17. ^ . Microsoft Docs. Microsoft. Извлечено 5 августа 2019года .
  18. ^ . Msdn.microsoft.com. Извлечено 2013-09-29.
  19. ^ .
  20. ^ . www.smallbasic.com