Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
За одним исключением, рассматриваемым в разделе 17.4.2, типы распределения являются шаблонами с одним параметром типа шаблона, представляющим тип создаваемых распределением чисел. Эти типы всегда создают либо тип с плавающей точкой, либо целочисленный тип.
У каждого шаблона распределения есть аргумент шаблона по умолчанию (см. раздел 16.1.3). Типы распределения, создающие значения с плавающей точкой, по умолчанию создают значения типа double. Распределения, создающие целочисленные результаты, используют по умолчанию тип int. Поскольку у типов распределения есть только один параметр шаблона, при необходимости использовать значение по умолчанию следует не забыть расположить за именем шаблона пустые угловые скобки, чтобы указать на применение типа по умолчанию (см. раздел 16.1.3):
// пустые <> указывают на использование
// для результата типа по умолчанию
uniform_real_distribution<> u(0,1); // по умолчанию double
Создание чисел с неравномерным распределениемКроме корректного создания случайных чисел в заданном диапазоне, новая библиотека позволяет также получить числа, распределенные неравномерно. Действительно, библиотека определяет 20 типов распределений! Эти типы перечисляются в разделе А.3.
Для примера создадим серию нормально распределенных значений и нарисуем полученное распределение. Поскольку тип normal_distribution создает числа с плавающей запятой, данная программа будет использовать функцию lround() из заголовка cmath для округления каждого результата до ближайшего целого числа. Создадим 200 чисел с центром в значении 4 и среднеквадратичным отклонением 1,5. Поскольку используется нормальное распределение, можно ожидать любых чисел, но приблизительно 1% из них будет в диапазоне от 0 до 8 включительно. Программа подсчитает, сколько значений соответствует каждому целому числу в этом диапазоне:
default_random_engine е; // создает случайные целые числа
normal_distribution<> n(4,1.5); // середина 4, среднеквадратичное
// отклонение 1.5
vector<unsigned> vals(9); // девять элементов со значением 0
for (size_t i = 0; i != 200; ++i) {
unsigned v = lround(n(e)); // округление до ближайшего целого
if (v < vals.size()) // если результат в диапазоне
++vals[v]; // подсчитать, как часто встречается каждое число
}
for (size_t j = 0; j != vals.size(); ++j)
cout << j << ": " << string(vals[j], '*') << endl;
Начнем с определения объектов генератора случайных чисел и вектора vals. Вектор vals будет использован для расчета частоты создания каждого числа в диапазоне 0…9. В отличие от большинства других программ, использующих вектор, создадим его сразу с необходимым размером. Так, каждый его элемент инициализируется значением 0.
В цикле for происходит вызов функции lround(n(е)) для округления возвращенного вызовом n(е) значения до ближайшего целого числа. Получив целое число, соответствующее случайному числу с плавающей точкой, используем его для индексирования вектора счетчиков. Поскольку вызов n(е) может создавать числа и вне диапазона от 0 до 9, проверим полученное число на принадлежность диапазону прежде, чем использовать его для индексирования вектора vals. Если число принадлежит диапазону, увеличиваем соответствующий счетчик.
Когда цикл заканчивается, вывод содержимого вектора vals выглядит следующим образом:
0: ***
1: ********
2: ********************
3: **************************************
4: **********************************************************
5: ******************************************
6: ***********************
7: *******
8: *
Выведенные строки содержат столько звездочек, сколько раз встретилось соответствующее значение, созданное генератором случайных чисел. Обратите внимание: эта фигура не совершенно симметрична. Если бы она была симметрична, то возникли бы подозрения в качестве генератора случайных чисел.
Класс bernoulli_distributionКак уже упоминалось, есть одно распределение, которое не получает параметр шаблона. Это распределение bernoulli_distribution, являющееся обычным классом, а не шаблоном. Это распределение всегда возвращает логическое значение true с заданной вероятностью. По умолчанию это вероятность .5.
В качестве примера распределения этого вида напишем программу, которая играет с пользователем. Игру начинает один из игроков (пользователь или программа). Чтобы выбрать первого игрока, можно использовать объект класса uniform_int_distribution с диапазоном от 0 до 1. В качестве альтернативы этот выбор можно сделать, используя распределение Бернулли. С учетом, что игру начинает функция play(), для взаимодействия с пользователем может быть использован следующий цикл:
string resp;
default_random_engine e; // e имеет состояние, поэтому располагается
// вне цикла!
bernoulli_distribution b; // по умолчанию четность 50/50
do {
bool first = b(e); // если true, программа ходит первой
cout << (first ? "We go first"
: "You get to go first") << endl;
// играть в игру, угадывая, кто ходит первым
cout << ((play(first)) ? "sorry, you lost"
: "congrats, you won") << endl;
cout << "play again? Enter 'yes' or 'no'" << endl;
} while (cin >> resp && resp[0] == 'y');
Для повторного запроса на продолжение игры используем цикл do while (см. раздел 5.4.4).
Поскольку процессоры возвращают ту же последовательность чисел (см. раздел 17.4.1), их объявляют за пределами циклов. В противном случае при каждой итерации создавался бы новый процессор, выдающий каждый раз те же значения. Распределения также могут хранить состояние и также должны определяться вне циклов.
Одна из причин использования в этой программе распределения bernoulli_distribution заключается в том, что это предоставит программе лучший шанс пойти первой:
bernoulli_distribution b(.55); // предоставить программе небольшое
// преимущество
Такое определение b предоставит программе 55/45 шансов на первый ход.
Упражнения раздела 17.4.2Упражнение 17.31. Что случилось бы в программе игры данного раздела, будь объекты b и е определены в цикле do?
Упражнение 17.32. Что случилось бы, будь строка resp определена в цикле?
Упражнение 17.33. Напишите версию программы преобразования слова из раздела 11.3.6, допускающую несколько преобразований для заданного слова и случайно выбирающую применяемое преобразование.
17.5. Еще о библиотеке ввода и вывода
Глава 8 познакомила вас с базовой архитектурой и наиболее часто используемой частью библиотеки ввода-вывода. В этом разделе рассматриваются три более специализированных средства, поддерживаемых библиотекой ввода-вывода: управление форматом, не форматированный ввод-вывод и произвольный доступ.
17.5.1. Форматированный ввод и вывод
Кроме флага состояния (см. раздел 8.1.2), каждый объект iostream имеет также флаг формата, контролирующий подробности формата ввода и вывода. Флаг формата контролирует такие аспекты, как формат записи целочисленных значений, точность значений с плавающей запятой, ширина выводимого элемента и т.д.
Библиотека определяет набор перечисленных в табл. 17.17 и 17.18 манипуляторов (manipulator) (см. раздел 1.2), изменяющих флаг формата потока. Манипулятор — это функция или объект, влияющие на состояние потока и применяемые как операнд оператора ввода или вывода. Как и операторы ввода и вывода, манипулятор возвращает потоковый объект, к которому он применяется; таким образом, можно объединить манипуляторы и данные в один оператор.
Таблица 17.17. Манипуляторы, определенные в объекте iostream
boolalpha Отображать значения true и false как строки *noboolalpha Отображать значения true и false как 0 и 1 showbase Создавать префикс, означающий базу целочисленных значений *noshowbase Не создавать префикс базы чисел showpoint Всегда отображать десятичную точку для значений с плавающей запятой *noshowpoint Отображать десятичную точку, только если у значения есть дробная часть showpos Отображать + для положительных чисел *noshowpos Не отображать + в неотрицательных числах uppercase Выводить 0X в шестнадцатеричной и E в экспоненциальной формах записи *nouppercase Выводить 0x в шестнадцатеричной и е в экспоненциальной формах записи *dec Отображать целочисленные значения с десятичной базой числа hex Отображать целочисленные значения с шестнадцатеричной базой числа oct Отображать целочисленные значения с восьмеричной базой числа left Добавлять дополняющие символы справа от значения right Добавлять дополняющие символы слева от значения internal Добавлять дополняющие символы между знаком и значением fixed Отображать значения с плавающей точкой в десятичном представлении scientific Отображать значения с плавающей точкой в экспоненциальном представлении hexfloat Отображать значения с плавающей точкой в шестнадцатеричном представлении (нововведение С++11) defaultfloat Вернуть формат числа с плавающей точкой в десятичный (нововведение С++11) unitbuf Сбрасывать буфер после каждой операции вывода *nounitbuf Восстановить обычный сброс буфера *skipws Пропускать отступы в операторах ввода noskipws Не пропускать отступы в операторах ввода flush Сбросить буфер объекта ostream ends Вставить нулевой символ, а затем сбросить буфер объекта ostream endl Вставить новую строку, а затем сбросить буфер объекта ostream*Означает стандартное состояние потока