Дополнительные операторы управления циклами
К.т.н., доц. Троицкий Д.И.
Рассмотрено на заседании кафедры АСС
1 сентября 2007, протокол №1
Зав. кафедрой АСС д.т.н., проф.
_______________ Иноземцев А.Н.
Содержание
1. Задачи с циклами. 4
2. Цикл с предусловием.. 4
2.1 Вечный цикл. 6
3. Цикл с постусловием.. 7
4. Цикл с переменной. 8
5. Дополнительные операторы управления циклами. 10
6. Вложенные циклы.. 12
7. Статические массивы.. 13
8. Многомерные статические массивы.. 14
9. Ввод-вывод массивов. 15
9.1 Ввод с клавиатуры.. 15
9.2 Считывание массива из файла. 17
10. Поиск максимума и минимума. 18
Задачи с циклами
После поворота событий от плохого к худшему цикл повторится.
Из законов Мэрфи
Цикл в программировании позволяет выполнить одну и ту же последовательность операторов несколько раз. Циклы используются в трех случаях:
1. Обработка массивов данных, когда одни и те же операции выполняются над многими данными.
2. Возврат "вверх" (к началу программы). Например, после выдачи результата программа спрашивает пользователя "Повторить?" и при утвердительном ответе повторяет расчет. Это реализуется только с помощью цикла.
3. Повторение операторов до наступления какого-то заданного условия (нажатия клавиши, достижения требуемой точности вычислений и т.д.)
Кроме того, в ряде случаев циклы позволяют избежать повторения одних и тех же операторов. Если в программе есть повторяющиеся фрагменты – она написана неправильно.
Цикл с предусловием
Две интересные передачи всегда идут в одно и то же время.
Из законов Мэрфи.
Наиболее общим в Delphi является цикл с предусловием, реализуемый оператором WHILE…DO. Вот его общий вид:
WHILE условие DO
оператор;
В качестве условия может выступать любое логическое выражение или переменная типа BOOLEAN. Работает такой цикл следующим образом. Сначала проверятся условие (отсюда и название – цикл с предусловием). Если условие истинно, то выполняется оператор (он называется "тело цикла"), а затем программа "пятится назад", снова оказывается на операторе WHILE..DO, снова проверяет условие… и так далее до тех пор, пока условие не станет ложным. Как только это случится, цикл прекращается, и начинает выполняться оператор, следующий за телом цикла.
Цикл с предусловием имеет интересную особенность: если в начале его работы условие оказалось ложным, то цикл вообще не будет выполнятьсяни разу.
Рассмотрим пример использования цикла WHILE. Пусть мы суммируем ряд натуральных чисел 1+2+3+… и хотим узнать, на каком числе этого ряда сумма превысит 100. Программа будет выглядеть следующим образом:
VAR s:WORD; { сумма }
i: WORD; { текущее число ряда }
BEGIN
s:=0; { сумму обязательно обнуляем!!! }
i:=1; { начинаем суммировать с единицы }
WHILE s<100 DO
BEGIN
s:=s+i;
i:=i+1
END;
Label1.Caption:='Сумма превысит 100 при i='+IntToStr(i)
end;
Кстати, сумма превысит 100 при i=15, так как . Обратите внимание на операторные скобки BEGIN..END. Они позволяют поместить в тело цикла более одного оператора (разумеется, если в теле цикла только один оператор, BEGIN…END не нужны).
Рис. 1. Обозначение цикла с предусловием на блок-схемах.
На блок-схемах все циклы изображаются в виде крышки гроба, самого гроба и лежащего в нем тела цикла. Обозначения же зависят от вида цикла. Для цикла с предусловием блок-схема выглядит, как показано на Рис. 1.
Вечный цикл
На процессорах Pentium IV вечный цикл выполняется в два раза быстрее!
Из программистского фольклора.
Одна из самых больших неприятностей в программировании – зацикливание. Если при каждом проходе цикла в условии ничего не меняется, то цикл никогда не прекратится и программа "зависнет". Чтобы ее прервать насильно, нужно нажать клавиши Ctrl+C или Ctrl+Break. Вот яркий пример вечного цикла:
s:=0;
WHILE s<100 DO
i:=i+1;
В теле цикла находится только один оператор i:=i+1. Его выполнение никак не влияет на значение переменной s, которое всегда будет равным нулю и, соответственно, меньшим 100. Поэтому такой цикл никогда не прекратится.
Правило: в условии цикла обязательно должна присутствовать переменная6 значение которой изменяется в теле цикла.
Цикл с постусловием
Трубку вы схватите как раз тогда, когда в ней зазвучат сигналы отбоя.
Из законов Мэрфи.
Другая разновидность цикла отличается тем, что условие проверяется не до, а послевыполнения тела цикла. Поэтому цикл с постусловием обязательно выполняется как минимум один раз. Именно поэтому он не чаще всего не годится для решения вычислительных задач: возникает лишняя итерация (проход) цикла, и, складывая 1+2+3, можно получить ответ 10=1+2+3+4.
Цикл с постусловием записывается оператором REPEAT.. UNTIL. Его общий вид таков:
REPEAT
оператор1
…
оператор n
UNTIL условие;
Тело цикла составляют операторы, стоящие между REPEAT и UNTIL. Дополнительные BEGIN..END здесь не нужны – синтаксис оператора и так позволяет помещать в тело цикла несколько операторов.
Важная особенность цикла REPEAT состоит в том, что его условие является условием прекращения цикла. Цикл прерывается и программа идет дальше, как только условие становится истинным. Напомним, что в цикле WHILE все наоборот- там цикл прерывается, как только условие становится ложным.
Цикл с постусловием идеально подходит для организации контроля корректности вводимых пользователем данных. Например, мы ожидаем ввода числа от 1 до 3 (скажем, при выборе, куда вводить информацию: на экран или в файл). Как обезопасить себя на тот случай, если гнусный пользователь возьмет и введет 4 или 0? Очень просто:
VAR a:BYTE;
BEGIN
…
REPEAT
a:=StrToInt(LabeledEidit1.Text)
UNTIL (a>=1) AND (a<=3);
После ввода числа проверяется условие его нахождения в интервале [1;3]. Если введенное число не попадает в указанный интервал, условие будет ложным и цикл повторится до тех пор, пока условие не окажется истинным.
Обозначение цикла с постусловием на блок-схемах показано на Рис. 2.
Рис. 2. Обозначение цикла с постусловием на блок-схемах.
Цикл с переменной
Во всякой формуле константы должны рассматриваться как переменные.
Из законов Мэрфи.
Весьма часто возникает необходимость просто выполнить участок программы заданное число раз. Конечно, можно организовать такой цикл при помощи оператора WHILE. Например, нам нужно перебрать все целые числа от 10 до 20 и просуммировать их. Сделать это можно следующим образом:
VAR i: BYTE; s: WORD;
BEGIN
i:=10;
s:=0;
WHILE i<=20 DO
BEGIN
s:=s+i;
i:=i+1
END;
Такая программа работоспособна, но громоздка и некрасива. Есть способ лучше – использовать цикл с переменной FOR..TO. Его общий вид таков:
FOR переменная:=начальное значение TO конечное значение DO
оператор;
Оператор FOR автоматически перебирает значения целой переменной от начального до конечного c шагом 1и при каждом увеличении переменной на единицу выполняет оператор. Обратите внимание, что оператор FOR работает только с целыми числами и шаг приращения всегда равен единице.
Перепишем нашу программу, используя оператор FOR:
VAR i: BYTE; s: WORD;
BEGIN
s:=0;
FOR i:=10 TO 20 DO
s:=s+i;
Программа стала проще и понятнее. Кстати, не следует думать, что уменьшение длины исходного текста программы приводит и к уменьшению размера создаваемого при компиляции исполнимого файла.
А что делать, если нужно перебирать не целые, а дробные значения? Единственный выход – по-прежнему перебирать целые, а в теле цикла делить их на 10,100,1000… для получения дробного числа. Например, просуммируем числа от 0.1 до 1.2 с шагом 0.1:
VAR i:BYTE; s:REAL;
BEGIN
s:=0.0; { обязательно 0.0, а не 0 – это вещественное число }
FOR i:=1 TO 12 DO
s:=s+i/10;
Иногда нужно перебирать числа в обратную сторону – от большего к меньшему. Обычный цикл FOR в случае, когда начальное значение переменной цикла меньше конечного, вообще не будет выполняться ни разу. Однако в Delphi предусмотрен вариант цикла с переменной, в котором шаг равен минус единице. Все отличие состоит в том, что слово TO заменяется на DOWNTO:
FOR i:=20 DOWNTO 10 DO…
На блок-схемах цикл с переменной изображается так, как показано на Рис. 3.
Рис. 3. Обозначение цикла с переменной на блок-схемах.
Дополнительные операторы управления циклами
Люди согласны сделать работу, когда необходимость в этом уже отпала.
Из законов Мэрфи.
В теле всех трех циклов можно применять два специальных оператора: BREAK и CONTINUE. Оператор BREAK вызывает досрочный выход из цикла. Например, вернемся к задаче суммирования ряда натуральных чисел 1+2+3+… Мы хотим узнать, на каком числе этого ряда его сумма превысит 100. Программа с BREAK выглядит так:
VAR s:WORD; { сумма }
i: WORD; { текущее число ряда }
BEGIN
s:=0; { сумму обязательно обнуляем!!! }
i:=1; { начинаем суммировать с единицы }
WHILE TRUE DO { вечный цикл?! Не совсем…}
BEGIN
s:=s+i;
i:=i+1;
IF s>100 THEN
BREAK
END;
Label1.Caption:='Сумма превысит 100 при i='+IntToStr(i)
end;
Наличие оператора BREAK позволило смело совершить программистское преступление – написать вечный цикл WHILE TRUE DO. Прерывание цикла при s>100 обеспечивает оператор BREAK.
Оператор CONTINUE, наоборот, переходит в начало цикла и начинает следующую итерацию. Это позволяет пропустить часть итераций. Например, мы хотим вывести на экран все целые числа от 1 до 100, которые не делятся на 3. Делается это так:
VAR i:BYTE
BEGIN
FOR i:=1 TO 100 DO
BEGIN
IF (i MOD 3=0) THEN
CONTINUE;
Memo1.Lines.Add(IntToStr(i))
END;
Обратите внимание на способ проверки делимости. Операция MOD вычисляет остаток от деления нацело. Скажем, 10 MOD 3=1. Если одно число нацело делится на другие, то остаток будет равен нулю. В этом случае условие в операторе IF оказывается истинным и выполняется оператор CONTINUE. Он пропускает остаток тела цикла и снова возвращается к оператору FOR. Если же значение переменной i не делится на три, тело цикла выполняется дальше.
Операторы BREAK и CONTINUE являются вспомогательными – любой алгоритм можно запрограммировать и без них, но во многих случаях они оказываются удобными.
Вложенные циклы
Внутри каждой большой задачи сидит маленькая, пытающаяся пробиться наружу.
Из законов Мэрфи
Внутри одного цикла может находиться другой, внутри его – еще один и т.д. Такая "матрешка" нужна для решения многих задач. Рассмотрим пример вложенных циклов. Пусть задана прямоугольная решетка на плоскости размером 10´20. Нужно вывести на экран координаты всех узлов такой решетки. Делается это так:
VAR i, j: WORD;
BEGIN
FOR i:=1 TO 10 DO
FOR j:=1 TO 20 DO
Memo1.Lines.Add('X='+IntToStr(i)+' , Y='+IntToStr(j))
В данном случае BEGIN…END не нужны: внутри внешнего цикла находится только один оператор FOR j:=1 TO 20. Кстати, вложенными могут быть циклы всех трех типов.
На блок-схеме приведенная программа будет выглядеть следующим образом (рис. 4):
Рис. 4. Обозначение вложенного цикла на блок-схемах.
Статические массивы
Как только вы испробуете все возможные способы решения и не найдете подходящего,
тут же найдется решение, простое и очевидное для всех других людей.
Из законов Мэрфи
Массив – это набор переменных одинакового типа. Обращение к конкретному элементу массива происходит по его номеру. Это позволяет обрабатывать массивы в циклах.
Для создания массива сначала необходимо определить пользовательский тип данных.Если такие типы, как REAL, INTEGER, BOOLEAN есть в Delphi всегда (они называются встроенными), то встроенных статических массивов в Delphi нет – их надо создавать самому. Новые типы данных создаются оператором TYPE, который должен находиться в разделе описаний перед оператором VAR. Оператор TYPE не выделяет память под переменные, он нужен, чтобы потом в операторе VAR компьютер знал, сколько памяти выделить.
Обычные массивы в Delphi являются статическими. Это значит, что число элементов в массиве должно быть задано как константа и известно до начала работы программы. Если нужно, например, ввести число n с клавиатуры и затем создать массив из n элементов, то необходимо применять более сложную структуру – динамический массив (см. п. Ошибка! Источник ссылки не найден.).
Общий вид оператора TYPE при создании типа данных "массив" такой:
TYPE имя_типа = ARRAY [размерность] OF тип_данных_элементов;
Здесь имя_типа – придуманное программистом имя нового типа данных (обычно его начинают с буквы "Т", чтобы не путать с именами переменных), тип_данных_элементов – один из встроенных типов данных, к которому будут принадлежать все элементы массива.
Размерность массива указывает, сколько элементов содержится в массиве и как они будут расположены. В простейшем случае массив будет линейным (одномерным). Для линейного массива размерность указывается в виде imin..imax (обратите внимание – две точки, а не три!). Здесь imin – индекс первого элемента массива, imax – индекс последнего. Число элементов в таком массиве равно imax-imin+1.
Например, создадим массив из 10 чисел типа REAL:
TYPE TA=ARRAY [1..10] OF REAL;
А теперь выделим под него память:
VAR a:TA;
Индексы элементов массива могут быть и нулевыми, и даже отрицательными. Единственное ограничение – суммарный размер массива в памяти не должен превышать 64Кб.
При работе с массивом в программе можно обращаться к его отдельным элементам, указывая их индекс в квадратных скобках после имени массива, например: a[1], a[i], a[j+2-1]. Нельзя присвоить значение всему массиву сразу. В приведенном примере запись a:=10 является глупостью. Значения в массив надо заносить в цикле, по одному. Например, заполним массив случайными числами:
CONST Nmax=10; { число элементов в массиве }
TYPE TA=ARRAY[1..Nmax] OF REAL;
VAR a:TA; i:BYTE;
BEGIN
FOR i:=1 TO Nmax DO
a[i]:=RANDOM
. . .
Обратите внимание, что число элементов массива вынесено в отдельную константу Nmax. Если его потребуется изменить, достаточно будет сделать это в одном месте, а не бегать по всей программе.