В информатике поток управления (или поток управления ) — это порядок, в котором выполняются или оцениваются отдельные операторы , инструкции или вызовы функций императивной программы . Акцент на явном потоке управления отличает императивный язык программирования от декларативного языка программирования .
В императивном языке программирования оператор потока управления — это оператор, который приводит к выбору того, какому из двух или более путей следовать. Для нестрогих функциональных языков существуют функции и языковые конструкции для достижения того же результата, но их обычно не называют операторами потока управления.
Набор утверждений, в свою очередь, обычно структурирован как блок , который в дополнение к группировке также определяет лексическую область действия .
Прерывания и сигналы — это низкоуровневые механизмы, которые могут изменять поток управления аналогично подпрограмме , но обычно возникают в ответ на некоторый внешний стимул или событие (которое может происходить асинхронно ), а не в результате выполнения встроенного оператора потока управления.
На уровне машинного языка или языка ассемблера инструкции потока управления обычно работают путем изменения счетчика программ . Для некоторых центральных процессоров (ЦП) единственными доступными инструкциями потока управления являются инструкции условного или безусловного перехода , также называемые переходами.
Виды операторов управления потоком, поддерживаемые разными языками, различаются, но их можно классифицировать по их эффекту:
Метка — это явное имя или номер , назначенные фиксированной позиции в исходном коде , и на которые могут ссылаться операторы потока управления, появляющиеся в другом месте исходного кода. Метка отмечает позицию в исходном коде и не имеет другого эффекта.
Номера строк являются альтернативой именованной метке, используемой в некоторых языках (например, BASIC ). Это целые числа, размещаемые в начале каждой строки текста в исходном коде. Языки, которые их используют, часто накладывают ограничение, что номера строк должны увеличиваться в значении в каждой последующей строке, но могут не требовать, чтобы они были последовательными. Например, в BASIC:
10 ДАВАЙТЕ X = 3 20 ПЕЧАТЬ X
В других языках, таких как C и Ada , метка — это идентификатор , обычно появляющийся в начале строки и сразу за которым следует двоеточие. Например, в C:
Успех : printf ( "Операция прошла успешно. \n " );
Язык ALGOL 60 допускал как целые числа, так и идентификаторы в качестве меток (оба были связаны двоеточиями со следующим оператором), но немногие, если таковые вообще имелись, варианты ALGOL допускали целые числа. Ранние компиляторы Fortran допускали в качестве меток только целые числа. Начиная с Fortran-90, также были разрешены буквенно-цифровые метки.
Оператор goto (сочетание английских слов go и to , произносимых соответственно) является самой простой формой безусловной передачи управления.
Хотя ключевое слово может быть написано как заглавными, так и строчными буквами в зависимости от языка, обычно оно пишется так:
перейти к метке
Эффект оператора goto заключается в том, что следующим выполняемым оператором будет оператор, находящийся на указанной метке (или сразу после нее).
Многие специалисты по информатике, в частности Дейкстра , считали операторы goto вредными .
Терминология для подпрограмм различается; они могут также называться подпрограммами, процедурами, функциями (особенно если они возвращают результаты) или методами (особенно если они принадлежат классам или классам типов ).
В 1950-х годах компьютерная память была очень маленькой по нынешним меркам, поэтому подпрограммы использовались в основном для уменьшения размера программы. Часть кода писалась один раз, а затем использовалась много раз из разных других мест программы.
Сегодня подпрограммы чаще используются для того, чтобы сделать программу более структурированной, например, изолируя какой-то алгоритм или скрывая какой-то метод доступа к данным. Если над одной программой работает много программистов, подпрограммы являются одним из видов модульности , который может помочь разделить работу.
В структурном программировании упорядоченная последовательность последовательных команд считается одной из основных структур управления, которая используется в качестве строительного блока для программ наряду с итерацией, рекурсией и выбором.
В мае 1966 года Бём и Якопини опубликовали статью [1] в Communications of the ACM , в которой было показано, что любая программа с goto s может быть преобразована в goto-free форму, включающую только выбор (IF THEN ELSE) и циклы (WHILE condition DO xxx), возможно, с дублированным кодом и/или добавлением булевых переменных (флаги true/false). Более поздние авторы показали, что выбор может быть заменен циклами (и еще большим количеством булевых переменных).
То, что такой минимализм возможен, не означает, что он обязательно желателен; в конце концов, теоретически компьютерам нужна только одна машинная инструкция (вычесть одно число из другого и выполнить переход, если результат отрицательный), но на практике компьютеры имеют десятки или даже сотни машинных инструкций.
Статья Бёма и Якопини показала, что все программы могут быть goto-free. Другие исследования показали, что структуры управления с одним входом и одним выходом гораздо проще для понимания, чем любая другая форма, [ требуется цитата ] в основном потому, что их можно было использовать где угодно как оператор, не нарушая поток управления. Другими словами, они были компонуемыми . (Более поздние разработки, такие как нестрогие языки программирования — и совсем недавно компонуемые программные транзакции — продолжили эту стратегию, сделав компоненты программ еще более свободно компонуемыми.)
Некоторые ученые придерживались пуристского подхода к результату Бёма–Якопини и утверждали, что даже инструкции вроде break
и return
из середины циклов являются плохой практикой, поскольку они не нужны в доказательстве Бёма–Якопини, и поэтому они выступали за то, чтобы все циклы имели одну точку выхода. Этот пуристский подход воплощен в языке Pascal (разработанном в 1968–1969 годах), который до середины 1990-х годов был предпочтительным инструментом для обучения вводному программированию в академических кругах. [2] Прямое применение теоремы Бёма–Якопини может привести к введению дополнительных локальных переменных в структурированную схему, а также может привести к некоторому дублированию кода . [3] Pascal подвержен обеим этим проблемам, и согласно эмпирическим исследованиям, на которые ссылается Эрик С. Робертс , у студентов-программистов возникли трудности с формулированием правильных решений на Pascal для нескольких простых задач, включая написание функции для поиска элемента в массиве. Исследование Генри Шапиро 1980 года, на которое ссылается Робертс, показало, что при использовании только управляющих структур, предоставляемых Pascal, правильное решение дали только 20% испытуемых, в то время как ни один испытуемый не написал неправильный код для этой задачи, если ему разрешили написать возврат из середины цикла. [2]
Большинство языков программирования с управляющими структурами имеют начальное ключевое слово, которое указывает на тип задействованной управляющей структуры. [ необходимо разъяснение ] Затем языки разделяются по тому, имеют ли управляющие структуры конечное ключевое слово.
begin
...end
{
...}
DO
...END
do
...end
end
+ пробел + начальное ключевое слово, например, if
... end if
, loop
...end loop
:End
необязательно + начальное ключевое слово, например, :If
... :End
или :If
... :EndIf
, Select
... :End
или :Select
... :EndSelect
, однако, если добавить конечное условие, конечное ключевое слово становится:Until
if
... fi
, case
...esac
END
+ начальное ключевое слово, например, IF
... ENDIF
, DO
...ENDDO
END
для всегоIf
... End If
; For
... Next
; Do
... Loop
; While
...Wend
Условные выражения и условные конструкции — это функции языка программирования , которые выполняют различные вычисления или действия в зависимости от того, является ли указанное программистом логическое условие истинным или ложным.
IF..GOTO
. Форма, встречающаяся в неструктурированных языках, имитирующая типичную инструкцию машинного кода, при выполнении условия переходит к метке (GOTO) или номеру строки.IF..THEN..(ENDIF)
. Вместо того, чтобы ограничиваться прыжком, любой простой оператор или вложенный блок может следовать за ключевым словом THEN. Это структурированная форма.IF..THEN..ELSE..(ENDIF)
. Как и выше, но со вторым действием, которое должно быть выполнено, если условие ложно. Это одна из самых распространенных форм, со множеством вариаций. Некоторые требуют терминала ENDIF
, другие — нет. C и родственные языки не требуют ключевого слова терминала или «then», но требуют скобок вокруг условия.ELSE
и в , избегая необходимости иметь ряд или другие конечные операторы в конце составного оператора.IF
ELSEIF
ENDIF
Менее распространенные варианты включают в себя:
if
, например в Lisp cond
.if
, например, тернарный оператор в языке C.if
с помощью when
и unless
.ifTrue
и ifFalse
для реализации условных операторов, а не для реализации какой-либо фундаментальной языковой конструкции.Операторы Switch (или операторы case , или многоканальные ветви ) сравнивают заданное значение с указанными константами и выполняют действие в соответствии с первой совпавшей константой. Обычно есть положение для действия по умолчанию («else», «internally»), которое должно быть выполнено, если совпадение не найдено. Операторы switch могут допускать оптимизации компилятора, такие как таблицы поиска . В динамических языках случаи могут не ограничиваться константными выражениями и могут распространяться на сопоставление с образцом , как в примере скрипта оболочки справа, где *)
реализует случай по умолчанию как глоб, соответствующий любой строке. Логика случаев также может быть реализована в функциональной форме, как в операторе SQLdecode
.
Цикл — это последовательность операторов, которая указана один раз, но может быть выполнена несколько раз подряд. Код «внутри» цикла ( тело цикла, показанное ниже как xxx ) выполняется указанное количество раз, или один раз для каждого из набора элементов, или пока не будет выполнено некоторое условие, или бесконечно . Когда один из этих элементов сам по себе также является циклом, он называется «вложенным циклом». [4] [5] [6]
В функциональных языках программирования, таких как Haskell и Scheme , как рекурсивные , так и итеративные процессы выражаются с помощью хвостовых рекурсивных процедур вместо циклических конструкций, которые являются синтаксическими.
Большинство языков программирования имеют конструкции для повторения цикла определенное количество раз. В большинстве случаев счет может идти вниз, а не вверх, и могут использоваться размеры шагов, отличные от 1.
В этих примерах, если N < 1, то тело цикла может выполниться один раз (при этом I имеет значение 1) или не выполниться вообще, в зависимости от языка программирования.
Во многих языках программирования только целые числа могут надежно использоваться в цикле с управлением подсчетом. Числа с плавающей точкой представлены неточно из-за аппаратных ограничений, поэтому такой цикл, как
для X := 0.1 шаг 0.1 до 1.0 сделать
может повторяться 9 или 10 раз, в зависимости от ошибок округления и/или аппаратного обеспечения и/или версии компилятора. Более того, если приращение X происходит путем повторного сложения, накопленные ошибки округления могут означать, что значение X в каждой итерации может довольно существенно отличаться от ожидаемой последовательности 0,1, 0,2, 0,3, ..., 1,0.
В большинстве языков программирования есть конструкции для повторения цикла до тех пор, пока не изменится какое-либо условие. Некоторые вариации проверяют условие в начале цикла, другие — в конце. Если проверка находится в начале, тело можно полностью пропустить; если в конце, тело всегда выполняется хотя бы один раз.
Контрольный перерыв — это метод обнаружения изменения значения, используемый в обычных циклах для запуска обработки групп значений. Значения отслеживаются в цикле, а изменение переключает поток программы на обработку группового события, связанного с ними.
ДЕЛАТЬ ДО (конца файла) ЕСЛИ новый почтовый индекс <> текущий почтовый индекс display_tally(текущий-почтовый-индекс, zipcount) текущий-почтовый-индекс = новый-почтовый-индекс почтовый индекс = 0 КОНЕСЛИ zipcount++ ПЕТЛЯ
В нескольких языках программирования (например, Ada , D , C++11 , Smalltalk , PHP , Perl , Object Pascal , Java , C# , MATLAB , Visual Basic , Ruby , Python , JavaScript , Fortran 95 и более поздних) есть специальные конструкции, которые позволяют выполнять неявный цикл по всем элементам массива или всем членам набора или коллекции.
someCollection сделать : [:eachElement |xxx]. для элемента в коллекции do begin xxx end ; foreach (элемент; мояКоллекция) { xxx } foreach someArray { xxx } foreach ($someArray as $k => $v) { xxx } Коллекция<Строка> кол; для (Строка s : кол) {} foreach ( строка s в myStringCollection) { xxx } someCollection | ForEach-Object { $_ } forall ( индекс = первый:последний:шаг... )
В Scala есть for-выражения , которые обобщают циклы, управляемые коллекциями, а также поддерживают другие применения, такие как асинхронное программирование . В Haskell есть do-выражения и включения, которые вместе обеспечивают схожую функцию для for-выражений в Scala.
Общие итерационные конструкции, такие как for
оператор C и форма Common Lisp , do
могут использоваться для выражения любого из вышеперечисленных видов циклов, а также других, таких как параллельный цикл по некоторому количеству коллекций. В тех случаях, когда можно использовать более конкретную циклическую конструкцию, она обычно предпочтительнее общей итерационной конструкции, поскольку она часто делает цель выражения более ясной.
Бесконечные циклы используются для обеспечения того, чтобы сегмент программы зацикливался вечно или пока не возникнет исключительное условие, например ошибка. Например, управляемая событиями программа (например, сервер ) должна зацикливаться вечно, обрабатывая события по мере их возникновения, останавливаясь только тогда, когда процесс завершается оператором.
Бесконечные циклы могут быть реализованы с использованием других конструкций потока управления. Чаще всего в неструктурированном программировании это прыжок назад (goto), тогда как в структурированном программировании это неопределенный цикл (цикл while), установленный так, чтобы никогда не заканчиваться, либо путем пропуска условия, либо явным образом устанавливая его в значение true, как while (true) ...
. В некоторых языках есть специальные конструкции для бесконечных циклов, обычно путем пропуска условия из неопределенного цикла. Примерами являются Ada ( loop ... end loop
), [7] Fortran ( DO ... END DO
), Go ( for { ... }
) и Ruby ( loop do ... end
).
Часто бесконечный цикл непреднамеренно создается из-за ошибки программирования в цикле с управляемым условием, где условие цикла использует переменные, которые никогда не изменяются внутри цикла.
Иногда в теле цикла возникает желание пропустить оставшуюся часть тела цикла и продолжить со следующей итерации цикла. Некоторые языки предоставляют оператор, такой как continue
(большинство языков), skip
, [8] cycle
(Fortran) или next
(Perl и Ruby), который это сделает. Эффект заключается в преждевременном завершении самого внутреннего тела цикла и последующем возобновлении в обычном режиме со следующей итерации. Если итерация является последней в цикле, эффект заключается в раннем завершении всего цикла.
В некоторых языках, таких как Perl [9] и Ruby [10], есть redo
оператор, который перезапускает текущую итерацию с самого начала.
В Ruby есть retry
оператор, который перезапускает весь цикл с начальной итерации. [11]
При использовании цикла с контролируемым подсчетом для поиска по таблице может быть желательно остановить поиск, как только будет найден требуемый элемент. Некоторые языки программирования предоставляют оператор, такой как break
(большинство языков), Exit
(Visual Basic) или last
(Perl), который немедленно завершает текущий цикл и передает управление оператору, следующему сразу за этим циклом. Другой термин для циклов с ранним выходом — loop-and-a-half .
Следующий пример реализован на языке Ada , который поддерживает как ранний выход из циклов , так и циклы с проверкой в середине . Обе функции очень похожи, и сравнение обоих фрагментов кода покажет разницу: ранний выход должен быть объединен с оператором if , тогда как условие в середине является самодостаточной конструкцией.
с Ada.Text IO ; с Ada.Integer Text IO ;procedure Print_Squares is X : Integer ; begin Read_Data : loop Ada . Integer Text IO . Get ( X ); exit Read_Data when X = 0 ; Ada . Text IO . Put ( X * X ); Ada . Text IO . New_Line ; end loop Read_Data ; end Print_Squares ;
Python поддерживает условное выполнение кода в зависимости от того, был ли цикл завершен преждевременно (с помощью break
оператора) или нет, используя else-предложение с циклом. Например,
for n in set_of_numbers : if isprime ( n ): print ( "Набор содержит простое число" ) break else : print ( "Набор не содержит простых чисел" )
Предложение else
в приведенном выше примере связано с for
оператором, а не с внутренним if
оператором. Оба цикла Python for
и while
поддерживают такое предложение else, которое выполняется только в том случае, если не произошел ранний выход из цикла.
Некоторые языки поддерживают прерывание вложенных циклов; в теоретических кругах это называется многоуровневыми прерываниями. Одним из распространенных примеров использования является поиск в многомерной таблице. Это можно сделать либо с помощью многоуровневых прерываний (прерывание N уровней), как в bash [12] и PHP, [13], либо с помощью помеченных прерываний (прерывание и продолжение на заданной метке), как в Go, Java и Perl. [14] Альтернативы многоуровневым прерываниям включают одиночные прерывания вместе с переменной состояния, которая проверяется на прерывание другого уровня; исключения, которые перехватываются на уровне, на который выполняется прерывание; размещение вложенных циклов в функции и использование return для завершения всего вложенного цикла; или использование метки и оператора goto. C не включает многоуровневый прерывание, и обычной альтернативой является использование goto для реализации помеченного прерывания. [15] В Python нет многоуровневого прерывания или продолжения — это было предложено в PEP 3136 и отклонено на том основании, что дополнительная сложность не стоила редкого законного использования. [16]
Понятие многоуровневых разрывов представляет определенный интерес в теоретической информатике , поскольку оно порождает то, что сегодня называется иерархией Косараджу . [17] В 1973 году С. Рао Косараджу усовершенствовал теорему о структурированной программе , доказав, что можно избежать добавления дополнительных переменных в структурном программировании, если разрешены многоуровневые разрывы произвольной глубины из циклов. [18] Кроме того, Косараджу доказал, что существует строгая иерархия программ: для каждого целого числа n существует программа, содержащая многоуровневый разрыв глубины n , которую нельзя переписать как программу с многоуровневыми разрывами глубиной меньше n без введения дополнительных переменных. [17]
Можно также return
выйти из подпрограммы, выполняющей циклические операторы, выйдя из вложенного цикла и подпрограммы. Существуют и другие предлагаемые структуры управления для множественных прерываний, но они обычно реализуются как исключения.
В своем учебнике 2004 года Дэвид Уотт использует понятие секвенсора Теннента для объяснения сходства между многоуровневыми разрывами и операторами возврата. Уотт отмечает, что класс секвенсоров, известных как escape-секвенсоры , определяемый как «секвенсор, который завершает выполнение текстуально включающей команды или процедуры», охватывает как разрывы циклов (включая многоуровневые разрывы), так и операторы возврата. Однако, как правило, секвенсоры возврата могут также нести (возвращаемое) значение, тогда как секвенсор прерывания, реализованный в современных языках, обычно не может. [19]
Варианты и инварианты циклов используются для выражения корректности циклов. [20]
На практике вариант цикла — это целочисленное выражение, имеющее начальное неотрицательное значение. Значение варианта должно уменьшаться в течение каждой итерации цикла, но никогда не должно становиться отрицательным в течение правильного выполнения цикла. Варианты цикла используются для гарантии того, что циклы будут завершены.
Инвариант цикла — это утверждение, которое должно быть истинным до первой итерации цикла и оставаться истинным после каждой итерации. Это подразумевает, что при корректном завершении цикла выполняются как условие выхода, так и инвариант цикла. Инварианты цикла используются для мониторинга определенных свойств цикла во время последовательных итераций.
Некоторые языки программирования, такие как Eiffel, содержат встроенную поддержку вариантов и инвариантов циклов. В других случаях поддержка является дополнением, например, спецификация Java Modeling Language для операторов циклов в Java .
Некоторые диалекты Lisp предоставляют обширный подъязык для описания циклов. Ранний пример можно найти в Conversional Lisp из Interlisp . Common Lisp [21] предоставляет макрос Loop, который реализует такой подъязык.
while (true)
не считается бесконечным циклом для этой цели, поскольку не является специализированной языковой структурой.for (init; test; increment)
range()
while
для этого можно использовать функцию.std::for_each
шаблонная функция, которая может выполнять итерации по контейнерам STL и вызывать унарную функцию для каждого элемента. [22] Функциональность также может быть создана как макрос для этих контейнеров. [23]retry
, однако оно используется при обработке исключений , а не при управлении циклом.GO TO
и процедур.Многие языки программирования, особенно те, которые отдают предпочтение более динамичным стилям программирования, предлагают конструкции для нелокального потока управления . Они заставляют поток выполнения выпрыгивать из заданного контекста и возобновляться в некоторой заранее объявленной точке. Условия , исключения и продолжения — это три распространенных вида нелокальных конструкций управления; существуют и более экзотические, такие как генераторы , сопрограммы и ключевое слово async .
В PL/I имеется около 22 стандартных условий (например, ZERODIVIDE SUBSCRIPTRANGE ENDFILE), которые могут быть вызваны и которые могут быть прерваны с помощью: действия по условию ; Программисты также могут определять и использовать свои собственные именованные условия.
Как и в случае неструктурированного if , можно указать только один оператор, поэтому во многих случаях для принятия решения о том, где следует возобновить поток управления, требуется GOTO.
К сожалению, некоторые реализации имели существенные накладные расходы как по пространству, так и по времени (особенно SUBSCRIPTRANGE), поэтому многие программисты старались избегать использования условий.
Примеры общего синтаксиса:
Состояние ВКЛ . Метка GOTO
Современные языки имеют специализированную структурированную конструкцию для обработки исключений, которая не полагается на использование GOTO
или (многоуровневые) breaks или returns. Например, в C++ можно написать:
try { xxx1 // Где-то здесь xxx2 // use: '''throw''' someValue; xxx3 } catch ( someClass & someId ) { // catch value of someClass actionForSomeClass } catch ( someType & anotherId ) { // catch value of someType actionForSomeType } catch (...) { // catch все, что еще не перехвачено actionForAnythingElse }
Выше может быть использовано любое количество и разнообразие catch
предложений. Если нет catch
соответствия конкретному throw
, управление просачивается обратно через вызовы подпрограмм и/или вложенные блоки до тех пор, пока не будет найдено соответствие catch
или пока не будет достигнут конец основной программы, после чего программа принудительно останавливается с соответствующим сообщением об ошибке.
Благодаря влиянию C++, catch
это ключевое слово зарезервировано для объявления обработчика исключений сопоставления с образцом в других популярных сегодня языках, таких как Java или C#. Некоторые другие языки, такие как Ada, используют ключевое слово exception
для введения обработчика исключений, а затем могут даже использовать другое ключевое слово ( when
в Ada) для сопоставления с образцом. Несколько языков, таких как AppleScript, включают заполнители в синтаксис обработчика исключений для автоматического извлечения нескольких фрагментов информации при возникновении исключения. Этот подход проиллюстрирован ниже конструкцией on error
из AppleScript:
попробуйте установить myNumber в myNumber / 0 при ошибке e число n от f до t частичный результат pr если ( e = "Невозможно разделить на ноль" ) затем отобразить диалоговое окно "Вы не должны этого делать" конец попытки
Учебник Дэвида Уотта 2004 года также анализирует обработку исключений в рамках секвенсоров (представленных в этой статье в разделе о ранних выходах из циклов). Уотт отмечает, что ненормальная ситуация, обычно иллюстрируемая арифметическими переполнениями или сбоями ввода/вывода , такими как файл не найден, является разновидностью ошибки, которая «обнаруживается в некотором программном модуле низкого уровня, но [для которой] обработчик более естественно расположен в программном модуле высокого уровня». Например, программа может содержать несколько вызовов для чтения файлов, но действие, которое необходимо выполнить, когда файл не найден, зависит от значения (цели) рассматриваемого файла для программы, и, таким образом, процедура обработки для этой ненормальной ситуации не может быть расположена в системном коде низкого уровня. Уоттс далее отмечает, что введение тестирования флагов состояния в вызывающую программу, как это повлекло бы за собой структурное программирование с одним выходом или даже секвенсоры возврата (с несколькими выходами), приводит к ситуации, когда «код приложения имеет тенденцию быть загроможденным тестами флагов состояния» и что «программист может по забывчивости или лениво пропустить проверку флага состояния. Фактически, ненормальные ситуации, представленные флагами состояния, по умолчанию игнорируются!» Уоттс отмечает, что в отличие от тестирования флагов состояния исключения имеют противоположное поведение по умолчанию , заставляя программу завершаться, если программа явно не обрабатывает исключение каким-либо образом, возможно, путем добавления явного кода для его игнорирования. Основываясь на этих аргументах, Уоттс приходит к выводу, что секвенсоры переходов или секвенсоры escape менее подходят в качестве выделенного секвенсора исключений с семантикой, обсуждавшейся выше. [24]
В Object Pascal, D, Java, C# и Python finally
к конструкции можно добавить предложение try
. Независимо от того, как управление покидает конструкцию, try
код внутри finally
предложения гарантированно выполнится. Это полезно при написании кода, который должен освободить дорогостоящий ресурс (например, открытый файл или соединение с базой данных) после завершения обработки:
FileStream stm = null ; // Пример на C# try { stm = new FileStream ( "logfile.txt" , FileMode.Create ) ; return ProcessStuff ( stm ) ; // может выдать исключение } finally { if ( stm != null ) stm.Close () ; }
Поскольку этот шаблон довольно распространен, в C# предусмотрен специальный синтаксис:
using ( var stm = new FileStream ( "logfile.txt" , FileMode . Create )) { return ProcessStuff ( stm ); // может выдать исключение }
При выходе из using
-блока компилятор гарантирует, что stm
объект будет освобожден, эффективно связывая переменную с файловым потоком, абстрагируясь от побочных эффектов инициализации и освобождения файла. with
Оператор Python и аргумент блока Ruby to File.open
используются для аналогичного эффекта.
Все упомянутые выше языки определяют стандартные исключения и обстоятельства, при которых они выбрасываются. Пользователи могут выбрасывать собственные исключения; C++ позволяет пользователям выбрасывать и перехватывать почти любой тип, включая базовые типы, такие как int
, тогда как другие языки, такие как Java, менее либеральны.
В C# 5.0 введено ключевое слово async для поддержки асинхронного ввода-вывода в «прямом стиле».
Генераторы , также известные как полукорутины, позволяют временно передать управление методу-потребителю, обычно с помощью yield
ключевого слова (описание выхода). Как и ключевое слово async, это поддерживает программирование в «прямом стиле».
Сопрограммы — это функции, которые могут передавать управление друг другу — форма кооперативной многозадачности без потоков.
Сопрограммы могут быть реализованы в виде библиотеки, если язык программирования предоставляет либо продолжения, либо генераторы, поэтому на практике различие между сопрограммами и генераторами является технической деталью.
В пародийной статье Datamation [31] в 1973 году Р. Лоуренс Кларк предположил, что оператор GOTO можно заменить оператором COMEFROM , и привел несколько занимательных примеров. COMEFROM был реализован в одном эзотерическом языке программирования под названием INTERCAL .
Статья Дональда Кнута 1974 года "Structured Programming with go to Statements" [32] определяет две ситуации, которые не были охвачены перечисленными выше управляющими структурами, и приводит примеры управляющих структур, которые могли бы справиться с этими ситуациями. Несмотря на свою полезность, эти конструкции пока не нашли своего пути в основные языки программирования.
Следующее было предложено Далем в 1972 году: [33]
петля петля xxx1 прочитать(символ); пока тест; пока не вКонцеФайла; xxx2 запись(символ); повторить ; повторить ;
Если xxx1 опущено, мы получаем цикл с проверкой наверху (традиционный цикл while ). Если xxx2 опущено, мы получаем цикл с проверкой внизу, эквивалентный циклу do while во многих языках. Если while опущено, мы получаем бесконечный цикл. Конструкция здесь может рассматриваться как цикл do с проверкой while посередине. Следовательно, эта одна конструкция может заменить несколько конструкций в большинстве языков программирования.
Языки, в которых отсутствует эта конструкция, обычно эмулируют ее с помощью эквивалентной идиомы бесконечного цикла с прерыванием:
в то время как (истина) { xxx1 если ( не тест) перерыв xxx2}
Возможным вариантом является разрешение более одной проверки while внутри цикла, но использование exitwhen (см. следующий раздел), по-видимому, лучше охватывает этот случай.
В языке Ada указанная выше конструкция цикла ( loop - while - repeat ) может быть представлена с помощью стандартного бесконечного цикла ( loop - end loop ), имеющего предложение exit when в середине (не путать с оператором exitwhen в следующем разделе).
с Ada.Text_IO ; с Ada.Integer_Text_IO ;procedure Print_Squares is X : Integer ; begin Read_Data : loop Ada . Integer_Text_IO . Get ( X ); exit Read_Data when X = 0 ; Ada . Text IO . Put ( X * X ); Ada . Text IO . New_Line ; end loop Read_Data ; end Print_Squares ;
Присвоение имени циклу (например, Read_Data в этом примере) необязательно, но позволяет выйти за пределы внешнего цикла из нескольких вложенных циклов.
Эта конструкция была предложена Заном в 1974 году. [34] Здесь представлена модифицированная версия.
выход при Событии A или Событии B или Событии C; ххх выходы СобытиеA: действиеA СобытиеB: действиеB СобытиеC: действиеC endexit ;
exitwhen используется для указания событий, которые могут произойти в пределах xxx , их возникновение обозначается использованием имени события в качестве утверждения. Когда происходит какое-либо событие, выполняется соответствующее действие, а затем управление передается сразу после endexit . Эта конструкция обеспечивает очень четкое разделение между определением того, что некоторая ситуация применима, и действием, которое следует предпринять для этой ситуации.
exitwhen концептуально похож на обработку исключений , и во многих языках для этой цели используются исключения или подобные конструкции.
Следующий простой пример включает поиск определенного элемента в двумерной таблице.
выход при обнаружении или отсутствии; для I := 1 до N выполнить для J := 1 до M выполнить если таблица[I,J] = цель тогда найдено; отсутствующий; выходы найдено: print ("элемент есть в таблице"); отсутствует: печать («элемент отсутствует в таблице»); endexit ;
Один из способов атаковать часть программного обеспечения — перенаправить поток выполнения программы. Для защиты от этих атак используются различные методы целостности потока управления , включая стековые канарейки , защиту от переполнения буфера , теневые стеки и проверку указателя vtable . [35] [36] [37]