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

Из Википедии, бесплатной энциклопедии

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

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

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

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

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

Категории [ править ]

Диаграмма состояний процесса поиска карт масс пептидных ионов.

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

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

Примитивы [ править ]

Этикетки [ править ]

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

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

10   БУКУС   X   =   3 
 20   ПЕЧАТЬ   X 

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

Успех  :   printf  (  «Операция прошла успешно.  \n  »  ); 

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

Перейти [ править ]

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

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

   перейти к   ярлыку 

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

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

Подпрограммы [ править ]

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

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

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

Последовательность [ править ]

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

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

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

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

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

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

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

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

  • Нет финального ключевого слова: ALGOL 60 , C , C++ , Haskell , Java , Pascal , Perl , PHP , PL/I , Python , PowerShell . Таким языкам нужен какой-то способ группировки операторов:
  • Последнее ключевое слово: Ada , APL , ALGOL 68 , Modula-2 , Fortran 77 , Mythryl , Visual Basic . Формы конечного ключевого слова различаются:
    • Ада: последнее ключевое слово end + пробел + начальное ключевое слово, например, if ... end if, loop ... end loop
    • APL: последнее ключевое слово :End опционально + начальное ключевое слово, например, :If ... :End или :If ... :EndIf, Select ... :End или :Select ... :EndSelect, однако, если добавить конечное условие, конечное ключевое слово станет :Until
    • АЛГОЛ 68, Мифрил: начальное ключевое слово написано наоборот, например: if ... fi, case ... esac
    • Фортран 77: последнее ключевое слово END + начальное ключевое слово, например, IF ... ENDIF, DO ... ENDDO
    • Модуль-2: то же последнее ключевое слово END За все
    • Visual Basic: каждая структура управления имеет свое ключевое слово. If ... End If; For ... Next; Do ... Loop; While ... Wend

Выбор [ править ]

Операторы if-then-(else) [ править ]

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

  • IF..GOTO. Форма, встречающаяся в неструктурированных языках, имитирующая типичную инструкцию машинного кода, будет переходить к (GOTO) метке или номеру строки, когда условие будет выполнено.
  • IF..THEN..(ENDIF). Вместо того, чтобы ограничиваться переходом, любой простой оператор или вложенный блок может следовать за ключевым словом THEN. Это структурированная форма.
  • IF..THEN..ELSE..(ENDIF). То же, что и выше, но со вторым действием, которое необходимо выполнить, если условие ложно. Это одна из самых распространенных форм, имеющая множество вариаций. Некоторым требуется терминал ENDIF, другие нет. C и родственные языки не требуют ключевого слова терминала или «то», но требуют круглых скобок вокруг условия.
  • Условные операторы могут быть и часто вложены в другие условные операторы. Некоторые языки позволяют ELSE и IF быть объединены в ELSEIF, избегая необходимости иметь серию ENDIF или другие заключительные утверждения в конце составного утверждения.
Паскаль : Есть :
если   a   >   0,   то 
   writeln  (  "  да  "  ) 
 else 
   writeln  (  "  нет  "  )  ; 
если   a   >   0   , то 
       Put_Line  (  «да»  ); 
  еще 
       Put_Line  (  «нет»  ); 
  конец   , если  ; 
С : Шелл-скрипт :
if   (  a   >   0  )   {  
     puts  (  «да»  ); 
  } 
 Еще   { 
     ставит  (  "нет"  ); 
  } 
если   [   $a   -gt   0   ]  ;    тогда 
       эхо   «да», 
 иначе 
       эхо   «нет» 
 фи 
Питон : Лисп :
если   a   >   0  :  
     распечатать  (  «да»  ), 
 иначе  : 
     распечатать  (  «нет»  ) 
(  princ 
   (  if   (  plusp   a  ) 
       «да» 
       «нет»  ) )) 

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

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

Операторы Case и Switch [ править ]

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

Паскаль : Есть :
случай   someChar   of 
   'a'  :   actionOnA  ; 
    'х'  :   действиеOnX  ; 
    'y'  ,  'z'  :  actionOnYandZ  ; 
    еще   действиеOnNoMatch  ; 
  конец  ; 
случай   someChar   — это 
   когда   '  a  '   =>   actionOnA  ; 
    когда   '  x  '   =>   actionOnX  ; 
    когда   '  у  '   |    '  z  '   =>   actionOnYandZ  ; 
    когда   другие   =>   actionOnNoMatch  ; 
  конец  ; 
С : Шелл-скрипт :
переключатель   (  someChar  )   { 
   case   'a'  :   actionOnA  ;    перерыв  ; 
    случай   'x'  :   actionOnX  ;    перерыв  ; 
    случай   'y'  : 
   случай   'z'  :   actionOnYandZ  ;    перерыв  ; 
    по умолчанию  :   actionOnNoMatch  ; 
  } 
случай   $someChar   в  
    a  )      actionOnA   ;; 
     x  )      действиеOnX   ;; 
     [  yz  ])   actionOnYandZ   ;; 
     *  )      actionOnNoMatch    ;; 
  Эсак 
Лисп : Фортран :
(  case   some-char 
   ((  #\a  )       action-on-a  ) 
   ((  #\x  )       action-on-x  ) 
   ((  #\y   #\z  )   action-on-y-and-z  ) 
   (  else        действие без совпадений  )) 
выберите регистр   (  someChar  ) 
   регистр   (  'a'  ) 
     actionOnA 
   регистр   (  'x'  ) 
     actionOnX 
   регистр   (  'y'  ,  'z'  ) 
     actionOnYandZ 
   регистр  по умолчанию 
     actionOnNoMatch 
 end select 

Петли [ править ]

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

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

Петли, управляемые счетом [ править ]

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

ДЛЯ I = 1 К N
    ххх
 СЛЕДУЮЩИЙ Я
 
для  I := от  до  N  начните   1 
     ххх 
  конец  ; 
 
ДО I = 1,N
     ххх
 КОНЕЦ ДЕЛАТЬ
 
для  ( I=1; I<=N; ++I ) { 
      ххх 
  } 
 

В этих примерах, если 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 (текущий почтовый индекс, почтовый индекс)
         
          текущий почтовый индекс = новый почтовый индекс
          почтовый индекс = 0
       КОНДИФ
      
       zipcount++
    ПЕТЛЯ
 

Циклы, управляемые коллекцией [ править ]

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

someCollection  do  : [:eachElement |xxx]. 

     для  элемента  в  коллекции  do   Begin  xxx  End  ; 

     foreach  (элемент; myCollection) { xxx } 

     Еогеасп  someArray { xxx } 

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

     Коллекция<String> coll;   for  (String s: coll) {} 

     foreach  (  строка  s  в  myStringCollection) { xxx } 

     некотораяКоллекция |   ForEach-Object { $_ } 

     forall  (индекс = первый:последний:шаг...) 
 

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

Общая итерация [ править ]

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

Бесконечные циклы [ править ]

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

Бесконечные циклы могут быть реализованы с использованием других конструкций потока управления. Чаще всего в неструктурированном программировании это переход назад (goto), тогда как в структурированном программировании это бесконечный цикл (цикл while), который никогда не заканчивается, либо путем пропуска условия, либо явно устанавливая для него значение true, как while (true) .... В некоторых языках есть специальные конструкции для бесконечных циклов, обычно путем исключения условия из неопределенного цикла. Примеры включают Аду ( loop ... end loop), [7] Фортран ( DO ... END DO), Идти ( for { ... }) и Руби ( loop do ... end).

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

Продолжение следующей итерации [ править ]

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

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

Некоторые языки, например Perl [9] и Руби, [10] иметь redo оператор, который перезапускает текущую итерацию с самого начала.

Перезапустить цикл [ править ]

У Руби есть retry оператор, который перезапускает весь цикл с начальной итерации. [11]

Ранний выход из циклов [ править ]

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

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

с   Ada.Text   IO  ; 
  с   Ada.Integer   Text   IO  ; 

  процедура   Print_Squares   равна  
     X   :   Integer  ; 
  начать 
     Read_Data   :   цикл 
         Ada  .   Целочисленный   текстовый   ввод-вывод  .   Получить  (  Х  ); 
      выйти из   Read_Data   , когда   X   =   0  ; 
          Ада  .   Текст   ИО  .   Поместите   (  Х   *   Х  ); 
          Ада  .   Текст   ИО  .   Новая линия  ; 
      завершить   цикл   Read_Data  ; 
  конец   Print_Squares  ; 

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

for   n   в   set_of_numbers  : 
     if   isprime  (  n  ): 
         print  (  «Набор содержит простое число»  ), 
         Break 
 else  : 
     print  (  «Набор не содержит простых чисел»  ) 

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

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

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

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

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

Варианты цикла и инварианты [ править ]

Варианты цикла и инварианты цикла используются для выражения правильности циклов. [20]

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

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

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

Циклический подъязык [ править ]

Некоторые диалекты Лиспа предоставляют расширенный подъязык для описания циклов. Ранний пример можно найти в Conversional Lisp of Interlisp . Общий Лисп [21] предоставляет макрос Loop, который реализует такой подъязык.

Таблица перекрестных ссылок системы контуров [ править ]

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

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

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

Условия [ править ]

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

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

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

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

 Состояние   ВКЛ   GOTO   Метка 

Исключения [ править ]

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

try   { 
     xxx1                                    // Где-то здесь 
     xxx2                                    // используйте: '''throw''' someValue; 
      xxx3 
 }   catch   (  someClass  &   someId  )   {               // перехватываем значение someClass 
     actionForSomeClass  
 }   catch   (  someType  &   otherId  )   {             // перехватываем значение someType 
     actionForSomeType 
 }   catch   (...)   {                             // перехватываем все, что еще не перехвачено 
     actionForAnythingElse 
 } 

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

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

попробуйте 
     установить   myNumber   в   myNumber   /   0 
 при   ошибке   e    номер   n    от   f    до   t    частичный   результат   pr 
     if   (   e   =   «Невозможно разделить на ноль»   ),   затем   отобразить диалоговое окно   «Вы не должны этого делать» и 
 завершить   попытку 

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

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

FileStream   stm   =   ноль  ;                       // Пример C# 
 try 
 { 
     stm   =   new   FileStream  (  "logfile.txt  ,   FileMode.Create  "  )  ; 
      вернуть   ProcessStuff  (  stm  );                // может вызвать исключение 
 }  
 наконец 
 { 
     if   (  stm   !=   null  ) 
         stm  .   Закрывать  (); 
  } 

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

использование   (  вар   stm   =   новый   FileStream  (  «logfile.txt»  ,   FileMode  .  Create  )) 
 { 
     return   ProcessStuff  (  stm  );    // может вызвать исключение 
 } 

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

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

Продолжение [ править ]

Асинхронный [ править ]

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

Генераторы [ править ]

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

Сопрограммы [ править ]

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

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

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

Язык программирования условия исключения генераторы/сопрограммы асинхронный
Есть Нет Да ? ?
С Нет Нет Нет Нет
С++ Нет Да Да ?
С# Нет Да Да Да
КОБОЛ Да Да Нет Нет
Общий Лисп Да Нет ? ?
Д Нет Да Да ?
Эйфелева Нет Да ? ?
Эрланг Нет Да Да ?
Ф# Нет Да Да Да
Идти Нет Да Да ?
Хаскелл Нет Да Да Нет
Джава Нет Да Нет Нет
JavaScript ? Да Да Да
Цель-C Нет Да Нет ?
PHP Нет Да Да ?
ПЛ/Я Да Нет Нет Нет
Питон Нет Да Да Да [25]
Ребол Да Да Нет ?
Рубин Нет Да Да через расширение [26]
Ржавчина Нет Да экспериментальный [27] [28] Да [29]
Скала Нет Да через экспериментальное расширение [30] через экспериментальное расширение
Ткл по следам Да Да через цикл событий
Визуальный Бейсик .NET Да Да Нет ?
PowerShell Нет Да Нет ?

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

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

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

Петля с тестом посередине [ править ]

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

   петля                             петля 
         xxx1 чтение (символ); 
     во время  теста;   пока   не  в EndOfFile; 
         xxx2 запись (символ); 
     повторить  ;   повторить  ; 
 

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

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

в то время как  (истина) { 
      ххх1 
      если  (  не  тестировать) 
          перерыв 
      ххх2 
  } 
 

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

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

с   Ada.Text_IO  ; 
  с   Ada.Integer_Text_IO  ; 

  процедура   Print_Squares   равна  
     X   :   Integer  ; 
  начать 
     Read_Data   :   цикл 
         Ada  .   Целое_Текст_IO  .   Получить  (  Х  ); 
      выйти из   Read_Data   , когда   X   =   0  ; 
          Ада  .   Текст   ИО  .   Поместите   (  Х   *   Х  ); 
          Ада  .   Текст   ИО  .   Новая линия  ; 
      завершить   цикл   Read_Data  ; 
  конец   Print_Squares  ; 

Именование цикла (например, Read_Data в этом примере) не является обязательным, но позволяет оставить внешний цикл из нескольких вложенных циклов.

Множественный ранний выход/выход из вложенных циклов [ править ]

Эта конструкция была предложена Заном в 1974 году. [34] Модифицированная версия представлена ​​здесь.

   выйти, когда  EventA  или  EventB  или  EventC; 
         ххх 
     выходит 
         Событие А: действие А 
         Событие Б: действие Б 
         СобытиеC: действиеC 
     конечный выход  ; 
 

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

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

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

   выход  при обнаружении  или  пропаже; 
         для  I := от 1  до  N  сделать 
            для  J := от 1  до  M  сделать 
                если  таблица[I,J] = цель,  затем  найдена; 
         отсутствующий; 
     выходит 
         найдено: печать ("элемент находится в таблице"); 
         отсутствует: печать («элемента нет в таблице»); 
     конечный выход  ; 
 

Безопасность [ править ]

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

См. также [ править ]

Ссылки [ править ]

  1. ^ Бём, Якопини. «Блок-схемы, машины Тьюринга и языки только с двумя правилами формирования» Комм. ACM , 9(5):366-371, май 1966 г.
  2. ^ Перейти обратно: а б Робертс, Э. [1995] « Выходы из цикла и структурированное программирование: возобновление дебатов, заархивировано 25 июля 2014 г. в Wayback Machine », Бюллетень ACM SIGCSE, (27)1: 268–272.
  3. ^ Дэвид Энтони Ватт; Уильям Финдли (2004). Концепции проектирования языков программирования . Джон Уайли и сыновья. п. 228. ИСБН  978-0-470-85320-7 .
  4. ^ «Вложенные циклы в C с примерами» . Гики для Гиков . 25 ноября 2019 г. Проверено 14 марта 2024 г.
  5. ^ «Вложенные циклы Python» . www.w3schools.com . Проверено 14 марта 2024 г.
  6. ^ Дин, Дженна (22 ноября 2019 г.). «Вложенные циклы» . Стартап . Проверено 14 марта 2024 г.
  7. ^ Программирование на языке Ada: Управление: бесконечный цикл
  8. ^ «Что такое циклы и как их использовать?» . Архивировано из оригинала 28 июля 2020 г. Проверено 25 мая 2020 г.
  9. ^ «повторить — perldoc.perl.org» . perldoc.perl.org . Проверено 25 сентября 2020 г.
  10. ^ «control_expressions — Документация для Ruby 2.4.0» . docs.ruby-lang.org . Проверено 25 сентября 2020 г.
  11. ^ «control_expressions — Документация для Ruby 2.3.0» . docs.ruby-lang.org . Проверено 25 сентября 2020 г.
  12. ^ Расширенное руководство по написанию сценариев Bash: 11.3. Контурное управление
  13. ^ Руководство по PHP: « перерыв »
  14. ^ perldoc: последний
  15. ^ comp.lang.c Список часто задаваемых вопросов · « Вопрос 20.20b »
  16. ^ [Python-3000] Анонс PEP 3136 , Гвидо ван Россум
  17. ^ Перейти обратно: а б Козен, Декстер (2008). «Теорема Бема – Якопини ложна с теоретической точки зрения». Математика построения программ (PDF) . Конспекты лекций по информатике. Том. 5133. стр. 177–192. CiteSeerX   10.1.1.218.9241 . дои : 10.1007/978-3-540-70594-9_11 . ISBN  978-3-540-70593-2 .
  18. ^ Косараджу, С. Рао. «Анализ структурированных программ», Учеб. Пятый ежегодный сироп ACM. Теория вычислений (май 1973 г.), 240–252; также в журнале «Компьютерные и системные науки», 9, 3 (декабрь 1974 г.). цитируется Кнут, Дональд (1974). «Структурное программирование с переходом к операторам». Вычислительные опросы . 6 (4): 261–301. CiteSeerX   10.1.1.103.6084 . дои : 10.1145/356635.356640 . S2CID   207630080 .
  19. ^ Дэвид Энтони Ватт; Уильям Финдли (2004). Концепции проектирования языков программирования . Джон Уайли и сыновья. стр. 215–221. ISBN  978-0-470-85320-7 .
  20. ^ Мейер, Бертран (1991). Эйфель: Язык . Прентис Холл. стр. 129–131.
  21. ^ «Общий макрос Lisp LOOP» .
  22. ^ for_each . Sgi.com. Проверено 9 ноября 2010 г.
  23. ^ Глава 1. Boost.Foreach. Архивировано 29 января 2010 г. в Wayback Machine . Boost-sandbox.sourceforge.net (19 декабря 2009 г.). Проверено 9 ноября 2010 г.
  24. ^ Дэвид Энтони Ватт; Уильям Финдли (2004). Концепции проектирования языков программирования . Джон Уайли и сыновья. стр. 221–222. ISBN  978-0-470-85320-7 .
  25. ^ «Asyncio: асинхронный ввод-вывод: документация Python 3.10.2» .
  26. ^ «Сокет/Асинхронный» . Гитхаб . 25 февраля 2022 г.
  27. ^ «Генераторы — книга о нестабильной ржавчине» .
  28. ^ «Корона-Ржавчина» .
  29. ^ «Начало работы — асинхронное программирование на Rust» .
  30. ^ «Джитси Встреча» . Storm-enroute.com . Проверено 7 сентября 2022 г.
  31. ^ Мы не знаем, куда ПЕРЕЙТИ, если не знаем, ОТКУДА мы ПРИШЛИ. Эта (пародия) лингвистическая инновация оправдывает все ожидания. Архивировано 16 июля 2018 г. в Wayback Machine Р. Лоуренсом Кларком * Из Datamation, декабрь 1973 г.
  32. ^ Кнут, Дональд Э. «Структурное программирование с переходом к операторам» ACM Computing Surveys 6 (4): 261-301, декабрь 1974 г.
  33. ^ Даль, Дейкстра и Хоар, «Структурированное программирование», Academic Press, 1972.
  34. ^ Зан, Коннектикут «Управляющий оператор для естественного структурированного нисходящего программирования», представленный на симпозиуме по языкам программирования, Париж, 1974.
  35. ^ Пайер, Матиас ; Кузнецов Владимир. «О различиях свойств CFI, CPS и CPI» . nebelwelt.net . Проверено 1 июня 2016 г.
  36. ^ «Обнаружение ошибок Adobe Flash приводит к созданию нового метода предотвращения атак» . Мрачное чтение . 10 ноября 2015 года . Проверено 1 июня 2016 г.
  37. ^ Финал. «Финал будет представлен на выставке Black Hat USA 2016» . www.prnewswire.com . Проверено 1 июня 2016 г.

Дальнейшее чтение [ править ]

  • Хоар, CAR «Разбиение: алгоритм 63», «Быстрая сортировка: алгоритм 64» и «Найти: алгоритм 65». Комм. ACM 4, 321–322, 1961.

Внешние ссылки [ править ]