Организация циклов
Для решения ряда задач часто требуется многократное выполнение одних и тех же действий. На практике это реализуется с помощью рекурсивных (см. занятие 5) или итеративных алгоритмов. Суть итеративного процесса заключается в повторении последовательности операций нужное количество раз.
История оператора goto
В те годы, когда программирование находилось еще на начальной стадии развития, использовались только небольшие по размеру и достаточно примитивные программы. Нельзя было назвать приятным и сам процесс их разработки. В таких программах циклы состояли из метки, последовательности команд и оператора безусловного перехода.
В C++ меткой называют идентификатор, за которым следует двоеточие (:). Метка всегда устанавливается перед оператором, на который необходимо будет передать управление. Для перехода на нужную метку используется оператор goto, за которым следует имя метки. Пример использования оператора goto приведен в листинге 7.1.
Листинг 7.1. Организация цикла с помощью оператора goto
1: // Листинг 7.1.
2: // Организация цикла с помощью goto
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter = 0; // инициализация счетчика
9: loop: counter++; // начало цикла
10: cout.<< "counter: " << counter << "\n";
11: if (counter < 5) // проверка значения
12: goto loop; // возвращение к началу
13:
14: cout << "Complete. Counter: " << counter << ".\n";
15: return 0;
16: }
Результат:
counter: 1
counter: 2
countor: 3
counter: 4
counter: 5
Complete. Counter: 5.
Анализ: В строке 8 переменная counter инициализируется нулевым значением. Метка loop: в строке 9 показывает начало цикла. На каждой итерации значение counter yвeличивaeтcя на единицу и выводится на экран. В строке 11 выполняется проверка значения переменной counter. Если оно меньше пяти, значит условие выполняется и управление передается оператору goto, в результате чего осуществляется переход на строку 9. Итеративный процесс выполняется до тех пор, пока значение переменной counter не достигнет пяти. После этого программа выходит за пределы цикла и на экран выводится окончательный результат.
Почему следует избегать оператора goto
Со временем нелестные высказывания в адрес оператора goto участились, впрочем, вполне заслуженно. С помощью оператора goto можно осуществлять переход в любую точку программы - вперед или назад. Такое беспорядочное использование этого оператора привело к появлению запутанных и абсолютно непригодных для восприятия программ, получивших жаргонное название "спагетти". Поэтому последние двадцать лет преподаватели программирования во всем мире твердили студентам одну и ту же фразу: "Никогда не используйте оператор goto".
На смену оператору goto пришли конструкции с несколько более сложной структурой, но и с более широкими возможностями: for, while и do...while. Несмотря на то что после полного искоренения оператора goto структура программ значительно прояснилась, негативные высказывания в его адрес следует признать преувеличенными. Как любой инструмент программирования, при правильном использовании оператор goto может оказаться достаточно полезным. В силу этого комитет ANS1 принял решение оставить этот оператор в языке. Правда, вместе с этим родилась шутка: "Дети! Использование этого оператора в домашних условиях небезопасно!"
Организация циклов с помощью оператора while
В циклах, организованных с помощью оператора while, выполнение последовательности операций продолжается до тех пор, пока условие продолжения цикла истинно. В примере программы в листинге 7.1 значение переменной counter увеличивалось до тех пор, пока не стало равным пяти. Листинг 7.2 демонстрирует тот же алгоритм, реализованный с помощью оператора while.
Листинг 7.2. Организация цикла с помощью оператора while
1: // Листинг 7.2.
2: // Организация цикла с помощью оператора while
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter = 0; // присвоение начального значения
9:
10: while(counter < 5) // проверка условия продолжения цикла
11: {
12: counter++; // тело цикла
13: cout << " counter: " << counter << "\;n";
14: }
15:
16: cout << " Complete. Counter: " << counter << ".\n";
17: return 0;
18: }
Результат:
counter: 1
counter: 2
counter: 3
counter: 4
counter: 5
Complete. Counter: 5.
Анализ: Эта несложная программа показывает пример организации цикла с помощью оператора while. В начале каждой итерации проверяется условие, и, если оно выполняется, управление передается на первый оператор цикла. В нашем примере условию продолжения цикла удовлетворяют все значения переменной counter, меньшие пяти (строка 10). Если условие выполняется, запускается следующая итерация цикла. В строке 12 значение счетчика увеличивается на единицу, а в строке 13 выводится на экран. Как только значение счетчика достигает пяти, тело цикла (строки 11 - 14) пропускается и управление передается в строку 15.
Сложные конструкции с оператором while
Сложность логического выражения, являющегося условием в операторе while, не ограничена. Это позволяет использовать в конструкции while любые логические выражения C++. При построении выражений допускается использование логических операций: && (логическое И), 11 (логическое ИЛИ), а также ! (логическое отрицание). В листинге 7.3 показан пример использования более сложных условий в конструкциях с оператором while.
Листинг 7.3. Сложные условия в конструкциях while
1: // Листинг 7.3.
2: // Сложные условия в конструкциях while
3:
4: include <iostream.h>
5:
6: int main()
7: {
8: unsigned short small;
9: unsigned long large;
10: const unsigned short MAXSMALL=65535;
11:
12: cout << "Enter a small number: ";
13: cin >> small;
14: cout << "Enter a large number: ";
15: cin >> large;
16:
17: cout << "small: " << small << "...";
18:
19: // на каждой итерации проверяются три условия
20: while (small < large && large > 0 && small < MAXSMALL)
21: {
22: if (small % 5000 == 0) // после каждых 5000 строк выводится точка
23: cout << ".";
24:
25: small++;
26:
27: large-=2;
28: }
39:
30: cout << "\nSmall: " << small << " Large: " << large << endl;
31: return 0;
32: }
Результат:
Enter а small number: 2
Enter а large number: 100000
small: 2
Small: 33335 Large: 33334
Анализ: Программа представляет собой простую логическую игру. Вначале предлагается ввести два числа - small и large. После этого меньшее значение увеличивается на единицу, а большее уменьшается на два до тех пор, пока они не "встретятся". Цель игры: угадать число, на котором значения "встретятся".
В строках 12-15 осуществляется ввод значений. В строке 20 проверяется три условия продолжения цикла.
1. Значение переменной small не превышает значения large.
2. Значение переменной large неотрицательное и не равно нулю.
3. Значение переменной small не превышает значения константы MAXSMALL.
Далее, в строке 23, вычисляется остаток от деления числа small на 5000, причем значение переменной small не изменяется. Если small делится на 5000 без остатка, результатом выполнения этой операции будет 0. В этом случае для визуального представления процесса вычислений на экран выводится точка. Затем в строке 26 значение переменной small увеличивается на 1, а в строке 28 значение large уменьшается на 2.
Цикл завершается, если хотя бы одно из условий перестает выполняться. После этого управление передается в строку 29, следующую за телом цикла.
Операторы break и continue
Часто бывает необходимо перейти на следующую итерацию цикла еще до завершения выполнения всех операторов тела цикла. Для этого используется оператор continue.
Кроме того, в ряде случаев требуется выйти за пределы цикла, даже если условия продолжения цикла выполняются. В этом случае используется оператор break.
Пример использования этих операторов приведен в листинге 7.4. Это несколько усложненный вариант уже знакомой игры. В этом случае, кроме меньшего и большего значений, предлагается ввести шаг и целевое значение. Как и в предыдущем примере, на каждой итерации цикла значение переменной small увеличивается на единицу. Значение large уменьшается на два, если меньшее число не кратно значению переменной шага (skip). Игра заканчивается, когда значение переменой small становится больше, чем значение large. Если значение переменной large совпадает с целевым значением (target), выводится сообщение и игра прерывается.
Цель игры состоит в том, чтобы угадать число, в которое "попадет" значение target.
Листинг 7.4. Использование break и continue
1: // Листинг 7.4.
2: // Пример использования операторов break и continue
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: unsigned short small;
9: unsigned long large;
10: unsigned long skip;
11: unsigned long target;
12: const unsigned short MAXSMALL=65535;
13:
14: cout << "Enter a smail number: ";
15: cin >> small;
16: cout << "Enter a large number: ";
17: cin >> large;
18: cout << "Enter a skip number: ";
19: cin >> skip;
20: cout << "Enter a target number; ";
21: cin >> target;
22:
23: cout << "\n"
24:
25: // установка условий продолжения цикла
26: while (small < large && large > 0 && small < MAXSMALL)
27:
28: {
29:
30: small++;
31:
32: if (small % skip == 0) // уменьшить значение large?
33: {
34: cout << "skipping on:" << small << endl;
35: continue;
36: }
37:
38: if (large == target) // проверка попадания в цель
39: {
40: cout << " Target reached!";
41: break;
42: }
43:
44: large-=2;
45: } // конец цикла
46:
47: cout << "\nSmall: " << small << " Large: " << large << endl;
48: return 0;
49: }
Результат:
Enter a small number: 2
Enter a large number: 20
Enter a skip number: 4
Enter a target number: 6
skipping on 4
skipping on 8
Small: 10 Large: 8
Анализ: Как видим, игра закончилась поражением пользователя, поскольку меньшее значение превысило большее, а цель так и не была достигнута.
В строке проверяются условия продолжения цикла. Если значение переменной small меньше значения large, а также если large больше нуля и small не превышает значение константы SMALLINT, управление передается первому оператору тела цикла.
В строке 32 вычисляется остаток от деления значения переменной small на значение skip. Если значение small кратно skip, оператор continue запускает следующую итерацию цикла (срока 26). В результате такого перехода пропускается проверка целевого значения и операция уменьшения значения переменной large.
Сравнение значений target и large выполняется в строке 38. Если эти значения равны, игра заканчивается победой пользователя. В этом случае программа выводит сообщение о победе, работа цикла прерывается оператором break и управление передается в строку 46.
Использование конструкции while(true)
В качестве условия, проверяемого при переходе на очередную итерацию цикла, может выступать любое выражение, корректное с точки зрения синтаксиса языка C++. Цикл выполняется до тех пор, пока это выражение истинно. Для организации так называемых бесконечных циклов в качестве такого выражения применяется логическая константа true. Листинг 7.5 демонстрирует пример бесконечного цикла, выполняющего счет до десяти.
Листинг 7.5. Еще один пример использования оператора while
1: // Листинг 7.5.
2: // Пример "бесконечного" цикла
3:
4: #include <iostream.h>
5:
6: int main()
7: {
8: int counter = 0;
9:
10: while (true)
11: {
12: counter++;
13: if (counter > 10)
14: break;
15: }
16: cout << "Counter: " << counter << "\n";
17: return 0;
18: }
Результат:
Counter: 11
Анализ: Понятно, что условие продолжения цикла, заданное в строке 10, будет выполняться всегда. В теле цикла (строка 12) значение переменной counter увеличивается на единицу. Работа цикла продолжается до тех пор, показначение counter не превысит 10. Выполнение цикла прерывается оператором break в строке 14, и на экран выводится значение переменной counter (строка 16).
Несмотря на то что данная программа работает, ее структуру нельзя назвать оптимальной. Это типичный пример некорректного использования оператора while. Правильным решением была бы организация проверки значения counter в условии продолжения цикла.
Гибкий синтаксис языка C++ позволяет решить одну и ту же задачу множеством различных способов. Поэтому важно научиться выбирать средство, наиболее подходящее в конкретной ситуации.
Организация циклов с помощью конструкции do...while
При организации циклов с помощью оператора while возможна ситуация, когда тело цикла вообще не будет выполняться. Поскольку условие продолжения цикла проверяется в начале каждой итерации, при нарушении истинности выражения, задающего это условие, выполнение цикла будет прервано еще до запуска первого оператора тела цикла. Пример такой ситуации приведен в листинге 7.6.
Листинг 7.6. Преждевременное завершение цикла с while
1: // Листинг 7.6.
2: // Если условие продолжения цикла не выполняется,
3: // тело цикла пропускается.
4:
5: #include <iostream.h>
6:
7: int main()
8: {
9: int counter;
10: cout << "How many hellos?: ";
11: cin >> counter;
12: while (counter > 0)
13: {
14: cout << "Hello!\n";
15: counter--;
16: }
17: cout << "Counter is OutPut: " << counter;
18: return 0;
19: }
Результат:
How many hellos?: 2
Hello!
Hello!
Counter is 0utPut: 0
How many hellos?: 0
Counter is 0utPut: 0
Анализ: В строке 10 вам предлагается ввести начальное значение счетчика, которое записывается в переменную counter. В строке 12 это значение проверяется, а затем в теле цикла уменьшается на единицу. При первом запуске программы начальное значение счетчика равнялось двум, поэтому тело цикла выполнялось дважды. Во втором случае было введено число 0. Понятно, что в этом случае условие продолжения цикла не выполнялось и тело цикла было пропущено. В результате приветствие не было выведено ни разу.
Как же поступить, чтобы сообщение выводилось по крайней мере один раз? С помощью оператора while это сделать невозможно, так как условие проверяется еще до выполнения тела цикла. Один из способов решения этой проблемы - использование оператора if для контроля начального значения переменной counter.
If (counter < 1) // контроль начального значения
counter = 1;
Правда, это довольно <<корявый>> выход из ситуации.
Использование конструкции do...while
При использовании конструкции do...while условие проверяется после выполнения тела цикла. Это гарантирует выполнение операторов цикла по крайней мере один раз. В листинге 7.7 приведен измененный вариант предыдущей программы, в котором вместо оператора while используется конструкция do...while.
Листинг 7.7. Использование конструкции do...while
1: // Листинг 7.7.
2: // Пример использования конструкции do...while
3:
4: include <iostream.h>
5:
6: int main()
7: {
8: int counter;
9: cout << "How many hellos? ";
10: cin >> counter;
11: do
12: {
13: cout << "Hello\n";
14: counter--;
15: } while (counter >0 );
16: cout << "Counter is: " << counter << endl;
17: return 0;
18: }
Результат:
How many hellos? 2
Hello
Hello
Counter is: 0
Анализ: В строке 9 пользователю предлагается ввести начальное значение счетчика, которое записывается в переменную counter. В конструкции do.. while условие проверяется в конце каждой итерации, что гарантирует выполнение тела цикла по меньшей мере один раз. В строке 13 на экран выводится текст приветствия, а в строке 14 значение переменной counter уменьшается на единицу. Условие продолжения цикла проверяется в строке 15. Если оно истинно, выполняется следующая итерация цикла ,(строка 13). В противном случае цикл завершается и управление передается в строку 16.
При использовании в конструкциях do.. .while операторы break и continue дают тот же результат, что и при использовании с оператором while. Единственное различие этих двух методов организации циклов состоит в проверке условия продолжения цикла. В первом случае оно контролируется перед выполнением тела цикла, а во втором - после него.