Программирование на Lazarus

Циклы и переключатель case

Показывать лекцию целиком

Цель лекции

Изучение циклов и оператора выбора case. Применение этих инструментов на практике в различных случаях.

Циклы

На этой лекции мы поговорим еще об одном способе организовать логику программы - о циклах. Довольно часто программисту приходится выполнять какой-то участок кода заданное количество раз, или до тех пор, пока не наступит какое-то условие. Такой повтор кода и называется циклом.

Цикл - последовательный повтор части кода от нуля раз до бесконечности.

Действительно, бывают условия, при которых цикл не выполняется ни разу. А бывает и так, что цикл начинает выполняться бесконечно, при этом программа "зависает" - не реагирует на клавиатуру и мышь, не дает продолжать с ней работу. Но это уже ошибка программиста. Циклы бывают двух видов - по счетчику, и по условию. Первые выполняются заданное количество раз, вторые - пока не наступит определенное логическое условие. Именно второй тип циклов начинающие программисты могут "зациклить", не предусмотрев гарантированного наступления условия их завершения. Но давайте-ка по порядку.

Цикл for…to…do

Цикл for…to…do выполняется определенное количество раз, по счетчику. Счетчик представляет собой переменную целочисленного типа - обычно используют integer, но если цикл должен выполниться 10-20 раз, вполне можно обойтись типом byte, чтобы не тратить впустую лишних 3 байта оперативной памяти. Синтаксис цикла следующий:

for <счетчик>:= <начальное значение> to <конечное значение> do <оператор>;
    

Здесь, счетчик, как говорилось выше - переменная целого типа. Начальное и конечное значения - целые числа, например, от 1 до 10. Оператор - та часть кода, которую нужно выполнить нужное количество раз. Нередко бывает так, что нужно выполнить не один оператор, а несколько. В этом случае делают составной оператор, поместив весь нужный код между операторными скобками begin…end:

for <счетчик>:= <начальное значение> to <конечное значение> do begin
   Оператор 1;
   Оператор 2;
   …
   Оператор n;
end;
    

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

Загрузите Lazarus с новым проектом. Сохраните его в папку 10-01 там, где вы храните все учебные проекты. Проект назовите, скажем, MyCycles. Как назвать главную форму, вам уже, наверное, не нужно напоминать? Посреди окна установите простую кнопку TButton, переименовывать её не нужно, но в Caption напишите

for…do
    

Сгенерируйте для кнопки событие OnClick, в котором напишите следующий код:

procedure TfMain.Button1Click(Sender: TObject);
var
  b: byte;
begin
  for b:= 1 to 10 do ShowMessage('Проход цикла №' + IntToStr(b));
end;
    

Что, по-вашему, произойдет при нажатии на кнопку? Выйдет окошко с сообщением "Проход цикла №1". Как только вы нажмете <OK>, выйдет новое сообщение "Проход цикла №2". И так будет 10 раз, при этом b будет автоматически увеличиваться на 1. После того, как b станет равно 10, и цикл выполнится в последний раз, процедура Button1Click завершит работу и управление перейдет обратно главной форме.

В данном примере цикл for наращивал счетчик по возрастающей, от 1 до 10. Но у этого цикла есть ещё одна форма, когда счетчик не увеличивается, а уменьшается на единицу:

for <счетчик>:= <начальное значение> downto <конечное значение> do <оператор>;
    

Разница здесь только в том, что вместо ключевого слова to мы указываем downto. И, разумеется, в этом случае начальное значение счетчика должно быть больше конечного значения. Переделайте строку с циклом так:

  for b:= 10 downto 1 do ShowMessage('Проход цикла №' + IntToStr(b));
    

Запустив программу на выполнение, вы убедитесь, что теперь счетчик уменьшается от 10 до 1.

Инструкции break и continue

Циклы не всегда нужно выполнять от начала, и до конца. Иногда, в зависимости от условий, бывает необходимо пропустить какие-то шаги цикла, или вовсе досрочно завершить цикл. Для этого и служат инструкции break и continue.

Break - инструкция досрочного завершения работы цикла.

Изменим пример процедуры Button1Click. Предположим, нам нужно вывести на экран результаты деления числа 100 на числа от -10 до 10. НО! На ноль делить нельзя, поэтому мы выполним проверку: если второе число - ноль, мы завершим цикл. А чтобы нам не пришлось много раз нажимать на <OK>, как в прошлом примере, результаты мы соберем в одну строку и в конце цикла разом выведем её на экран. Итак, код:

procedure TfMain.Button1Click(Sender: TObject);
var
  s: string; //для сбора результатов деления
  b: ShortInt; //счетчик
  r: real; //результат деления
begin
  for b:= -10 to 10 do begin  //начало цикла
    //если ноль, не делим, а сразу выходим из цикла:
    if b = 0 then break;
    // делим:
    r:= 100 / b;
    //теперь добавляем результат в строку s:
    s:= s + '100 / ' + IntToStr(b) + ' = ' + FloatToStr(r) + #13;
  end;  //конец цикла

  //теперь разом выводим все полученные результаты:
  ShowMessage(s);
end;
    

Я постарался дать подробные комментарии, но всё же остановимся на некоторых моментах. Прежде всего, бросается в глаза, что переменной b вместо предыдущего типа byte мы назначили тип ShortInt. Ведь нам нужно считать от -10 до 10, а byte имеет диапазон от 0 до 255, отрицательные величины этим типом не поддерживаются. Тип ShortInt также занимает 1 байт, но он имеет диапазон от -128 до 127, и в данном случае подходит нам больше всего. Если же мы попытаемся оставить счетчику тип byte, то компилятор просто выведет ошибку нарушения допустимого диапазона, и не соберет исполняемую программу, не запустит её.

Далее, для получения результатов деления мы использовали переменную типа real. Ведь деление одного целого числа на другое не всегда даст целое число! Например, 100 / -8 = -12,5. Поэтому и переменная для результата у нас вещественного типа.

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

ShowMessage(s);
    

Если b не равна нулю, то цикл продолжает свою работу, выполняя деление 100 на b. Поскольку значение b будет изменяться при каждом проходе, то и результат все время будет другой. И так будет, пока b не поменяет значение от -10 до 0. Как только это случится, цикл закончится.

Самой сложной строкой цикла является оператор сбора строки s:

s:= s + '100 / ' + IntToStr(b) + ' = ' + FloatToStr(r) + #13;
    

Смотрите, как формируется эта строка. В начале выражения у нас стоит "s + ", то есть, предыдущее значение s не теряется, а добавляется к новой строке, причем при первом проходе цикла s еще пуста, зато потом она уже будет содержать текст! Затем мы собираем строку из разных частей. Сначала идет часть "100 / ", затем к ней добавляется значение счетчика b, преобразованное в строку, затем добавляется часть строки " = ". Потом к строке добавляется результат деления, преобразованный функцией FloatToStr() в строковое выражение. И в самом конце мы добавляем символ перехода на новую строку #13. Таким образом, у нас каждое выражение будет на новой строке, не смешиваясь в кашу. В результате нажатия на кнопку мы получим следующее сообщение:

(рис 10.1) Досрочный выход из цикла

Как видите, строка цикла

if b = 0 then break;
    

не позволила нам сделать деление на ноль, и мы избежали ошибки. Но тут сразу бросается незавершенность работы цикла. А как же целая часть, от 1 до 10?! Чтобы исправить положение, замените break на continue:

if b = 0 then continue;
    
Continue - инструкция пропуска оставшейся части цикла и переход к новому шагу цикла. Цикл при этом не завершается.

Нажав на кнопку, вы убедитесь, что теперь цикл выполняется до конца, от -10 до 10, пропустив лишь нулевое значение. Однако и тут дотошный пользователь сможет найти недостаток. Куда делся ноль то? Ну да, на ноль делить нельзя, но что-то ведь нужно вывести в этой позиции! Давайте поступим, как стандартный калькулятор Windows: в случае, если b равно нулю, выведем сообщение, что деление на нуль запрещено. Нам нужно только изменить условие if, сделав составной оператор:

if b = 0 then begin
  s:= s + 'Деление на нуль запрещено';
  continue;
end;
    

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

На всякий случай, полный код процедуры:

procedure TfMain.Button1Click(Sender: TObject);
var
  s: string; //для сбора результатов деления
  b: ShortInt; //счетчик
  r: real; //результат деления
begin
  for b:= -10 to 10 do begin  //начало цикла
    //если ноль, не делим, а пропускаем шаг цикла с соответствующим сообщением:
    if b = 0 then begin
      s:= s + 'Деление на нуль запрещено' + #13;
      continue;
    end;
    //сначала делим:
    r:= 100 / b;
    //теперь добавляем результат в строку s:
    s:= s + '100 / ' + IntToStr(b) + ' = ' + FloatToStr(r) + #13;
  end;  //конец цикла

  //теперь разом выводим все полученные результаты:
  ShowMessage(s);
end;
    

В результате мы получим такое сообщение:

(рис 10.2) Пропуск шага цикла

Условный цикл while…do

Слово while переводится как пока, а do - как делать. Другими словами, цикл while…do выполняется до тех пор, пока какое-то условие верно. Синтаксис:

while <условие> do <оператор>;
    

Здесь, условие - какое то условное выражение или логическая переменная. Если переменная или выражение имеют значение true, то цикл будет выполняться до тех пор, пока это значение не станет false. И только тогда цикл прекратит свою работу.

Если же значение условия изначально будет false, то цикл не будет выполнен ни разу.

Оператор также может быть составным, если находится в скобках begin…end.

В цикле while…do также можно использовать операторы break и continue.

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

var b: boolean;
begin
   b:= true;
   while b = true do 
      ShowMessage('Истина');
    

Здесь цикл while будет выполняться до тех пор, пока b будет оставаться true. А поскольку внутри цикла не предусмотрено изменение значения b, цикл получился "зацикленным". Он будет выводить сообщение "Истина" до тех пор, пока у вас не сгорит компьютер, либо пока вы сами его со злости не разобьёте (шучу, у вас всегда есть возможность прервать зависшую программу).

Обратите внимание на строку

   while b = true do
    

Я так написал, чтобы сразу было понятно, что тут к чему. Для цикла важно, чтобы условие было истинным. Поскольку b действительно равно true, то условие истинно. Но в этом случае можно было написать проще:

   while b do
    

Ведь b итак содержит true! А зацикленный цикл можно написать еще понятней, вообще обойдясь без переменной:

   while true do
    

Делать, пока истина, то есть, всегда. Все эти три примера будут равноценны. Однако вернемся к нашему проекту, и опробуем цикл на практике. Мы будем 100 делить на любое введенное пользователем число, и выводить результат. Причем будем делать это, пока пользователю не надоест, то есть, заранее неизвестно, сколько раз. Когда же ему надоест вводить числа, он введет ноль. Мы со своей стороны выведем сообщение, что на нуль делить нельзя, и на этом завершим работу цикла. Проверку на корректность введенного числа производить не будем. Пользователь сам будет контролировать введенное значение.

Чтобы не заниматься переделкой кода, добавим на форму еще одну кнопку, в Caption которой напишем

while…do
    

чтобы было понятно, какая кнопка с каким циклом работает. Для кнопки сгенерируем такое событие OnClick:

procedure TfMain.Button2Click(Sender: TObject);
var
  s: string; //для получения числа от пользователя
  r: real; //результат деления
  begin
    //начинаем цикл
    while true do begin
      //очистим строку:
      s:= '';
      //получим число от пользователя:
      InputQuery('100 / на...', 'Введите число', s);
      //если ноль, выводим сообщение и выходим из цикла:
      if s = '0' then begin
        ShowMessage('На нуль делить нельзя!');
        break;
      end;
      //если еще не вышли, значит не нуль. делим:
      r:= 100 / StrToFloat(s);
      //выводим результат:
      ShowMessage('100 / ' + s + ' = ' + FloatToStr(r));
    end;  //конец цикла
end;
    

Комментарии достаточно подробны, но все же проясним некоторые детали. Прежде всего, мы сделали "зацикленный" цикл:

    while true do begin
    

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

      if s = '0' then begin
        ShowMessage('На нуль делить нельзя!');
        break;
      end;
    

Поэтому компьютер не зависнет - стоит пользователю ввести ноль, как цикл прекратится. Как пользоваться функцией InputQuery() вы, надеюсь, не забыли? Если забыли, вернитесь к лекции №6. Всё остальное должно быть понятно по предыдущим примерам.

Когда цикл начнется, пользователю выйдет запрос:

(рис 10.3) Запрос пользователю

При вводе числа не забывайте о десятичном разделителе, который зависит от языка операционной системы. В русских Windows это запятая, в английских - точка. Если введете неправильное (некорректное) число, программа зависнет. Сбросить отладку просто: <Запуск -> Сбросить отладчик>.

Условный цикл repeat…until

Цикл repeat…until - еще один условный цикл. Однако есть и отличия. Если while…do - цикл с предусловием, то repeat…until имеет постусловие. То есть, если в цикле while…do вначале проверяется условие, и в зависимости от этого условия принимается решение - выполнять ли цикл, то в repeat…until вначале выполняется цикл, а затем только проверяется условие. Другими словами, цикл repeat…until обязательно будет выполнен хотя бы однажды. Причем, опять таки, в отличие от while…do, цикл repeat…until будет выполняться до тех пор, пока условие ложно, и прекратит свою работу, когда условие станет истинным.

Англ. repeat переводится, как повторять, until - до тех пор, пока… Другими словами - выполнять какие то действия до тех пор, пока не выполнится условие. Синтаксис простой:

repeat <оператор(ы)> until <условие>;
    

Обратите внимание: ключевые служебные слова repeat…until работают, как операторские скобки begin…end, поэтому в тело цикла можно включить сколько угодно операторов. Проверим работу цикла на примере. Добавьте в проект третью кнопку, в Caption которой напишите

repeat…until
    

Код нажатия на кнопку будет следующий:

procedure TfMain.Button3Click(Sender: TObject);
var
  i: byte;
  s: string;
begin
  //зададим начальное значение счетчика:
  i:= 1;
  //теперь сам цикл:
  repeat
    s:= s + 'Квадрат от ' + IntToStr(i) + ' равен ' + IntToStr(i * i) + #13;
    i:= i + 1;
  until i > 10;
  //отчет:
  ShowMessage(s);
end;
    

Как видите, внутри цикла выполняется два оператора, при этом никаких скобок begin…end не используется. Внутри цикла мы обеспечиваем наращивание счетчика - если этого не сделать, программа зациклится, так как i никогда не станет больше 10. В нашем примере i увеличивается, и как только она станет больше 10, цикл прекращает работу, управление передается дальше, на ShowMessage(). В результате мы получим следующий отчет:

(рис 10.4) Работа цикла repeat…until

Операторы break и continue также можно использовать в этом цикле.

Переключатель case

Переключатель case не относится к циклам, это оператор множественного выбора. Синтаксис:

case <Селектор> of
   <Значение 1>: <Оператор 1>;
  <Значение 2>: <Оператор 2>;
  …
  <Значение n>: <Оператор n>
  else <Оператор Иначе>;
end;
    

Здесь, Селектор - это переменная или выражение исчисляемого типа. То есть, как правило, это целое число или символ (для ПК символ - число, номер в таблице ANSI, то есть, исчисляемый тип), или выражение, которое возвращает в результате целое число или символ.

Далее, в зависимости от значения селектора, выполняется соответствующий оператор. Если нужно выполнить блок операторов, их заключают в операторские скобки begin…end.

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

Для примера установим на форму последнюю, четвертую кнопку, в Caption которой напишем:

case
    

Код события нажатия на кнопку будет таким:

procedure TfMain.Button4Click(Sender: TObject);
var
  i: integer;
  s: string;
begin
  //очистим строку:
  s:= '';
  //получим число от пользователя:
  InputQuery('Квадрат целого числа', 'Введите целое число от 1 до 3:', s);
  //преобразуем строковое представление числа в число:
  i:= StrToInt(s);
  //теперь выбор case
  case i of
    1: ShowMessage('Квадрат от 1 равен 1');
    2: ShowMessage('Квадрат от 2 равен 4');
    3: ShowMessage('Квадрат от 3 равен 9')
    else ShowMessage('Неизвестное число, нет решения');
  end;
end;
    

Код достаточно понятен, чтобы его дополнительно комментировать. Когда пользователь нажмет на кнопку "case", выйдет запрос на ввод целого числа. Если ввести число от 1 до 3, то будет выполнен соответствующий оператор. Если целое число будет другим, выполнится оператор else. Если ввести не целое число, а что-то другое, то произойдет ошибка, которую можно будет снять, сбросив отладчик.

Если вам потребуется выполнить более одного оператора в каждом случае, то это можно оформить, например, так (код для примера, выполнять его вам не нужно):

case i of 
  1 : begin
          ShowMessage('i = 1');
          f:= 10;
      end;
  2 : begin
          ShowMessage('i = 2');
          f:= 20;
      end;
  3 : begin
          ShowMessage('i = 3');
          f:= 30;
      end;
  else ShowMessage ('i не равно 1, 2, или 3'); 
end; //конец case
    

Собственно, этот же код можно было бы реализовать и посредством условного оператора if:

if i = 1 then …
else i = 2 then …
    

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

Вернуться к учебному плану