§7 Инструкции циклов. Цикл while и do while. Инструкции break и continue

Инструкция while

Цикл — это периодическое выполнение одних и тех же инструкций по условию. В C++ используется несколько разновидностей циклов. Цикл while реализует алгоритмическую структуру цикл с предстусловием.
Синтаксис

while (условие) {
    Тело цикла
}

Блок-схема
15
В этом цикле условие проверяется вначале. Пока условие имеет значение, отличное от нуля или логическое выражение имеет значение true, то цикл делает один шаг — выполняется некоторый набор инструкций (одна или более), называемый телом цикла, затем снова проверяется условие. Если проверяемое условие ложно изначально, то инструкции, входящие в тело цикла, не выполняются, а выполняются инструкции находящиеся за циклом (после }). Иногда требуется организовать бесконечный цикл. Выход из этого цикла должен быть программируем внутри тела цикла. «Бесконечный цикл» можно организовать следующими условиями: while (true) {} или while (1) {}.
Цикл while является универсальным циклом. В тоже время разработчик должен программировать изменение параметров в теле цикла так, чтобы это влияло на условие выхода. Неверно составленный программный код может привести к «зацикливанию» (невозможности выхода из цикла).
Рассмотрим пример.
Постановка задачи: Вывести на экран натуральные числа от 1 до N.
Для решения данной задачи нам необходимо использовать переменную-счетчик и определить её начальное значение.
Программа 7.1

#include <iostream>
using namespace std;

int main() {
    int n, k = 0;
    cout << "n = "; cin >> n;
    while (k < n) {
        ++k;
        cout << k << " ";
    }
    cout << endl;
    return 0;
}

Цикл можно реализовать более компактно, используя операцию инкремента в самом потоке:

    while (k < n)
        cout << ++k << " ";

В таком случае скобки можно опустить.
Другой пример программы, в которой можно обойтись без счетчика, используя операцию декремента в условии.
Программа 7.2 Вывести n раз число k.

#include <iostream>
using namespace std;

int main() {
    int n, k;
    cout << "n = "; cin >> n;
    cout << "k = "; cin >> k;
    while (n--)
        cout << k << " ";
    cout << endl;
    return 0;
}

Используйте такие возможности C++ очень внимательно.
Часто требуется в цикле «накапливать» сумму или произведение. Для этого в программе используются переменные-накопители. Начальное значение этих переменных не должно влиять на конечный результат. Таким образом для получения суммы начальное значение накопителя (обычно — s) должно быть равно нулю, а для произведения (обычно — p) — единице. Счетчик тоже, своего рода, накопитель, следовательно перед циклом он всегда обнуляется.
Пример накопления произведения — факториал числа. Факториал величина, которая имеет очень быстрый рост. Следовательно, нужно предусмотреть переполнение соответствующего типа.
Программа 7.3 Вычислить факториал числа n, n! = 1 * 2 * 3 * ... * n.

#include <iostream>
using namespace std;

int main() {
    int n, i = 0;
    long long int f = 1;
    cout << "n = "; cin >> n;
    while (i < n) {
        ++i;
        f *= i;
    }
    cout << n << "! = " << f << endl;
    return 0;
}

Данную задачу можно решить и другим способом. В этом примере i используется не как счетчик, а для сохранения начального значения n:
Программа 7.4

#include <iostream>
using namespace std;

int main() {
    int n;
    long long int f = 1;
    cout << "n = "; cin >> n;
    int i = n;
    while (n > 0)
        f *= n--;
    cout << i << "! = " << f << endl;
    return 0;
}

Инструкция do while

Алгоритмическую структуру «цикл с постусловием» в языке программирования C++ реализует инструкция do while.
Блок-схема
16
Синтаксис

do {
    Тело цикла
} while (условие)

Поскольку условие проверяется в конце цикла, то тело цикла (одна или более инструкций) будет выполнено хотя бы один раз. В этом существенное отличие инструкций while и do while.
Пока условие имеет значение, отличное от нуля или true выполняется тело цикла. Если проверяемое условие ложно изначально, то инструкции, входящие в тело цикла, будут выполнены один раз, затем произойдет выход из цикла.
Эту разновидность цикла удобно использовать, когда есть необходимость производить анализ вводимых данных. В общем случае, инструкции while и do while взаимозаменяемы.
Программа 7.5 Вывести n-первых четных положительных чисел (включая 0).

#include <iostream>
using namespace std;

int main() {
    int n, i = 0;
    cout << "n = "; cin >> n;
    do {
        cout << i++ * 2 << " ";
    } while (i < n);
    cout << endl;
    return 0;
}

Инструкция if вложенная в цикл

В циклах очень часто приходится прибегать к использованию условной инструкции. Однако, если задачу всё же можно решить без if, то следует выбирать именно этот вариант, так как if увеличивает время выполнения и сложность алгоритма. Например, задачу (7.5) вывода четных чисел можно было бы решить с помощью if так:

    while (i < n * 2) {
        if (i % 2 == 0) cout << i << " ";
        i++;
    }

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

Инструкция break прерывает выполнение циклов while, do whilefor, который мы рассмотрим на следующем занятии) и передает управление выполнения программы инструкции, следующей за блоком. Это означает, что если используется структура вложенных циклов, то будет осуществлен выход только из вложенного цикла (если break будет выполнена в нем), внешний же цикл продолжит свою работу.
Программа 7.6 Дано натуральное число n. Определить, является ли оно простым.

#include <iostream>
using namespace std;

int main() {
	int n, i = 2;
	bool k = true;
	cout << "n = "; cin >> n;
	while (i <= n / 2) {
		if (!(n % i)) {
			k = false;
			break;
		}
		i++;
	}
	cout << "Число "
		 << n
		 << (k ? " простое" : " не является простым");
	return 0;
}

В отличие от breakcontinue прерывает выполнение только текущей итерации (то есть, выполнение инструкций тела цикла) и передает выполнение на вычисление условия. При этом инструкции, следующие за continue, игнорируются. Данная инструкция применяется не часто, но может быть использована для контроля состояния допустимости значения выражений в теле цикла. Особенность работы continue с циклом for мы рассмотрим на следующем уроке.
Следует сказать, что многие разработчики стараются не использовать в циклах эти инструкции. Однако последние повышают читаемость программы.
Например. Постановка задачи. Протабулировать значение функции f(x) = 2/x с шагом 1 на отрезке [-5, 5]
Программа 7.7

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	int i = -5;
	while (i <= 5) {
		if (i == 0) {
			cout << "В точке 0 - не определена" << endl;
			i += 1;
			continue;
		}
		cout << setprecision(2)
			 << "x = "
			 << setw(2)
			 << i
			 << setw(10)
			 << "f(x) = "
			 << setw(5)
			 << fixed
			 << 2.0 / i
			 << endl;
		i += 1;
	}
	return 0;
}

Вложенные циклы

Когда на каждом шаге цикла требуется выполнять циклические операции применяется структура вложенных циклов, т.е. один цикл находится внутри другого цикла. Посмотрим пример такой программы.
Программа 7.8 Вывести таблицу Пифагора. Таблица Пифагора представляет собой матрицу (квадратную таблицу) в которой в первом столбце и первой строке числа от 1 до 9, а на пересечении строк и столбцов произведение номера строки на номер столбца.

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
	int i = 0;
	while (++i < 10) {
		int j = 0;
		while (++j < 10) {
			cout << i * j << setw(3);
		}
		cout << '\n';
	}
	return 0;
}

Вывод программы:

1  2  3  4  5  6  7  8  9  
2  4  6  8 10 12 14 16 18  
3  6  9 12 15 18 21 24 27  
4  8 12 16 20 24 28 32 36  
5 10 15 20 25 30 35 40 45  
6 12 18 24 30 36 42 48 54  
7 14 21 28 35 42 49 56 63  
8 16 24 32 40 48 56 64 72  
9 18 27 36 45 54 63 72 81  

Следует избегать слишком большой вложенности. Однако, даже при многократной вложенности, можно составить более эффективный алгоритм, в частности, в задачах перебора разрядов числа. Приведем пример такой задачи. Для определения времени выполнения алгоритма воспользуемся библиотекой chrono.
Программа 7.9 Среди трехзначных чисел вывести те числа, сумма разрядов которых равна k.

#include <chrono>
#include <iostream>
using namespace std;

int main() {
    int a = 1, k;
    cout << "k = "; cin >> k;
    auto start = std::chrono::system_clock::now();
    while (a < 10) {
        int b = 0;
        while (b < 10) {
            int c = 0;
            while (c < 10) {
                if (a + b + c == k)
                    cout << a * 100 + b * 10 + c << " ";
                c++;
            }
            b++;
        }
        a++;
    }
    auto end = std::chrono::system_clock::now();
    auto elapsed = end - start;
    cout << "\n" << elapsed.count() << endl;
    return 0;
}
k = 6
105 114 123 132 141 150 204 213 222 231 240 303 312 321 330 402 411 420 501 510 600 
14424

Аналог программы без вложенных while:
Программа 7.10

#include <chrono>
#include <iostream>
using namespace std;

int main()
{
    int a = 100, k;
    cout << "k = "; cin >> k;
    auto start = std::chrono::system_clock::now();
    while (a < 1000) {
        if (a / 100 + a / 10 % 10 + a % 10 == k)
            cout << a << " ";
        a++;
    }
    auto end = std::chrono::system_clock::now();
    auto elapsed = end - start;
    cout << "\n" << elapsed.count() << endl;
    return 0;
}
k = 6
105 114 123 132 141 150 204 213 222 231 240 303 312 321 330 402 411 420 501 510 600 
25704

Хотя результаты тестов с каждым запуском обеих программ будут различаться, тем не менее очевидно, что программа 7.9 выполняется быстрее.

Вопросы

1. Назовите отличия циклов while и do while. Можно ли заменить цикл while на do while? А наоборот?
2. Можно ли заменить инструкции break и continue на другие конструкции языка? Как бы вы изменили программу 7.7?
3. Как работает структура вложенных циклов в программе 7.8?

Практические задания

1. Постановка задачи: Вывести на экран все целые степени числа 2 от 0 до n.
2. Дано вещественное число A и целое число N (> 0). Найти A в степени N: AN = A·A· … ·A
3. Даны целые положительные числа N и K. Найти сумму 1K + 2K + … + NK.


Добавить комментарий