Содержание
Операторы циклов
Для того, чтобы обработать несколько однотипных элементов, совершить несколько одинаковых действий и т. п., разумно воспользоваться оператором цикла — любым из четырёх, который наилучшим образом подходит к поставленной задаче.
Оператор цикла повторяет некоторую последовательность операторов заданное число раз, которое может быть определено и динамически — уже во время работы программы.
Замечание: Алгоритмы, построенные только с использованием циклов, называются итеративными1 — от слова итерация, которое обозначает повторяемую последовательность действий.
for-to и for-downto
В случае, когда количество однотипных действий заранее известно (например, необходимо обработать все компоненты массива), стоит отдать предпочтение циклу с параметром (for).
Инкрементный цикл с параметром
Общий вид оператора for-to:
for i := first to last do <оператор>;
Счётчик i (переменная), нижняя граница first (переменная, константа или выражение) и верхняя граница last (переменная, константа или выражение) должны относиться к эквивалентным порядковым типам данных. Если тип нижней или верхней границы не эквивалентен типу счётчика, а лишь совместим с ним, то осуществляется неявное приведение: значение границы преобразуется к типу счётчика, в результате чего возможны ошибки.
Цикл for-to работает следующим образом:
- вычисляется значение верхней границы last;
- переменной i присваивается значение нижней границы first;
- производится проверка того, что i <= last;
- если это так, то выполняется <оператор>;
- значение переменной i увеличивается на единицу;
- пункты 3–5, составляющие одну итерацию цикла, выполняются до тех пор, пока i не станет строго больше, чем last; как только это произошло, выполнение цикла прекращается, а управление передаётся следующему за ним оператору.
Из этой последовательности действий можно понять, какое количество раз отработает цикл for-to в каждом из трёх случаев:
- first < last: цикл будет работать last - first + 1 раз;
- first = last: цикл отработает ровно один раз;
- first > last: цикл вообще не будет работать.
После окончания работы цикла переменная–счётчик может потерять своё значение2. Таким образом, нельзя с уверенностью утверждать, что после того, как цикл завершил работу, обязательно окажется, что i = last + 1. Поэтому попытки использовать переменную-счётчик сразу после завершения цикла (без присваивания ей какого–либо нового значения) могут привести к непредсказуемому поведению программы при отладке.
Декрементный цикл с параметром
Существует аналогичный вариант цикла for, который позволяет производить обработку не от меньшего к большему, а в противоположном направлении:
for i := first downto last do <оператор>;
Счётчик i (переменная), верхняя граница first (переменная, константа или выражение) и нижняя граница last (переменная, константа или выражение) должны иметь эквивалентные порядковые типы. Если тип нижней или верхней границы не эквивалентен типу счетчика, а лишь совместим с ним, то осуществляется неявное приведение типов.
Цикл for-downto работает следующим образом:
- переменной i присваивается значение first;
- производится проверка того, что i >= last;
- если это так, то выполняется <оператор>;
- значение переменной i уменьшается на единицу;
- пункты 2-4 выполняются до тех пор, пока i не станет меньше, чем last; как только это произошло, выполнение цикла прекращается, а управление передаётся следующему за ним оператору.
Если при этом
- first < last, то цикл вообще не будет работать;
- first = last, то цикл отработает один раз;
- first > last, то цикл будет работать first - last + 1 раз.
Замечание о неопределённости значения счётчика после окончания работы цикла справедливо и в этом случае.
while и repeat-until
Если заранее неизвестно, сколько раз необходимо выполнить тело цикла, то удобнее всего пользоваться циклом с предусловием (while) или циклом с постусловием (repeat-until).
Общий вид этих операторов таков:
while <условие_1> do <оператор>; repeat <операторы> until <условие_2>;
Условие окончания цикла может быть выражено переменной, константой или выражением, имеющим логический тип.
Замечание: Обратите внимание, что на каждой итерации циклы for и while выполняют только по одному оператору (либо группу операторов, заключённую в операторные скобки begin-end и потому воспринимаемую как единый составной оператор). В отличие от них, цикл repeat-until позволяет выполнить сразу несколько операторов: ключевые слова repeat и until сами служат операторными скобками.
Так же, как циклы for-to и for-downto, циклы while и repeat-until можно назвать в некотором смысле противоположными друг другу.
Последовательности действий при выполнении этих циклов таковы:
Для while: | Для repeat-until: |
---|---|
1. Проверяется, истинно ли <условие_1>. | 1. Выполняются <операторы>. |
2. Если это так, то выполняется <оператор>. | 2. Проверяется, ложно ли <условие_2> |
3. Пункты 1 и 2 выполняются до тех пор, пока <условие_1> не станет ложным. | 3. Пункты 1 и 2 выполняются до тех пор, пока <условие_2> не станет истинным. |
Таким образом, если <условие_1> изначально ложно, то цикл while не выполнится ни разу. Если же <условие_2> изначально истинно, то цикл repeat-until выполнится один раз.
Break и Continue
Существует возможность3 прервать выполнение цикла (или одной его итерации), не дождавшись конца его (или её) работы.
Break прерывает работу всего цикла и передаёт управление на следующий за ним оператор.
Continue прерывает работу текущей итерации цикла и передаёт управление следующей итерации (цикл repeat-until) или на предшествующую ей проверку (циклы for-to, for-downto, while).
Замечание: При прерывании работы циклов for-to и for-downto с помощью функции Break переменная цикла (счётчик) сохраняет своё текущее значение, не «портится».
Оператор безусловного перехода goto
Возвращаясь к сказанному об операторе goto4, необходимо отметить, что при всей его нежелательности всё–таки существует ситуация, когда предпочтительно использовать именно этот оператор — как с точки зрения структурированности текста программы, так и с точки зрения логики её построения, и уж тем более с точки зрения уменьшения трудозатрат программиста. Эта ситуация — необходимость передачи управления изнутри нескольких вложенных циклов на самый верхний уровень.
Дело в том, что процедуры Break и Continue прерывают только один цикл — тот, в теле которого они содержатся. Поэтому в упомянутой выше ситуации пришлось бы заметно усложнить текст программы, вводя много дополнительных прерываний. А один оператор goto способен заменить их все.
Сравните, например, два программно–эквивалентных отрывка:
Write('Матрица ');
for i := 1 to n do
begin
flag := False;
for j := 1 to m do
if a[i, j] > a[i, i] then
begin
flag := True;
Write('не ');
Break;
end;
if flag then Break;
end;
WriteLn('обладает свойством' +
' диагонального преобладания.');
Write('Матрица ');
for i := 1 to n do
for j := 1 to m do
if a[i, j] > a[i, i] then
begin
Write('не ');
goto 1;
end;
1: WriteLn('обладает свойством' +
' диагонального преобладания.');
Пример использования циклов
Задача. Вычислить интеграл в заданных границах a и b для некоторой гладкой функции f от одной переменной (с заданной точностью).
Алгоритм. Метод последовательных приближений, которым мы воспользуемся для решения этой задачи, состоит в многократном вычислении интеграла со всё возрастающей точностью, — до тех пор, пока два последовательных результата не станут различаться менее, чем на заданное число (скажем, eps = 0,001). Количество приближений нам заранее неизвестно (оно зависит от задаваемой точности), поэтому здесь годится только цикл с условием (любой из них).
Вычислять одно текущее значение для интеграла мы будем с помощью метода прямоугольников: разобьём отрезок [a, b] на несколько мелких частей, каждую из них дополним (или урежем — в зависимости от наклона графика функции на данном участке) до прямоугольника, а затем просуммируем получившиеся площади. Количество шагов нам известно, поэтому здесь удобнее всего воспользоваться циклом с параметром.
На нашем рисунке изображена функция f(x) = x2 (на отрезке [1, 2]). Каждая из криволинейных трапеций будет урезана (сверху) до прямоугольника: высотой каждого из них послужит значение функции на левом конце участка. График станет «ступенчатым».
Реализация
h := b - a;
s_nov := f(a) * h;
repeat
s_star := s_nov;
s_nov := 0;
step := step * 2;
h := h / 2;
for i := 1 to step do
s_nov := s_nov + f(a + (step - 1) * h);
s_nov := s_nov * h;
until Abs(s_nov - s_star) <= eps;
WriteLn(s_nov);
Вывод массива, удобный для пользователя
Задача. Распечатать двумерный массив размерности MxN удобным для пользователя способом. (Известно, что массив содержит только целые числа из промежутка [0 .. 100].)
Алгоритм. Понятно, что если весь массив мы вытянем в одну строчку (или, того хуже, в один столбик), то хороших слов в свой адрес мы от пользователя не дождёмся. Именно поэтому нам нужно вывести массив построчно, отражая его структуру.
Реализация
begin
for j := 1 to m do
Write(a[i, j] : 4);
WriteLn;
end;