Лекция №3.3: Ветвления. Массивы. Циклы

страницы: 1 2 3

Содержание

Операторы циклов

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

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

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

for-to и for-downto

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

Инкрементный цикл с параметром

Общий вид оператора for-to:

for i := first to last do <оператор>;

Счётчик i (переменная), нижняя граница first (переменная, константа или выражение) и верхняя граница last (переменная, константа или выражение) должны относиться к эквивалентным порядковым типам данных. Если тип нижней или верхней границы не эквивалентен типу счётчика, а лишь совместим с ним, то осуществляется неявное приведение: значение границы преобразуется к типу счётчика, в результате чего возможны ошибки.

Цикл for-to работает следующим образом:

  1. вычисляется значение верхней границы last;
  2. переменной i присваивается значение нижней границы first;
  3. производится проверка того, что i <= last;
  4. если это так, то выполняется <оператор>;
  5. значение переменной i увеличивается на единицу;
  6. пункты 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 работает следующим образом:

  1. переменной i присваивается значение first;
  2. производится проверка того, что i >= last;
  3. если это так, то выполняется <оператор>;
  4. значение переменной i уменьшается на единицу;
  5. пункты 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]). Каждая из криволинейных трапеций будет урезана (сверху) до прямоугольника: высотой каждого из них послужит значение функции на левом конце участка. График станет «ступенчатым».

Реализация

step := 1;
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);

03-01.png

Вывод массива, удобный для пользователя

Задача. Распечатать двумерный массив размерности MxN удобным для пользователя способом. (Известно, что массив содержит только целые числа из промежутка [0 .. 100].)

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

Реализация

for i := 1 to n do
begin
 for j := 1 to m do
   Write(a[i, j] : 4);
 WriteLn;
end;

страницы: 1 2 3

Примечания

  1. ^ В противовес алгоритмам рекурсивным — см. лекцию 9
  2. ^ Исключение из этого правила см. п. «Break и continue»
  3. ^ В поздних версиях языка.
  4. ^ См. лекцию 1.
Код для вставки: :: :: :: ::
Поделиться: // //