В компьютерном программировании оператор возврата заставляет выполнение оставить текущую подпрограмму и возобновить ее в точке кода сразу после инструкции, которая вызвала подпрограмму, известной как ее адрес возврата. Адрес возврата сохраняется вызывающей подпрограммой, сегодня обычно в стеке вызовов процесса или в регистре. Операторы возврата на многих языках позволяют функции указывать возвращаемое значение, которое должно быть передано обратно в код, который вызвал функцию.
В C и C ++, (где это выражение ) является утверждение, что говорит функцию для выполнения возврата программы вызывающей функции, и сообщить значение. Если функция имеет тип возврата void, оператор return может использоваться без значения, и в этом случае программа просто вырывается из текущей функции и возвращается к вызывающей. return exp;
exp
exp
В Паскале нет оператора возврата. (Однако в более новых Паскалях можно использовать для немедленного возврата значения. Без параметров он просто прерывает процедуру.) Подпрограмма автоматически возвращается, когда выполнение достигает своего последнего исполняемого оператора. Значения могут быть возвращены путем присвоения идентификатору, имеющему то же имя, что и подпрограмма, функции в терминологии Паскаля. Таким образом, идентификатор функции используется для рекурсивных вызовов и в качестве держателя результата; синтаксически это похоже на явный выходной параметр. Тот же синтаксис используется в Fortran 66 и Fortran 77, хотя в FORTRAN II был добавлен оператор return. В некоторых других языках вместо идентификатора функции используется определяемая пользователем переменная результата. Exit(exp);
Оберон ( Оберон-07 ) имеет предложение возврата вместо оператора возврата. Предложение return помещается после последнего оператора тела процедуры. Это позволяет во время компиляции проверять правильность возврата и возвращаемого значения из процедуры.
Некоторые языки программирования, ориентированные на выражения, такие как Lisp, Perl и Ruby, позволяют программисту опускать явный оператор возврата, указывая вместо этого, что последнее вычисленное выражение является возвращаемым значением подпрограммы.
В других случаях возвращается значение Null, если нет явного оператора возврата: в Python значение None
возвращается, когда оператор возврата опущен, тогда как в JavaScript undefined
возвращается значение.
В Windows PowerShell все оцениваемые выражения, которые не учитываются (например, присваиваемый переменным, гипс к недействительному или по трубопроводу до $ Null ) возвращаются из подпрограммы в качестве элементов в массиве, или в качестве одного объекта в том случае, если только один объект не был захвачен.
В Perl возвращаемое значение или значения подпрограммы могут зависеть от контекста, в котором она была вызвана. Наиболее фундаментальное различие - это скалярный контекст, в котором вызывающий код ожидает одно значение, контекст списка, в котором вызывающий код ожидает список значений, и пустой контекст, в котором вызывающий код вообще не ожидает никакого возвращаемого значения. Подпрограмма может проверять контекст с помощью wantarray
функции. Для возврата неопределенного значения в скалярном контексте и пустого списка в контексте списка используется специальный синтаксис возврата без аргументов. Скалярный контекст можно дополнительно разделить на контексты логических, числовых, строковых и различных ссылочных типов. Кроме того, контекстно-зависимый объект может быть возвращен с помощью контекстной возвращаемой последовательности с ленивым вычислением скалярных значений.
Многие операционные системы позволяют программе возвращать результат (отдельный от обычного вывода ), когда ее процесс завершается; эти значения называются кодами возврата или, более конкретно, статусами выхода. Объем информации, который может быть передан таким образом, весьма ограничен, на практике часто ограничивается сигналом успеха или неудачи. Изнутри программы этот возврат обычно достигается путем вызова Exit (системного вызова) (распространен даже в C, где доступен альтернативный механизм возврата из основной функции ).
Заявления возврата бывают разных форм. Наиболее распространены следующие синтаксисы:
Язык | Заявление о возврате | Если значение опущено, возврат |
---|---|---|
Ада, оболочка Борна, C, C ++, Java, PHP, C #, JavaScript, D | return value; | в оболочке Bourne значение выхода последней команды, выполненной в функции в C и C ++ - неопределенное поведение, если функция возвращает значение в PHP возвращает в Javascript возвращает значение в Java и C # не разрешено, если функция возвращает значение |
БАЗОВЫЙ | RETURN | |
Лисп | (return value) | значение последнего утверждения |
Perl, Ruby | return @values; return $value; return; или контекстная возвращаемая последовательность | значение последнего утверждения |
PL / I | return(expression); return; | неопределенное поведение, если процедура объявлена как возвращающая значение |
Python | return value | None |
Болтовня | ^ value | |
Tcl | return return $value return -code error "Error message" или какое-то более сложное сочетание опций | значение последнего утверждения |
Visual Basic.NET | Return value | |
Windows PowerShell | return value; | объект |
сборка x86 | ret | содержимое регистра eax (по соглашению) |
В некоторых языках ассемблера, например в MOS Technology 6502, используется мнемоника «RTS» (ReTurn from Subroutine).
Языки с явным оператором возврата создают возможность использования нескольких операторов возврата в одной функции. Спорный вопрос, хорошо это или нет.
Сильные сторонники структурного программирования следят за тем, чтобы каждая функция имела один вход и единственный выход (SESE). Таким образом, утверждалось, что следует избегать использования явного оператора возврата, за исключением текстового конца подпрограммы, учитывая, что, когда он используется для «раннего возврата», он может страдать от тех же проблем, которые возникают для GOTO заявление. И наоборот, можно утверждать, что использование оператора return имеет смысл, когда альтернативой является более запутанный код, такой как более глубокая вложенность, ухудшающая читаемость.
В своем учебнике 2004 года Дэвид Ватт пишет, что «часто желательны потоки управления с одним входом и множеством выходов». Используя концепцию секвенсора Теннента, Ватт единообразно описывает конструкции потока управления, встречающиеся в современных языках программирования, и пытается объяснить, почему определенные типы секвенсоров предпочтительнее других в контексте потоков управления с несколькими выходами. Ватт пишет, что неограниченные переходы (секвенсоры перехода) - это плохо, потому что назначение перехода не является самоочевидным для читателя программы, пока читатель не найдет и не изучит фактическую метку или адрес, являющийся целью перехода. В отличие от этого, Уотт утверждает, что концептуальная цель секвенсора возврата ясна из его собственного контекста, без необходимости исследовать его назначение. Более того, Уотт пишет, что класс секвенсоров, известных как escape-последовательности, определяемый как «секвенсор, который завершает выполнение команды или процедуры, содержащей текст», охватывает как разрывы из циклов (включая многоуровневые разрывы), так и операторы возврата. Ватт также отмечает, что, хотя секвенсоры переходов (gotos) были в некоторой степени ограничены в таких языках, как C, где цель должна быть внутри локального блока или охватывающим внешним блоком, одного этого ограничения недостаточно, чтобы сделать намерение gotos в C самим собой. -описание, и поэтому они все еще могут производить « спагетти-код ». Ватт также исследует, чем секвенсоры исключений отличаются от секвенсоров перехода и перехода; подробнее об этом читайте в статье о структурном программировании.
Согласно эмпирическим исследованиям, процитированным Эриком С. Робертсом, студентам-программистам было трудно сформулировать правильные решения для нескольких простых задач на языке, подобном Паскаль, который не допускает множественных точек выхода. Что касается проблемы написания функции для линейного поиска элемента в массиве, исследование 1980 года Генри Шапиро (цитируется Робертсом) показало, что при использовании только управляющих структур, предоставленных Паскалем, правильное решение было дано только 20% испытуемых., в то время как ни один субъект не написал неправильный код для этой проблемы, если разрешено писать возврат из середины цикла.
Другие, в том числе Кент Бек и Мартин Фаулер, утверждают, что одно или несколько защитных предложений - условные операторы возврата «раннего выхода» в начале функции - часто делают функцию более легкой для чтения, чем альтернативу.
Наиболее распространенная проблема при раннем выходе состоит в том, что операторы очистки или final не выполняются - например, выделенная память не является нераспределенной или открытые файлы не закрываются, что приводит к утечкам. Это необходимо делать на каждом сайте возврата, который является хрупким и может легко привести к ошибкам. Например, в более поздней разработке разработчик мог упустить из виду оператор return, и действие, которое должно быть выполнено в конце подпрограммы (например, оператор трассировки ), могло бы выполняться не во всех случаях. Языки без оператора возврата, такие как стандартный Паскаль, не имеют этой проблемы. Некоторые языки, такие как C ++ и Python, используют концепции, которые позволяют автоматически выполнять действия при возврате (или выбросе исключения), что смягчает некоторые из этих проблем - они часто известны как «попробуй / наконец» или аналогичные. Функциональность, подобная этим предложениям "finally", может быть реализована с помощью перехода к единственной точке возврата подпрограммы. Альтернативным решением является использование обычного раскручивания стека (освобождение переменных) при выходе из функции для освобождения ресурсов, например, с помощью деструкторов для локальных переменных или аналогичных механизмов, таких как оператор «with» в Python.
Некоторые ранние реализации языков, такие как исходный Pascal и C, ограничивали типы, которые могут быть возвращены функцией (например, не поддерживающие типы записей или структур ), чтобы упростить их компиляторы.
В Java - и аналогичных языках, смоделированных после нее, таких как JavaScript - возможно выполнение кода даже после оператора return, потому что всегда выполняется блок finally в структуре try-catch. Поэтому, если оператор return помещается где-то в блоках try или catch, код в finally (если он добавлен) будет выполнен. Можно даже изменить возвращаемое значение непримитивного типа (свойство уже возвращенного объекта), потому что выход также происходит позже.
Кузен для возвращения заявления является заявлением выхода : где возвращение вызывает суб рутину, чтобы прекратить, выход вызывает со днем, чтобы приостановить. Позднее сопрограмма продолжит работу с того места, где она была приостановлена, если она будет вызвана снова. Сопрограммы значительно сложнее реализовать, чем подпрограммы, и поэтому операторы yield менее распространены, чем операторы return, но они встречаются во многих языках.
В зависимости от набора аппаратных инструкций возможен ряд возможных последовательностей вызова / возврата, включая следующие:
CALL
Инструкция выталкивает адрес следующей инструкции на стеке и филиалов по указанному адресу. RETURN
Инструкция выталкивает адрес возврата из стека в указатель команд и выполнение программы продолжается по указанному адресу. (Примеры x86, PDP-11)CALL
местах команд - адрес следующей команды в регистре и филиалов по указанному адресу. Последовательность RETURN
команд помещает адрес возврата из регистра в указатель команд, и выполнение возобновляется с этого адреса. (Пример IBM System / 360)CALL
местах инструкции адрес следующего (или тока) инструкции в месте хранения по адресу вызова и филиалы по указанному адресу + 1. Последовательность RETURN
инструкций переходит к адресу возврата путем косвенного перехода к первой инструкции подпрограммы. (Примеры IBM 1130, SDS9XX)