
6.6. Бесконечные циклы
В теории программирования бесконечным циклом называют цикл, у которого при заданных условиях, теле цикла и/или входных значениях параметров условие выхода из него никогда не выполняется. Если программа вошла в бесконечный цикл, то говорят, что она зациклилась. Практически зацикливание определяют по слишком большому времени счета программы, при котором компьютер “зависает”, т.е. перестает реагировать на действия пользователя, поскольку продолжает расчеты по программе. При этом нарушается одно из основных свойств алгоритмов – результативность. Возникновение бесконечных циклов является одной из главных причин неправильной работы программ. Поэтому желательно предусматривать их возникновение еще на стадии разработки алгоритмов решения задач.
Теоретически любой цикл может быть представлен как бесконечный цикл, в тело которого добавлена проверка условия выхода из цикла и команда, реализующая сам выход.
Несмотря на отрицательные свойства бесконечных циклов, они широко используются на практике для управления многими программными продуктами (операционные системы, диалоговые окна и др.) и аппаратными средствами (микроконтроллеры, датчики и т.п.), для которых заранее неизвестно условие окончания их работы.
В некоторых языках программирования для задания бесконечных циклов введены специальные операторы. В Паскале для практического создания бесконечного цикла обычно используется итерационный цикл с предусловием, в котором задается всегда истинное значение выражения:
while True do оператор;
Также бесконечный цикл в Паскале можно задать при помощи итерационного цикла с постусловием, в котором задано всегда ложное значение выражения:
repeat
оператор 1;
...
oператор n
until False
Вместо явной подстановки в логические выражения циклов логических значений True (False) для организации бесконечных циклов в их выражения могут быть подставлены логические условия, которые всегда истинны (ложны). Например, условия (1<2), (x*x+y*y≥0) всегда истинны; условия (1≥2), (x*x+y*y<0) – всегда ложны.
Прерывание бесконечных циклов во всех языках программирования прямо или опосредованно зависит от действий пользователя, в результате которых в теле цикла срабатывают Break-подобные операторы (в Паскале – оператор break). Теоретически для выхода из бесконечного цикла можно использовать оператор безусловного перехода GOTO, но в соответствии с принципами структурного программирования лучше применять специальный оператор break.
Пример 1 использования оператора break для прерывания бесконечного цикла в зависимости от реакции пользователя на запрос программы. Рассмотрим код программы, который реализует теоретически бесконечное наращивание счетчика i и вывод его значений на экран до тех пор, пока пользователь не прекратит выполнение цикла путем ввода в программу любого сообщения, отличающегося от ’y’:
var s:char; i:integer;
begin
i:=0; {присвоение начального значения целочисленному счетчику i}
while True do {оглавление бесконечного цикла}
begin {открытие тела бесконечного цикла}
i:=i+1; Write (’counter=’,i); {наращивание значения счетчика i и его вывод}
Write(’ Enter request for cycle continue (y - yes, any others - no)’);
ReadLn(s); {получение ответа от пользователя на запрос о продолжении цикла}
if not(s='y')then break {реакция - прерывание цикла при значениях s,не равных y}
end; {закрытие тела бесконечного цикла }
end.
Одном из основных случаев применения потенциально бесконечного цикла является получение гарантированного правильного ответа пользователя на запрос программы в том случае, когда задано допустимое множество вариантов ответа. Запрос программы и анализ ответа пользователя повторяются до тех пор, пока пользователь не даст тот ответ, который входит в допустимое множество.
Пример 2. От пользователя необходимо при помощи бесконечного цикла гарантировано получить ответ в виде одного символа, который должен быть только одной из цифр 1,2,3,4,5. При получении правильного ответа v необходимо выйти из цикла и вывести на экран сообщение " variant = v ".
Решение. Так как должен быть выдан хотя бы один запрос, то применяем цикл repeat, который станет практически бесконечным, если пользователь в каждой итерации будет вводить символы, отличные от цифр 1,2,3,4,5. Обозначим получаемый от пользователя символ через v и присвоим ему тип char для того, чтобы пользователь мог вводить любые символы без аварийного выхода из программы из-за несоответствия типов. Так как коды цифр 1,2,3,4,5 в кодировке ASCII стоят подряд, переводим все проверяемые величины в ее порядковые номера при помощи функции ord. Полный код решения задачи:
var v:char; i,i1,i5:integer;
begin
i1:=ord('1');i5:=ord('5'); {перевод символов '1','5' в порядковые номера кодировки ASCII}
repeat
Write(' Enter variant number (1,2,3,4 or 5):'); {запрос на ввод номера варианта }
ReadLn(v); {получение ответа от пользователя на запрос о номере варианта}
i:=ord(v); {перевод символа v в порядковый номер кодировки ASCII}
until (i1<=i)and(i<=i5); {анализ ответа и уход на повтор запроса при неправильном ответе}
WriteLn(' variant =', v); {вывод номера варианта после выхода из цикла}
end.
В рассмотренном примере при вводе строковых сообщений, состоящих из двух и более символов, программа будет анализировать только первый символ, поскольку величине v присвоен тип char (одиночный символ).
Пример 3. От пользователя необходимо при помощи бесконечного цикла гарантировано получить ответ в виде одного символа, который должен быть только одна из заглавных латинских букв A,B,C,G,H,I.. При получении правильного ответа v необходимо выйти из цикла и вывести на экран сообщение " variant = v ".
Решение. Для формирования условия проверки в алгоритме решения данной задачи можно использовать тот факт, что в кодировке ASCII коды заглавных букв A,B,C,D,E,F, G,H,I стоят подряд. При этом коды букв в парах, равноотстоящих от краев (A,I), (B,H), (C,G), (D,F), будут отличаться от кода средней буквы последовательности E, соответственно, на 4,3,2. Это свойство можно использовать для задания условия проверки принадлежности введенной буквы l заданному множеству с использованием кодов k(l), k(E) букв l и E: 2≤k(l)-k(E)≤4. Это условие вставляем в цикл repeat. Для краткости значение модуля разности k(l)-k(E) присвоим переменной abs_d:
var v:char; kv,kE,abs_d:integer;
begin
kE:=ord('E'); {перевод символа E в порядковый номер кодировки ASCII}
repeat
Write(’ Enter variant number (A,B,C,F,G or H):’); {запрос на ввод буквы }
ReadLn(v); {получение ответа от пользователя }
kv:=ord(v); {перевод символа v в порядковый номер кодировки ASCII}
abs_d:=abs(kv-kE);
until (2<=abs_d)and(abs_d<=4);{анализ ответа, повтор запроса при неправильном ответе}
WriteLn(' variant =', v); {вывод номера варианта после выхода из цикла}
end.
Так как в примерах 2 и 3 требуется получать от пользователя гарантированный ответ, то для ввода его ответов использован оператор readln.
Вопросы для проверки знаний.
1. Что в теории программирования называют бесконечным циклом и какое состояние выполнения программы называют зацикливанием ?
2. Как практически проявляется зацикливание ?
3. При решении каких задач используются бесконечные циклы ?
4. Как в Паскале можно задать бесконечный цикл ?
5. Как в Паскале практически реализуется выход из бесконечного цикла ?
6. Как бесконечный цикл используется для получения гарантировано правильного ответа от пользователя ?
Практическое задание.
1. На основе использования программного кода из примера 1 п.6.6. разработать код программы, в которой компьютер играет в кости с пользователем. Для простоты использовать одну игральную кость, на шести гранях которой нанесены от 1 до 6 точек. После каждого выбрасывания кости на экран выдается результат, а также запрос на дальнейшее продолжение игры. Для получения случайных целочисленных значений в интервале от 1 до 6 использовать стандартную функцию random с предварительным обращением к процедуре Randomize.