Поток управления - Control flow

В информатике , управление потоком (или поток управления ) это порядок , в котором отдельные заявления , инструкция или вызовы функций из в императивной программе будут выполнены или оценены. Акцент на явном потоке управления отличает императивный язык программирования от декларативного языка программирования .

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

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

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

На уровне машинного языка или языка ассемблера инструкции потока управления обычно работают путем изменения счетчика программ . Для некоторых центральных процессоров (ЦП) единственными доступными инструкциями потока управления являются условные или безусловные инструкции перехода , также называемые переходами .

Категории

Блок- схема, показывающая поток управления.

Типы операторов потока управления, поддерживаемые разными языками, различаются, но их можно разделить на категории по их эффекту:

  • Продолжение в другом заявлении (безусловная ветвь или прыгать)
  • Выполнение набора операторов только при соблюдении некоторого условия (выбор - т.е. условная ветвь )
  • Выполнение набора операторов ноль или более раз, пока не будет выполнено какое-либо условие (т. Е. Цикл - то же, что и условный переход )
  • Выполнение набора удаленных операторов, после которых поток управления обычно возвращается ( подпрограммы , сопрограммы и продолжения )
  • Остановка программы, предотвращение дальнейшего выполнения (безусловная остановка)

Примитивы

Этикетки

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

Номера строк являются альтернативой именованной метке, используемой в некоторых языках (например, BASIC ). Это целые числа, помещенные в начало каждой строки текста в исходном коде. Языки, в которых они используются, часто накладывают ограничение, состоящее в том, что номера строк должны увеличиваться в значении в каждой следующей строке, но могут не требовать, чтобы они были последовательными. Например, в BASIC:

10 LET X = 3
20 PRINT X

В других языках, таких как C и Ada , метка - это идентификатор , обычно появляющийся в начале строки и сразу за которым следует двоеточие. Например, в C:

Success: printf("The operation was successful.\n");

Язык АЛГОЛ 60 допускает как целые числа, так и идентификаторы в качестве меток (оба связаны двоеточием со следующим утверждением), но немногие, если вообще какие-либо другие варианты АЛГОЛА допускают целые числа. Ранние компиляторы Fortran допускали только целые числа в качестве меток. Начиная с Fortran-90, также разрешены буквенно-цифровые метки.

Перейти к

Оператор goto (комбинация английских слов go и to , произносимых соответствующим образом) является самой простой формой безусловной передачи управления.

Хотя ключевое слово может быть в верхнем или нижнем регистре в зависимости от языка, обычно оно записывается как:

   goto label

Эффект оператора goto заключается в том, что следующий оператор, который должен быть выполнен, будет оператором, появляющимся на (или сразу после) указанной метке.

Многие компьютерные ученые, особенно Дейкстра, считали утверждения Goto вредными .

Подпрограммы

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

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

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

Последовательность

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

Минимальный структурированный поток управления

В мае 1966 года Бём и Якопини опубликовали статью в « Коммуникациях ACM», в которой было показано, что любую программу с goto s можно преобразовать в форму без goto, включающую только выбор (IF THEN ELSE) и циклы (WHILE condition DO xxx), возможно с дублированным кодом и / или добавлением логических переменных (флаги true / false). Позднее авторы показали, что выбор можно заменить циклами (и тем более логическими переменными).

То, что такой минимализм возможен, не означает, что он обязательно желателен; в конце концов, компьютерам теоретически нужна только одна машинная инструкция (вычтите одно число из другого и выполните переход, если результат отрицательный), но на практике компьютеры имеют десятки или даже сотни машинных инструкций.

В статье Бема и Якопини показано, что все программы могут быть свободными от goto. Другое исследование показало, что управляющие структуры с одним входом и одним выходом намного легче понять, чем любую другую форму, в основном потому, что они могут использоваться где угодно в качестве оператора, не нарушая потока управления. Другими словами, их можно было компоновать . (Более поздние разработки, такие как нестрогие языки программирования - и в последнее время составные программные транзакции - продолжили эту стратегию, сделав компоненты программ еще более свободно компонуемыми.)

Некоторые ученые придерживались пуристического подхода к результату Бема-Якопини и утверждали, что даже инструкции типа breakи returnиз середины циклов являются плохой практикой, поскольку они не нужны в доказательстве Бема-Якопини, и поэтому они выступали за то, чтобы все циклы имели единый точка выхода. Этот пуристический подход воплощен в языке Pascal (разработанный в 1968–1969), который до середины 1990-х годов был предпочтительным инструментом для обучения вводному программированию в академических кругах. Прямое применение теоремы Бема-Якопини может привести к добавлению дополнительных локальных переменных в структурированную диаграмму, а также может привести к некоторому дублированию кода . На Паскаль влияют обе эти проблемы, и, согласно эмпирическим исследованиям, процитированным Эриком С. Робертсом , студентам-программистам было трудно сформулировать правильные решения на Паскале для нескольких простых задач, включая написание функции для поиска элемента в массиве. Исследование 1980 года Генри Шапиро, цитируемое Робертсом, показало, что при использовании только управляющих структур, предоставленных Паскалем, правильное решение было дано только 20% испытуемых, в то время как ни один из испытуемых не написал неправильный код для этой проблемы, если бы разрешили написать ответ из середина петли.

Управляющие структуры на практике

Большинство языков программирования с управляющими структурами имеют начальное ключевое слово, которое указывает тип задействованной управляющей структуры. Затем языки разделяются относительно того, есть ли у управляющих структур последнее ключевое слово.

  • Нет последнего ключевого слова: ALGOL 60 , C , C ++ , Haskell , Java , Pascal , Perl , PHP , PL / I , Python , PowerShell . Таким языкам нужен какой-то способ группировки операторов вместе:
  • Последнее ключевое слово: Ада , АЛГОЛ 68 , Модула-2 , Фортран 77 , Мифрил , Visual Basic . Формы последнего ключевого слова различаются:
    • Ada: последнее ключевое слово - это end+ пробел + начальное ключевое слово, например if... end if, loop...end loop
    • АЛГОЛ 68, мифрил: начальное ключевое слово написано в обратном порядке, например, if... fi, case...esac
    • Fortran 77: последнее ключевое слово END+ начальное ключевое слово, например, IF... ENDIF, DO...ENDDO
    • Модула-2: одно и то же последнее ключевое слово ENDдля всего
    • Visual Basic: каждая структура управления имеет собственное ключевое слово. 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или других заключительных операторов в конце составного оператора.
Паскаль : Ада : C : Сценарий оболочки : Python : Лисп :
if a > 0 then
  writeln("yes")
else
  writeln("no");
if a > 0 then
      Put_Line("yes");
else
      Put_Line("no");
end if;
if (a > 0) { 
    printf("yes");
}
else {
    printf("no");
}
if [ $a -gt 0 ]; then
      echo "yes"
else
      echo "no"
fi
if a > 0: 
    print("yes")
else:
    print("no")
(princ
  (if (plusp a)
      "yes"
      "no"))

Менее распространенные варианты включают:

  • Некоторые языки, такие как Фортран , имеют трехстороннее или арифметическое значение if , проверяющее, является ли числовое значение положительным, отрицательным или нулевым.
  • Некоторые языки имеют функциональную форму ifоператора, например, Лисп cond .
  • Некоторые языки имеют оператор форму ifзаявления, такие как C в тройном операторе .
  • Perl дополняет стиль Си ifс помощью whenи unless.
  • В Smalltalk для реализации условных выражений используются сообщения ifTrueи ifFalseсообщения, а не какие-либо фундаментальные языковые конструкции.

Операторы case и switch

Операторы переключения (или операторы case , или многосторонние ветки ) сравнивают заданное значение с указанными константами и предпринимают действия в соответствии с первой совпадающей константой. Обычно существует условие для действия по умолчанию («иначе», «иначе»), которое должно выполняться, если совпадение не увенчалось успехом. Операторы switch могут допускать оптимизацию компилятора, например таблицы поиска . В динамических языках регистры могут не ограничиваться константными выражениями и могут распространяться на сопоставление с образцом , как в примере сценария оболочки справа, где *)реализует регистр по умолчанию как глобус, соответствующий любой строке. Логика Дела также может быть реализована в функциональной форме, как в SQL «s decodeзаявлении.

Паскаль : Ада : C : Сценарий оболочки : Лисп :
case someChar of
  'a': actionOnA;
  'x': actionOnX;
  'y','z':actionOnYandZ;
  else actionOnNoMatch;
end;
case someChar is
  when 'a' => actionOnA;
  when 'x' => actionOnX;
  when 'y' | 'z' => actionOnYandZ;
  when others => actionOnNoMatch;
end;
switch (someChar) {
  case 'a': actionOnA; break;
  case 'x': actionOnX; break;
  case 'y':
  case 'z': actionOnYandZ; break;
  default: actionOnNoMatch;
}
case $someChar in 
   a)    actionOnA ;;
   x)    actionOnX ;;
   [yz]) actionOnYandZ ;;
   *)    actionOnNoMatch  ;;
esac
(case some-char
  ((#\a)     action-on-a)
  ((#\x)     action-on-x)
  ((#\y #\z) action-on-y-and-z)
  (else      action-on-no-match))

Петли

Цикл - это последовательность операторов, которая указывается один раз, но может выполняться несколько раз подряд. Код «внутри» цикла ( тело цикла, обозначенное ниже как xxx ) выполняется определенное количество раз, или один раз для каждого набора элементов, или до тех пор, пока не будет выполнено какое-либо условие, или бесконечно .

В языках функционального программирования , таких как Haskell и Scheme , циклы могут быть выражены с помощью рекурсии или итерации с фиксированной точкой, а не явных конструкций цикла. Хвостовая рекурсия - это частный случай рекурсии, который можно легко преобразовать в итерацию.

Петли с контролем счета

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

   FOR I = 1 TO N           | for I := 1 to N do begin
       xxx                  |     xxx
   NEXT I                   | end;
------------------------------------------------------------
   DO I = 1,N               | for ( I=1; I<=N; ++I ) {
       xxx                  |     xxx
   END DO                   | }

В этих примерах, если N <1, тело цикла может выполняться один раз (при I, имеющем значение 1) или не выполняться вообще, в зависимости от языка программирования.

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

   for X := 0.1 step 0.1 to 1.0 do

может повторяться 9 или 10 раз, в зависимости от ошибок округления и / или оборудования и / или версии компилятора. Кроме того, если приращение X происходит путем повторного сложения, накопленные ошибки округления могут означать, что значение X в каждой итерации может довольно значительно отличаться от ожидаемой последовательности 0,1, 0,2, 0,3, ..., 1,0.

Циклы с контролем состояния

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

   DO WHILE (test)          | repeat 
       xxx                  |     xxx 
   LOOP                     | until test;
----------------------------------------------
   while (test) {           | do
       xxx                  |     xxx
   }                        | while (test);

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

   DO UNTIL (End-of-File)
      IF new-zipcode <> current-zipcode
         display_tally(current-zipcode, zipcount)
         
         current-zipcode = new-zipcode
         zipcount = 0
      ENDIF
      
      zipcount++
   LOOP

Петли, контролируемые сбором

Некоторые языки программирования (например, Ada , D , C ++ 11 , Smalltalk , PHP , Perl , Object Pascal , Java , C # , MATLAB , Visual Basic , Ruby , Python , JavaScript , Fortran 95 и более поздние версии) имеют специальные конструкции, которые позволяют неявно перебор всех элементов массива или всех членов набора или коллекции.

   someCollection do: [:eachElement |xxx].
   for Item in Collection do begin xxx end;

   foreach (item; myCollection) { xxx }

   foreach someArray { xxx }

   foreach ($someArray as $k => $v) { xxx }

   Collection<String> coll; for (String s : coll) {}

   foreach (string s in myStringCollection) { xxx }

   someCollection | ForEach-Object { $_ }
   forall ( index = first:last:step... )

В Scala есть выражения for , которые обобщают циклы, управляемые коллекциями, а также поддерживают другие применения, такие как асинхронное программирование . В Haskell есть do-выражения и понимания, которые вместе предоставляют функции, аналогичные функциям for в Scala.

Общая итерация

Общие конструкции итераций, такие как forоператор C и форма Common Lisp , doмогут использоваться для выражения любого из вышеупомянутых видов циклов и других, таких как параллельный цикл по некоторому количеству коллекций. Там, где может использоваться более конкретная конструкция цикла, она обычно предпочтительнее общей конструкции итерации, поскольку она часто делает цель выражения более ясной.

Бесконечные петли

Бесконечные циклы используются, чтобы гарантировать, что сегмент программы будет циклически повторяться вечно или до тех пор, пока не возникнет исключительное условие, такое как ошибка. Например, программа, управляемая событиями (такая как сервер ), должна зацикливаться бесконечно, обрабатывая события по мере их возникновения, останавливаясь только тогда, когда процесс завершается оператором.

Бесконечные циклы могут быть реализованы с использованием других конструкций потока управления. Чаще всего в неструктурированном программировании это прыжок назад (goto), тогда как в структурированном программировании это неопределенный цикл (цикл while), для которого задано никогда не заканчиваться, либо путем исключения условия, либо путем явной установки его в значение true, as while (true) .... В некоторых языках есть специальные конструкции для бесконечных циклов, обычно путем исключения условия из неопределенного цикла. Примеры включают Ada ( loop ... end loop), Fortran ( DO ... END DO), Go ( for { ... }) и Ruby ( loop do ... end).

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

Продолжение со следующей итерацией

Иногда внутри тела цикла возникает желание пропустить оставшуюся часть тела цикла и продолжить следующую итерацию цикла. Некоторые языки предоставляют такие операторы, как continue(большинство языков) skip, или next(Perl и Ruby), которые будут делать это. Результатом является преждевременное завершение самого внутреннего тела цикла, а затем возобновление в обычном режиме со следующей итерацией. Если итерация является последней в цикле, результатом будет досрочное завершение всего цикла.

Повторить текущую итерацию

В некоторых языках, таких как Perl и Ruby, есть redoоператор, который перезапускает текущую итерацию с самого начала.

Цикл перезапуска

В Ruby есть retryинструкция, которая перезапускает весь цикл с начальной итерации.

Ранний выход из петель

При использовании цикла, управляемого счетчиком, для поиска в таблице может быть желательно прекратить поиск, как только будет найден требуемый элемент. Некоторые языки программирования предоставляют такие операторы, как break(большинство языков), Exit(Visual Basic) или last(Perl), результатом которых является немедленное завершение текущего цикла и передача управления оператору сразу после этого цикла. Другой термин для циклов раннего выхода - « полторы петли» .

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

with Ada.Text IO;
with 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("Set contains a prime number")
        break
else:
    print("Set did not contain any prime numbers")

Предложение elseв приведенном выше примере связано с forоператором, а не с внутренним ifоператором. И Python, forи whileциклы поддерживают такое предложение else, которое выполняется только в том случае, если не произошло раннего выхода из цикла.

Некоторые языки поддерживают выход из вложенных циклов; в теоретических кругах это называется многоуровневыми перерывами. Один из распространенных примеров использования - поиск в многомерной таблице. Это можно сделать либо с помощью многоуровневых разрывов (выход из N уровней), как в bash и PHP, либо с помощью помеченных разрывов (разрыв и продолжение с заданной меткой), как в Java и Perl. Альтернативы многоуровневым разрывам включают одиночные разрывы вместе с переменной состояния, которая проверяется для выхода на другой уровень; исключения, которые вылавливаются на разбитом уровне; размещение вложенных циклов в функции и использование return для завершения всего вложенного цикла; или используя метку и инструкцию goto. C не включает многоуровневый разрыв, и обычная альтернатива - использовать goto для реализации помеченного разрыва. Python не имеет многоуровневого прерывания или продолжения - это было предложено в PEP 3136 и отклонено на том основании, что дополнительная сложность не стоит редкого законного использования.

Понятие многоуровневых разрывов представляет определенный интерес для теоретической информатики , поскольку оно порождает то, что сегодня называется иерархией Косараджу . В 1973 году С. Рао Косараджу усовершенствовал теорему о структурированной программе , доказав, что можно избежать добавления дополнительных переменных в структурированное программирование, если разрешены многоуровневые прерывания произвольной глубины из циклов. Кроме того, Косараджу доказал, что существует строгая иерархия программ: для каждого целого числа n существует программа, содержащая многоуровневый разрыв глубины n, которую нельзя переписать как программу с многоуровневыми разрывами глубины меньше n без введения дополнительных переменные.

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

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

Варианты и инварианты петель

Петля варианты и инварианты петли используются , чтобы выразить правильность петель.

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

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

Некоторые языки программирования, такие как Eiffel, содержат встроенную поддержку вариантов и инвариантов цикла. В других случаях поддержка является надстройкой, такой как спецификация языка моделирования Java для операторов цикла в Java .

Подъязык цикла

Некоторые диалекты Lisp предоставляют обширный подъязык для описания циклов. Ранний пример можно найти в Конверсионном Лиспе Interlisp . Common Lisp предоставляет макрос Loop, который реализует такой подъязык.

Таблица перекрестных ссылок петлевой системы

Язык программирования условный петля ранний выход продолжение цикла повторить повторить попытку средства корректности
начинать середина конец считать коллекция Общее бесконечный вариант инвариантный
Ада да да да да массивы Нет да глубоко вложенный Нет
APL да Нет да да да да да глубоко вложенный да Нет Нет
C да Нет да Нет Нет да Нет глубоко вложенный глубоко вложенный Нет
C ++ да Нет да Нет да да Нет глубоко вложенный глубоко вложенный Нет
C # да Нет да Нет да да Нет глубоко вложенный глубоко вложенный
КОБОЛ да Нет да да Нет да Нет глубоко вложенный глубоко вложенный Нет
Common Lisp да да да да только встроенный да да глубоко вложенный Нет
D да Нет да да да да да глубоко вложенный глубоко вложенный Нет
Эйфелева да Нет Нет да да да Нет один уровень Нет Нет Нет только целое число да
F # да Нет Нет да да Нет Нет Нет Нет Нет
FORTRAN 77 да Нет Нет да Нет Нет Нет один уровень да
Фортран 90 да Нет Нет да Нет Нет да глубоко вложенный да
Fortran 95 и более поздние версии да Нет Нет да массивы Нет да глубоко вложенный да
Haskell Нет Нет Нет Нет да Нет да Нет Нет Нет
Джава да Нет да Нет да да Нет глубоко вложенный глубоко вложенный Нет не родной не родной
JavaScript да Нет да Нет да да Нет глубоко вложенный глубоко вложенный Нет
Естественный да да да да Нет да да да да да Нет
OCaml да Нет Нет да массивы, списки Нет Нет Нет Нет Нет
PHP да Нет да Нет да да Нет глубоко вложенный глубоко вложенный Нет
Perl да Нет да Нет да да Нет глубоко вложенный глубоко вложенный да
Python да Нет Нет Нет да Нет Нет глубоко вложенный глубоко вложенный Нет
REBOL Нет да да да да Нет да один уровень Нет Нет
Рубин да Нет да да да Нет да глубоко вложенный глубоко вложенный да да
Стандартный ML да Нет Нет Нет массивы, списки Нет Нет Нет Нет Нет
Visual Basic .NET да Нет да да да Нет да один уровень на каждый тип петли один уровень на каждый тип петли
PowerShell да Нет да Нет да да Нет ? да
  1. while (true)Для этой цели a не считается бесконечным циклом, потому что это не специальная языковая структура.
  2. a b c d e f g h Цикл C- это общая конструкция цикла, а не конкретно счетная, хотя она часто используется для этого.for (init; test; increment)
  3. a b c Глубокие разрывы могут быть выполнены в APL, C, C ++ и C # с помощью меток и gotos.
  4. Итерация над объектами быладобавленав PHP 5.
  5. a b c Цикл подсчета можно смоделировать, перебирая увеличивающийся список или генератор, например, Pythonrange().
  6. a b c d e Глубокие перерывы могут быть выполнены с помощью обработки исключений.
  7. a Специальной конструкции нет, так какwhileдля этого можно использовать функцию.
  8. a Специальной конструкции нет, но пользователи могут определять общие функции цикла.
  9. a СтандартC ++ 11представилдля. ВSTLестьфункция-std::for_each шаблон,которая может выполнять итерацию поконтейнерамSTLи вызыватьунарную функциюдля каждого элемента. Функциональность также может быть построена в видемакросадля этих контейнеров.
  10. Count-контролируемое подключение петли осуществляется путем итераций через целочисленный интервал; досрочный выход с включением дополнительного условия выхода.
  11. Eiffel поддерживает зарезервированное словоretry, однако он используется вобработке исключений,не управление циклом.
  12. a Требуетсяязыкспецификации интерфейса поведенияJava Modeling Language(JML).
  13. a Требует, чтобы варианты цикла были целыми числами; трансфинитные варианты не поддерживаются. [1]
  14. a D поддерживает бесконечные коллекции и возможность перебирать эти коллекции. Для этого не требуется особой конструкции.
  15. Глубокие разрывы могут быть достигнутыиспользованииGO TOи процедуры.
  16. Common Lisp предшествует понятие общего типа коллекции.

Структурированный нелокальный поток управления

Многие языки программирования, особенно те, которые отдают предпочтение более динамичным стилям программирования, предлагают конструкции для нелокального потока управления . Это заставляет поток выполнения выскакивать из заданного контекста и возобновляться в некоторой заранее объявленной точке. Условия , исключения и продолжения - это три распространенных вида нелокальных управляющих конструкций; существуют и более экзотические, такие как генераторы , сопрограммы и ключевое слово async .

Условия

PL / I имеет около 22 стандартных условий (например, ZERODIVIDE SUBSCRIPTRANGE ENDFILE), которые могут быть вызваны и которые могут быть перехвачены: действием условия ON ; Программисты также могут определять и использовать свои собственные именованные условия.

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

К сожалению, некоторые реализации имели значительные накладные расходы как в пространстве, так и во времени (особенно SUBSCRIPTRANGE), поэтому многие программисты пытались избегать использования условий.

Общие примеры синтаксиса:

 ON condition GOTO label

Исключения

Современные языки имеют специализированную структурированную конструкцию для обработки исключений, которая не полагается на использование GOTOили (многоуровневых) разрывов или возвратов. Например, в C ++ можно написать:

try {
    xxx1                                  // Somewhere in here
    xxx2                                  //     use: '''throw''' someValue;
    xxx3
} catch (someClass& someId) {             // catch value of someClass
    actionForSomeClass 
} catch (someType& anotherId) {           // catch value of someType
    actionForSomeType
} catch (...) {                           // catch anything not already caught
    actionForAnythingElse
}

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

Благодаря влиянию C ++, catchэто ключевое слово зарезервировано для объявления обработчика исключений сопоставления с образцом в других популярных сегодня языках, таких как Java или C #. Некоторые другие языки, такие как Ada, используют ключевое слово exceptionдля введения обработчика исключений, а затем могут даже использовать другое ключевое слово ( whenв Ada) для сопоставления с образцом. Некоторые языки, такие как AppleScript, включают заполнители в синтаксис обработчика исключений для автоматического извлечения нескольких фрагментов информации при возникновении исключения. Этот подход проиллюстрирован ниже on errorконструкцией из AppleScript:

try
    set myNumber to myNumber / 0
on error e  number n  from f  to t  partial result pr
    if ( e = "Can't divide by zero" ) then display dialog "You must not do that"
end try

В учебнике Дэвида Ватта 2004 г. также анализируется обработка исключений в рамках секвенсоров (представленных в этой статье в разделе о ранних выходах из циклов). Ватт отмечает, что ненормальная ситуация, обычно иллюстрируемая арифметическим переполнением или ошибками ввода / вывода, например, файл не найден, является своего рода ошибкой, которая «обнаруживается в некотором программном модуле низкого уровня, но [для которой] обработчик более естественно расположен в программном блоке высокого уровня ". Например, программа может содержать несколько вызовов для чтения файлов, но действие, которое нужно выполнить, когда файл не найден, зависит от значения (цели) файла, о котором идет речь в программе, и, следовательно, процедура обработки этой ненормальной ситуации не может быть находится в системном коде нижнего уровня. Уоттс также отмечает, что введение тестирования флагов состояния в вызывающей стороне, как это повлечет за собой структурированное программирование с одним выходом или даже (с несколькими выходами) секвенсоры возврата, приводит к ситуации, когда «код приложения имеет тенденцию быть загроможденным тестами флагов состояния» и что «программист может по забывчивости или лениво пропустить проверку флага состояния. Фактически, ненормальные ситуации, представленные флагами состояния, по умолчанию игнорируются!» Ватт отмечает, что в отличие от тестирования флагов состояния, исключения имеют противоположное поведение по умолчанию , вызывая завершение программы, если только программист явно не обработает исключение каким-либо образом, возможно, путем добавления явного кода для его игнорирования. Основываясь на этих аргументах, Ватт приходит к выводу, что секвенсоры переходов или управляющие секвенсоры не так подходят, как выделенный секвенсор исключений с семантикой, рассмотренной выше.

В Object Pascal, D, Java, C # и Python к конструкции finallyможно добавить предложение try. Независимо от того, как управление уходит, tryкод внутри finallyпредложения будет выполняться гарантированно. Это полезно при написании кода, который должен освободить дорогостоящий ресурс (например, открытый файл или соединение с базой данных) после завершения обработки:

FileStream stm = null;                    // C# example
try
{
    stm = new FileStream("logfile.txt", FileMode.Create);
    return ProcessStuff(stm);             // may throw an exception
} 
finally
{
    if (stm != null)
        stm.Close();
}

Поскольку этот шаблон довольно распространен, C # имеет особый синтаксис:

using (var stm = new FileStream("logfile.txt", FileMode.Create))
{
    return ProcessStuff(stm); // may throw an exception
}

После выхода из using-block компилятор гарантирует, что stmобъект освобожден, эффективно привязывая переменную к файловому потоку, абстрагируясь от побочных эффектов инициализации и освобождения файла. withОператор Python и аргумент блока Ruby File.openиспользуются с аналогичным эффектом.

Все упомянутые выше языки определяют стандартные исключения и обстоятельства, при которых они возникают. Пользователи могут создавать собственные исключения; на самом деле C ++ позволяет пользователям отбрасывать и ловить практически любой тип, включая базовые типы int, в то время как другие языки, такие как Java, не столь разрешительны.

Продолжение

Асинхронный

В C # 5.0 появилось ключевое слово async для поддержки асинхронного ввода-вывода в «прямом стиле».

Генераторы

Генераторы , также известные как полупрограммы, позволяют временно передавать управление методу потребителя, обычно используя yieldключевое слово ( описание выхода ). Подобно ключевому слову async, он поддерживает программирование в «прямом стиле».

Сопрограммы

Сопрограммы - это функции, которые могут передавать друг другу управление - форма совместной многозадачности без потоков.

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

Перекрестная ссылка нелокального потока управления

Язык программирования условия исключения генераторы / сопрограммы асинхронный
Ада Нет да ? ?
C Нет Нет Нет Нет
C ++ Нет да да, используя BOOST ?
C # Нет да да да
КОБОЛ да да Нет Нет
Common Lisp да Нет ? ?
D Нет да да ?
Эйфелева Нет да ? ?
Erlang Нет да да ?
F # Нет да да да
Идти Нет да да ?
Haskell Нет да да Нет
Джава Нет да Нет Нет
JavaScript ? да да да
Цель-C Нет да Нет ?
PHP Нет да да ?
PL / I да Нет Нет Нет
Python Нет да да да
REBOL да да Нет ?
Рубин Нет да да ?
Ржавчина Нет да экспериментальный да
Скала Нет да через экспериментальное расширение через экспериментальное расширение
Tcl по следам да да через цикл событий
Visual Basic .NET да да Нет ?
PowerShell Нет да Нет ?

Предлагаемые структуры управления

В фальшивой статье Datamation в 1973 году Р. Лоуренс Кларк предложил заменить оператор GOTO оператором COMEFROM и привел несколько занимательных примеров. COMEFROM был реализован на одном эзотерическом языке программирования под названием INTERCAL .

В статье Дональда Кнута 1974 г. «Структурированное программирование с использованием операторов перехода» определены две ситуации, которые не были охвачены перечисленными выше управляющими структурами, и приведены примеры управляющих структур, которые могут справиться с этими ситуациями. Несмотря на их полезность, эти конструкции еще не нашли своего применения в основных языках программирования.

Петля с тестом посередине

Следующее было предложено Далем в 1972 году:

   loop                           loop
       xxx1                           read(char);
   while test;                    while not atEndOfFile;
       xxx2                           write(char);
   repeat;                        repeat;

Если xxx1 опущен, то мы получим петлю с тестом на вершине (традиционная в то время как петля). Если xxx2 опущено, мы получаем цикл с тестом внизу, что эквивалентно циклу do while на многих языках. Если опустить while , мы получим бесконечный цикл. Конструкцию здесь можно представить как цикл do с проверкой while посередине. Следовательно, эта единственная конструкция может заменить несколько конструкций в большинстве языков программирования.

Языки, в которых отсутствует эта конструкция, обычно эмулируют ее, используя эквивалентную идиому бесконечного цикла с прерыванием:

while (true) {
    xxx1
    if (not test)
        break
    xxx2
}

Возможный вариант - разрешить более одного теста while ; внутри цикла, но использование exitwhen (см. следующий раздел), кажется, лучше охватывает этот случай.

В Ada указанная выше конструкция цикла ( цикл - while - repeat ) может быть представлена ​​с использованием стандартного бесконечного цикла ( цикл - конец цикла ), в котором есть предложение exit when посередине (не путать с оператором exitwhen в следующем разделе. ).

with Ada.Text_IO;
with 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 году. Здесь представлена ​​модифицированная версия.

   exitwhen EventA or EventB or EventC;
       xxx
   exits
       EventA: actionA
       EventB: actionB
       EventC: actionC
   endexit;

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

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

Следующий простой пример включает поиск определенного элемента в двумерной таблице.

   exitwhen found or missing;
       for I := 1 to N do
           for J := 1 to M do
               if table[I,J] = target then found;
       missing;
   exits
       found:   print ("item is in table");
       missing: print ("item is not in table");
   endexit;

Безопасность

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

Смотрите также

использованная литература

дальнейшее чтение

  • Хоар, CAR «Разделение: алгоритм 63», «Быстрая сортировка: алгоритм 64» и «Найти: алгоритм 65». Comm. АСМ 4, 321-322, 1961.

внешние ссылки