§8 Инструкции циклов. Цикл for (цикл с параметром). Датчик случайных чисел

Цикл с параметром. Инструкция for

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

Синтаксис:

for (инициализация; условие; модификация) {
    Тело цикла;
}

Цикл работает следующим образом:
1. В начале происходит инициализация переменной-счетчика. Чаще всего здесь же происходит и объявление переменной счетчика. Инициализация производится единожды, до выполнения цикла.
2. Затем проверяется выражение-условие: если выражение имеет значение true, будет выполняться тело цикла.
3. После выполнения инструкций тела цикла производится модификация (изменение) величины счетчика; обычно для этого используются операции инкремента или декремента. Наример.
Программа 8.1 Вывести натуральные числа от 1 до n.

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

int main() {
	int n;
	cout << "n = "; cin >> n;
	for (int i = 1; i <= n; i++)
		cout << i << setw(3); 
	return 0;
}

Если в теле цикла более чем одна инструкция (блок), то инструкции тела цикла необходимо заключать в { }.
Например. Вывести n чисел Фибоначчи (3 <= n <= 30). Числа Фибоначчи: f1 = 1, f2 = 1, fn = fn-1 + fn-2
Программа 8.2

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

int main() {
	int n, f, f1 = 1, f2 = 1;
	cout << "n = "; cin >> n;
	cout << "fibo  1 = " << f1 << endl;
	cout << "fibo  2 = " << f2 << endl;
	for (int i = 3; i <= n; i++) {
		f  = f1 + f2;
	    f1 = f2;
	    f2 = f;
	    cout << "fibo" << setw(3) << i << " = " << f << endl;
	}
	return 0;
}

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

n = 10
fibo  1 = 1
fibo  2 = 1
fibo  3 = 2
fibo  4 = 3
fibo  5 = 5
fibo  6 = 8
fibo  7 = 13
fibo  8 = 21
fibo  9 = 34
fibo 10 = 55

Область видимости данных

Мы уже знакомы с понятием составной инструкции, заключенной в фигурные скобки. Фигурные скобки определяют не только блок (составную инструкцию), но также и область видимости переменных объявляемых внутри этого блока (включая заголовок цикла). Если попытаться получить доступ к счетчику вне тела цикла, то программа выдаст ошибку. Переменная i (например, в программе 8.2) является локальной переменной цикла - её видимость определяют { }. После завершения работы цикла переменная i, объявленная в заголовке, уничтожается и получить значение i (вне цикла) станет невозможным.
Примечание. Переменная-счетчик не обязательно должна быть локальной. Если есть необходимость, то переменная-счетчик может быть объявлена вне цикла. В этом случае спецификатор типа (например, int) не используется. Тип данных счетчика может быть любой из арифметических типов, но чаще всего int.
Если переменная определена как локальная, но не была произведена инициализация этой переменной, то её значение неопределено и обращение к ее значению будет считаться ошибкой. Попытка изменить значения локальных переменных вне блока приведет к ошибке, но внутри блока можно получать и изменять значения всех объектов определенных вне блока (за исключением других локальных объектов). Более подробно локальную и глобальную видимость переменных мы обсудим позднее.

Процессор случайных чисел

В целом ряде задач, в которых требуется моделировать различные явления, возникает необходимость работы с такими величинами, которые получены случайным образом. (Например, игра в подбрасывание кубиков). Для создания наборов случайных чисел в C++ существует библиотека random. В этой библиотеке определены два типа взаимосвязанных классов:

  • random-namber engine - процессоры случайных чисел
  • random-namber distribution - распределения случайных чисел

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

  • uniform_int_distribution<type>
  • uniform_real_distribution<type>

Процесс создания последовательности случайных чисел будет состоять из следующих этапов:

  1. Создание объекта класса default_random_engine. Конструктор может иметь необязательный параметр - начальное значение;
  2. Создание объекта класса распределения с указанием, в качестве аргумента конструктора, промежутка на котором должна построиться последовательность случайных чисел;
  3. Получение случайного числа путем обращения к объекту распределения, которому, в качестве параметра, передается функциональный объект процессора.

На самом деле, случайные числа, генерируемые процессором, не являются случайными, а являются псевдослучайными, поскольку вычисляются по математическим формулам.
Процессор генерирует машинно-зависимую последовательность элементов. Это означает, что при каждом запуске программы будет генерироваться одна и та же последовательность. Для того, чтобы процессор выдавал всегда различные наборы случайных чисел необходимо установить начальное число последовательности. Часто, для инициализации начального значения, используется системный таймер и функция time() из соответствующей библиотеки ctime. Причем функция time() с аргументом "0" вернет количество секунд прошедшее от начала заданной эпохи.
Рассмотрим пример. Разработать программу игры "Угадай число". Компьютер "загадывает" число, которое пользователь должен угадать за известное количество шагов.
Программа 8.3

#include <iostream>
#include <random>
#include <ctime>
using namespace std;

int main() {
	int n, k, a;
    do {
    	n = 10;
    	// Подключаем процессор случайных чисел
    	default_random_engine rnd(time(0));
    	// Определяем распределения и указываем диапазоны
    	uniform_int_distribution<int> d1(1, 50);
    	uniform_int_distribution<int> d2(0, 50);
    	// Компьютер загадал число:
    	k = d1(rnd) + d2(rnd);
    	cout <<"ИГРА \"УГАДАЙ ЧИСЛО!\"\n"
    	       "Введите число от 1 до 100.\n"
    			"У вас " << n << " попыток." << endl;
    	do {
    		if (n <= 0) {
    			cout << "Вы проиграли!"; break;
    		}
    		cout << "Попытка " << 11 - n
    			 << ". Какое число загадал компьютер?\n -> ";
    		cin >> a;
    		if (a > k)
    			cout << " много" << endl;
    		else
    			if (a < k)
    				cout << " мало" << endl;
    			else
    				cout << "Вы выиграли!" << endl;
    		--n;
    	} while (a != k);
    	cout << "Играем ещё? (1 - \"да\" | 0 - \"нет\") => ";
    	cin >> n;
    } while (n);
    return 0;
}

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

ИГРА "УГАДАЙ ЧИСЛО!"
Введите число от 0 до 100.
У вас 10 попыток.
Попытка 1. Какое число загадал компьютер?
 -> 1
 мало
Попытка 2. Какое число загадал компьютер?
 -> 51
 много
Попытка 3. Какое число загадал компьютер?
 -> 2
 мало
Попытка 4. Какое число загадал компьютер?
 -> 30
 много
...
Попытка 8. Какое число загадал компьютер?
 -> 6
 мало
Попытка 9. Какое число загадал компьютер?
 -> 8
Вы выиграли!
Играем ещё? (1 - "да" | 0 - "нет") => 1
У вас 10 попыток
Попытка 1. Какое число загадал компьютер?
 -> 8
 мало
Попытка 2. Какое число загадал компьютер?
 -> 
...

Использование нескольких выражений в разделе инициализации и модификации

Как мы уже отметили ранее, в заголовке инструкции for должны быть три раздела. Выражения, находящееся в этих разделах, можно опускать, но нельзя опускать ";". Если оставить только ;;, то такой цикл будет "бесконечным":

for (;;) { ... }

Выход из такого цикла должен программироваться внутри тела цикла.
C++ поддерживает несколько выражений в разделах инициализации и модификации. Но условие продолжения цикла должно быть одно! Например. Вычислить факториал числа n, не превосходящий 20.
Программа 8.4

#include <iostream>
using namespace std;

int main() {
	unsigned long long n;
	int i, k;
	cout << "k = "; cin >> k; // 0 <= k <= 20
	for(n = 1, i = 1; i <= k; n *= i, ++i);
	cout << k << "! = " << n << endl;
	return 0;
}

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

k = 20
20! = 2432902008176640000

Обратите внимание, что поток вывода (в стр. 9) не относится к телу цикла! Таким образом, тело цикла - это пустая инструкция. Программа 8.4 правильно вычисляет факториал числа от 0 до 20.

Особенности использования continue в for

Рассмотрим следующую программу:
Программа 8.5 Вывести все нечетные числа от 1 до 20.

#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i < 20; i++) {
        if (i % 2 == 0)
            continue;
        cout << i << " ";
    }
    return 0;
}

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

1 3 5 7 9 11 13 15 17 19

Чтобы понять в чем тут дело, рассмотрим каков на самом деле порядок выполнения этого цикла:
1. Проверяется условие, если условие истинно, то делается шаг и переход к п. 2, иначе инструкция вывода в теле цикла пропускается и переход к п. 2;
2. Модифицируется счетчик.
То есть, счетчик модифицируется при любом раскладе в условии. Отсюда и такой вывод.
Заметим, однако, что так программировать эту задачу не следует! Можно обойтись и без условной инструкции:
Программа 8.6

#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i < 20; i += 2) {
        cout << i << " ";
    }
    return 0;
}

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

Так же как и другие инструкции циклов, for поддерживает структуру вложенных циклов. В случае с for запись будет осуществляться более компактно. Например. Вывести таблицу сложения от 1 до 20, представленной в 16-ой системе счисления.
Программа 8.7

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

int main() {
	for (int i = 0; i < 21; i++) {
		for (int j = 0; j < 21; j++) {
			cout << hex 	  // выводить в 16-ой системе
				 << uppercase // символы выводить в вернем регистре
				 << setw(3)
				 << i + j;
		}
		cout << '\n';
	}
	return 0;
}

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

  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 10 11 12 13 14
  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 10 11 12 13 14 15
  2  3  4  5  6  7  8  9  A  B  C  D  E  F 10 11 12 13 14 15 16
  3  4  5  6  7  8  9  A  B  C  D  E  F 10 11 12 13 14 15 16 17
  4  5  6  7  8  9  A  B  C  D  E  F 10 11 12 13 14 15 16 17 18
  5  6  7  8  9  A  B  C  D  E  F 10 11 12 13 14 15 16 17 18 19
  6  7  8  9  A  B  C  D  E  F 10 11 12 13 14 15 16 17 18 19 1A
  7  8  9  A  B  C  D  E  F 10 11 12 13 14 15 16 17 18 19 1A 1B
  8  9  A  B  C  D  E  F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C
  9  A  B  C  D  E  F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D
  A  B  C  D  E  F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E
  B  C  D  E  F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
  C  D  E  F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20
  D  E  F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21
  E  F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22
  F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23
 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24
 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25
 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26
 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27
 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28

Вопросы

1. Может ли быть заменена в программе инструкция цикла for на инструкцию цикла while? Всегда ли это можно сделать?
2. Когда удобнее применять для организации циклов инструкцию for? while?
3. Возможны ли в заголовке инструкции for следующие записи:

    a) for (;a > b && !(a % 2);)
    b) for (a > b;;)
    c) for (;;i = 0)
    d) for (;i = 0;)
    e) for (;;i++, --b)
    f) for (--i;;)
    g) for (b = 0; b != a;) ?

4. Необходимо получать случайные числа из отрезка [10, 99]. В каком виде будет использоваться функция rand()?
5. Переменная i - параметр внешнего цикла, а j - вложенного. Доступна ли будет переменная j во внешнем цикле? i во вложенном цикле?

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

1. Перепишите программу 7.9 (из предыдущего параграфа) с помощью инструкции for.
2. Найти сумму всех n-значных чисел (1 <= n <= 4), кратных k.
3. Найти сумму всех двузначных чисел, не превосходящих двузначного числа k. Условную инструкцию не использовать! (Обратите внимание на то, что в разделе модификации не обязательно использовать инкремент).
4. Составить программу-игру, которая имитирует подбрасывание двух игральных кубиков. Условия выигрыша придумайте самостоятельно.


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