C++ Primer 17 标准库特殊设置

C++11引入了许多新特性,增强了标准库的功能。元组提供了一种灵活的数据结构,用于存储不同类型的数据。bitset简化了位操作,可用于处理二进制数据。正则表达式库提供了一套强大的文本处理工具,用于查找、匹配和替换模式。此外,随机数库改进了随机数生成,支持多种分布和随机数引擎。这些新特性极大地丰富了C++程序员的工具箱。
摘要由CSDN通过智能技术生成

标准库特殊设置

1 tuple 类型(元组)

tuple 是类似 pair 的模板。每个 pair 的成员类型都不相同,但每个 pair 都恰好有两个成员。不同 tuple 类型的成员类型也不相同,但一个 tuple 可以有任一数量的成员每个确定的 tuple 类型的成员数目是固定的,但一个 tuple 类型的成员数目可以与另一个 tuple 类型不同。

tuple 类型及其伴随类型和函数都定义在 tuple 头文件中。

操作含义
tuple<T1, T2, …, Tn> t;所有成员都进行值初始化
tuple<T1, T2, …, Tn> t(v1, v2, …, vn);使用初始值 vi 进行初始化。此构造函数是 explicit 的
make_tuple(v1, v2, …, vn)返回一个用给定初始值初始化的 tuple。tuple 的类型从初始值的类型推断
t1 relop t2tuple 的关系运算使用字典序。两个 tuple 必须具有相同数量的成员。
get<i>(t)返回 t 的第 i 个数据成员的 引用
tuple_size<tupleType>::value一个类模板,可以通过一个 tuple 类型来初始化。表示给定 tuple 类型中 成员的数量
tuple_element<i, tupleType>::type一个类模板,可以通过一个整型常量和一个 tuple 类型来初始化。表示给定 tuple 类型中指定成员的类型

注: 我们可以将 tuple 看作一个“快速而随意”的数据结构。

1.1 定义和初始化 tuple

当我们定义一个 tuple 时,需要指出每个成员的类型:

tuple<size_t, size_t, size_t> threeD;  // 三个成员设置为 0
tuple<string, vector<double>, int, list<int>>
    someVal("constants", {3.14, 2.718}, 42, {0, 1, 2, 3, 4});

tuple 的构造函数时 explicit 的,因此我们必须使用直接初始化语法:

tuple<size_t, size_t, size_t> threeD = {1, 2, 3};  // 错误
tuple<size_t, size_t, size_t> threeD(1, 2, 3);  // 正确

还可以使用 make_tuple 函数来生成 tuple 对象:

auto item = make_tuple("0-999-78345-X", 3, 20.00);
访问 tuple 成员

tuple 的成员都是未命名的。要访问一个 tuple 的成员,就要使用一个名为 get 的标准库函数模板。

auto book = get<0>(item);  // 返回 item 的第一个成员
auto cnt = get<1>(item);  // 返回 item 的第二个成员
auto price = get<2>(item) // 返回 item 的最后一个成员
get<2>(item) *= 0.8;  // 打折 20%

注: 尖括号中的值必须是一个整形常量表达式。

如果不知道一个 tuple 准确的类型细节信息,可以用两个辅助类模板来查询 tuple 成员的数量和类型:

typedef decltype(item) trans;  // trans 是 item 的类型
// 返回 trans 类型对象中的成员数量
size_t sz = tuple_size<trans>::value;  // 返回 3
// cnt 的类型与 item 中第二个成员相同
tuple_element<1, trans>::type cnt = get<1>(item);  // cnt 是一个 int
关系和相等运算符

只有两个 tuple 具有相同数量的成员时,我们才可以比较它们。
注: 由于 tuple 定义了 < 和 == 运算符,我们可以将 tuple 序列传递给算法,并且可以在无序容器中将 tuple 作为关键字类型。

1.2 使用 tuple 返回多个值

tuple 的一个常见用途是从一个函数返回多个值。


2 bitset 类型

标准库还定义了 bitset 类,使得位运算的使用更为容易,并且能够处理超过最长整型类型的位集合。bitset 类定义在头文件 bitset 中。

2.1 定义和初始化 bitset

bieset 类是一个类模板,它类似 array 类,具有固定的大小。当我们定义一个 bitset 时,需要声明它包含多少个二进制位:

bitset<32> bitvec(1U);  // 32位;低位为1,其他位为0

大小必须是一个常量表达式。bitset 中的二进制位也是未命名的,我们通过位置来访问它们。二进制位的位置从 0 开始编号。编号从0开始的二进制位被称为低位,编号到31结束的二进制位被称为高位

操作含义
bitset<n> b;b 有 n 位;每一位均为 0.此构造函数是一个 constexpr
bitset<n> b(u);b 是 unsigned long long 值 u 的低 n 位的拷贝。如果 n 大于 unsigned long long 的大小,则 b 中超出 unsigned long long 的高位被置为0。如果 n 小于 unsigned long long 的二进制位数,则丢弃超出 bitset 大小的高位。此构造函数是一个 constexpr
bitset<n> b(s, pos, m, zero, one);b 是 string s 从位置 pos 开始 m 个字符的拷贝。s 只能包含字符 zero 和 one;如果 s 包含任何其他字符,构造函数会抛出 invalid_argument 异常。字符在 b 中分别保存为 zero 和 one。pos 默认为 0,m 默认为 string::npos,zero 默认为’0’,one 默认为’1’
bitset<n> b(cp, pos, m, zero, one);与上一个构造函数相同,但从 cp 指向的字符数组中拷贝字符。如果未提供 m,则 cp 必须指向一个 C 风格字符串。如果提供了 m,则从 cp 开始必须至少有 m 个 zero 或 one 字符

注: 接受一个 string 或一个字符指针的构造函数是 explicit 的。在新标准中增加了为 0 和 1 指定其他字符的功能。

用 unsigned 值初始化 bitset

当我们用一个整形值来初始化 bitset 时,此值奖杯转换为 unsigned long long 类型并被当作位模式来处理。

// bitvec1 比初始值小;初始值中的高位被丢弃
bitset<13> bitvec1(0xBEEF);  // 1 1110 1110 1111
// bitvec2 比初始值大;它的高位被置 0
bitset<20> bitvec2(0xbeef);  // 0000 1011 1110 1110 1111
// 在 64 位机器中,long long 0ULL 是 64 个 0 比特, ~0ULL 是 64 个 1
bitset<128> bitvec3(~0ULL);  // 0~63 位为 1;64~127 位为 0
用 string 初始化 bitset

string 的下标编号习惯与 bitset 恰好相反:string 中下标最大的字符(最右侧字符)用来初始化 bitset 中的低位(下标为 0 的二进制位)。用 string 初始化一个 bitset 时,要记住这个差别:

bit<32> bitvec4("1100");  // 2、3 两位为 1,剩余两位为 0

2.2 bitset 操作

操作含义
b.any()b 中是否存在置位的二进制位
b.all()b 中所有位置都置位了吗
b.none()b 中不存在置位的二进制位吗
b.count()b 中置位的位数
b.size()一个 constexpr 函数,返回 b 中的位数
b.test(pos)若 pos 位置的位是置位的,返回 true,否则返回 false
b.set(pos, v)
b.set()
将位置 pos 处的位设置为 bool 值的 v。v 默认为 true。
则将 b 中所有位置置位
b.reset(pos)
b.reset()
将位置 pos 处的位复位
将 b 中所有位复位 0
b.flip(pos)
b.flip()
改变位置 pos 处的位的状态
改变 b 中每一位的状态
b[pos]访问 b 中位置 pos 处的位;如果 b 是 const 的,则该位为 1 时 b[pos] 返回一个 bool 值 true,否则返回 false
b.to_ulong()
b.to_ullong()
返回一个 unsigned long 或一个 unsigned long long 的值,其位模式与 b 相同。如果 b 中位模式不能放入指定的结果类型,则抛出一个 overflow_error 异常
b.to_string(zero, one)返回一个 string,表示 b 中的位模式。zero 和 one 的默认值分别是 0 和 1,用来表示 b 中的 0 和 1
os << b将 b 中二进制位打印为字符 1 或 0,打印到流 os 中
is >> b从 is 读取字符存入 b。当下一个字符不是 1 或 0 时,或是已经读入 b.size() 个位时,读取过程停止。

count、size、all、any 和 none 等几个操作都不接收参数,返回整个 bitset 的状态。其他操作,set、reset 和 flip 则改变 bitset 的状态。改变 bieset 状态的成员函数都是重载的。对每个函数,不接受参数的版本对整个集合执行给定的操作;接受一个位置参数的版本则对指定位执行操作。


3 正则表达式

正则表达式是一种描述字符序列的方法,是一种及其强大的计算工具。正则表达式库(RE库)定义在头文件 regex 中,它包含多个组件:

组件含义
regex表示有一个正则表达式的类
regex_match将一个字符序列与一个正则表达式匹配
regex_search寻求第一个与正则表达式匹配的子序列
regex_replace使用给定格式替换一个正则表达式
sregex_iterator迭代器适配器,调用 regex_search 来遍历一个 string 中所有匹配的子串
smatch容器类,保存在 string 中搜索的结果
ssub_matchstring 中匹配的子表达式的结果

regex 类表示一个正则表达式。regex 支持的操作如下:

操作含义
(seq, m, r, mft)

(seq, r, mft )
在字符序列 seq 中查找 regex 对象 r 中的正则表达式。seq 可以是一个 string、表示范围的一对迭代器以及一个指向空字符结尾的字符数组的指针

m 是一个 match 对象,用来保存匹配结果的细节。m 和 seq 必须具有兼容的类型

mft 是一个可选的 regex_constants::match_flag_type 值。

函数 regex_matchregex_search 确定一个给定字符序列与一个给定 regex 是否匹配。

3.1 使用正则表达式库

查找违反众所周知的拼写规则“ i 除非在 c 之后,否则必须在 e 之前”的单词示例:

// 查找不在字符 c 之后的字符串 ei
string pattern("[^c]ei");
// 我们需要包含 pattern 的整个单词 
pattern = "[[:alpha:]]" + pattern + "[[:alpha:]]*";
regex r(pattern);  // 构造一个用于查找模式的 regex
smatch results;  // 定义一个对象保存搜索结果
// 定义一个 string 保存与模式匹配和不匹配的文本
string test_str = "receipt freind theif receive";
// 用 r 在 test_str 中查找与 pattern 匹配的子串
if(regex_search(text_str, results, r))
     cout << results.str() << endl;

这个正则表达式包含零个或多个字母后接我们的三字母的模式,然后再接零个或多个额外的字母。默认情况下,regex 使用的正则表达式语言是 ECMAScript

ECMAScript 中,模式 [[:alpha:]] 匹配任一字母,符号 + 和 * 分别表示我们希望“一个或多个”或“零个或多个”匹配。因此 [[::alpha:]]* 将匹配零个或多个字母。

指定 regex 对象的选项

当我们定义一个 regex 或是对一个 regex 调用 assign 为其赋予新值时,可以指定一些标志来影响 regex 如何操作。这些标志控制 regex 对象的处理过程。对于 6 个标志(正则表达式所用语言的标志),我们必须设置其中一个,且只能设置一个。默认情况下,ECMAScript 标志被设置,从而 regex 会使用 ECMA-262 规范,这也是很多 Web 浏览器所用的正则表达式语言。

操作含义
regex r(re)
regex r(re, f)
re 表示一个正则表达式,它可以是一个 string、一个表示字符范围的迭代对、一个指向空字符结尾的字符数组的指针、一个字符指针和一个计数器或是一个花括号包围的字符列表
f 是指出对象如何处理的标志。f 通过下面列出的值来设置。如果未指定 f,默认值为 ECMAScript
r1 = re将 r1 中的正则表达式替换成 re
r1.assign(re, f)与使用赋值运算符(=)效果相同
r.mark_count()r 中子表达式的数目
r.flags()返回 r 的标志集
f 标志含义
icase在匹配过程中忽略大小写
nosubs不保存匹配的子表达式
optimize执行速度优先于构造速度
ECMAScript使用 ECMA-262 指定的语法
basic使用 POSIX 基本的正则表达式语法
extended使用 POSIX 扩展的正则表达式语法
awk使用 POSIX 版本的 awk 语言的语法
grep使用 POSIX 版本的 grep 的语法
egrep使用 POSIX 版本的 egrep 的语法
指定或使用表达式时的错误

我们可以将正则表达式本身看作一种简单程序设计语言编写的“程序”。这种语言不是由 C++ 编译器解释的。正则表达式是在运行时,当一个 regex 对象被初始化或被赋予一个新模式时,才被“编译”的。
注: 一个正则表达式的语法是否正确是在运行时解析的。

如果我们编写的正则表达式存在错误,则在运行时标准库会抛出一个类型为 regex_error 的异常。类似标准库异常类型,regex_error 有一个描述发生什么错误的 what 操作,和一个返回错误类型对应的数值编码的 code 成员,用来返回某个错误类型对应的数值编码。

类型含义
error_collate无效的元素校对请求
error_ctype无效的字符类
error_escape无效的转义字符或无效的尾置转义
error_backref无效的向后引用
error_brack不匹配的方括号( ‘[’ 或 ‘]’ )
error_paren不匹配的小括号( ‘(’ 或 ‘)’ )
error_brace不匹配的花括号( ‘{’ 或 ‘}’ )
error_badbrace{}中无效的范围
error_range无效的字符范围(如[z-a])
error_space内存不足,无法处理此正则表达式
error_badrepeat重复字符(*、?、+ 或 { ) 之前没有有效的正则表达式
error_complexity要求的匹配过于复杂
error_stack栈空间不足,无法处理匹配

建议: 避免创建不必要的正则表达式
正则表达式的编译是一个非常慢的操作,特别是在使用了扩展的正则表达式语法或是复杂的正则表达式时。

正则表达式类和输入序列类型
如果输入序列类型则使用正则表达式类
stringregex、smatch、ssub_match 和 sregex_iterator
const char*regex、cmatch、csub_match 和 cregex_iterator
wstringwregex、wsmatch、wssub_match 和 wsregex_iterator
const wchar_t *wregex、wcmatch、wcsub_match 和 wcregex_iterator

3.2 匹配与 Regex 迭代器类型

我们可以使用 sregex_iterator 来获取所有匹配。regex 迭代器是一种迭代器适配器,被绑定到一个输入序列和一个 regex 对象上。

sregex_iterator 操作含义
sregex_iterator it(b, e, r);一个 sregex_iterator,遍历迭代器 b 和 e 表示的 string。它调用 sregex_search(b, e, r) 将 it 定位到输入中第一个匹配的位置
sregex_iterator end;sregex_iterator 的尾后迭代器
smatch 操作
操作含义
m.ready()如果已经通过调用 regex_searc h或 regex_match 设置了 m,则返回 true。否则返回 false
m.size()如果匹配失败,返回 0; 否则返回最近一次匹配的正则表达式中子表达式的数目
m.empty()若 m.size() 为 0,则返回true
m.prefix()一个 ssub_match 对象,表示当前匹配之前的序列
m.suffix()一个 ssub_match 对象,表示当前匹配之后的部分
m.format(…)见 3.4 节
m.length(n)第 n 个匹配的子表达式的大小
m.postiong(n)第 n 个子表达式距序列开始的距离
m.str(n)第 n 个子表达式匹配的 string
m[n]对应第 n 个子表达式的 ssub_match 对象
m.begin(), m.end()
m.cbegin(), m.cend()
表示 m 中 sub_match 元素范围的迭代器。

注: 这些操作也适用于 cmatch、wsmatch、wcmatch 和对应的 csub_match、wssub_match、和wcsub_match。

3.3 使用子表达式

正则表达式中的模式通常包含一个或多个子表达式。一个子表达式是模式的一部分,本身也具有意义。正则表达式语法通常用括号来表示子表达式。

// r 有两个子表达式:第一个是点之前表示文件名的部分,第二个表示文件扩展名
regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);

现在我们的模式包含两个括号括起来的子表达式:

  • ([[:alnum:]]+),匹配一个或多个字符的序列
  • (cpp|cxx|cc),匹配文件扩展名

可以通过修改使输出语句只打印文件名:

 if( regex_search(filename, results, r))
    cout << results.str(1) << endl;  // 打印第一个子表达式

调用 regex_search 在名位 filename 的 string 中查找模式 r,并传递 smatch 对象 results 来保存匹配结果。

在本例模式中,如果文件名为 foo.cpp,则 result.str(0) 将保持 foo.cpp;result.str(1) 将保存 foo;result.str(2) 将保存 cpp。

子表达式用于数据验证

子表达式的一个常见用途是验证必须匹配特定格式的数据

ECMAScript 正则表达式语言的一些特性:

  • {d} 表示单个数字,而 {d}{n} 则表示一个n个数字的序列。(如:{d}{3} 匹配三个数字的序列)
  • 在方括号中的字符集合表示匹配这些字符中的任意一个。(如:[-. ] 匹配一个短横线或一个点或一个空格。注意,点在括号中没有特殊含义)
  • 后接 ‘?’ 的组件是可选的。(如:{d}{3}[-. ]?{d}{4} 匹配序列是开始是三个数字,后接一个可选的短横线或点或空格,然后是四个数字。此模式可以匹配 555-0132 或 555.0132 或 555 0132 或 5550132)
  • 类似 C++,ECMAScript 使用反斜线 \ 表示一个字符本身而不是其特殊含义。由于我们的模式包括括号,而括号是 ECMAScript 中的特殊字符,因此必须使用 和 \( 和 \) 来表示括号是我们模式的一部分而不是特殊字符。

由于反斜线 \ 是 C++ 的特殊字符,在模式中每次出现 \ 的地方,必须用一个额外的反斜线来告知是反斜线而不是特殊字符。即我们用 \\{d}{3} 来表示正则表达式 \{d}{3}。

// 整个正则表达式包含七个子表达式:(ddd) 分隔符 ddd 分隔符 dddd
// 子表达式 1、3、4 和 6 是可选的;2、5 和7 保存号码
"(\\()?(\\{d}{3})(\\))?([-. ])?(\\{d}{3})([-. ])?(\\{d}{4})"

由于我们的模式使用了括号,而且必须去除反斜线的特殊含义,因此这个模式很难读。理解此模式最简单的方法是逐个剥离(括号包围的)子表达式:

  • ( \\( )?:表示区号部分可选的左括号
  • ( \\{d}{3} ):表示区号
  • (\\))?:表示区号部分可选的右括号
  • ([-. ])?:表示区号部分可选的分隔符
  • (\\{d}{3}):表示号码的下三位数字
  • ([-. ])?:表示可选的分隔符
  • (\\{d}{4}):表示号码的最后四位数字
使用子匹配操作
操作含义
matched一个 public bool 数据成员,指出此 ssub_match 是否匹配了
first
second
public 数据成员,指向匹配序列首元素和尾后位置的迭代器。如果未匹配,则 first 和 second 是相等的
length()匹配的大小。如果 matched 为 false,则返回 0
str()返回一个包含输入中匹配部分的 string。如果 matched 为 false,返回空 string
s == ssub将 ssub_match 对象 ssub 转化为 string 对象 s。等价于 s = ssub.str()。转换运算符不是 explicit 的
bool valid(const smatch &m)
{
    // 如果区号前有一个左括号
    if(m[1].matched)
	    // 则区号后面有一个右括号,之后紧跟剩余号码或一个空格
        return m[3].matched &&
                (m[4].matched == 0 || m[4].str() == " ");
    else
	    // 否则,区号后面不能有右括号,另外两个组成部分间的分隔符必须匹配
        return !m[3].matched &&
                m[4].str() == m[6].str();
}
使用 regex_replace

正则表达式不仅用在我们希望查找一个给定序列的时候,还用在当我们想将找到序列替换成另一个序列的时候。例如,我们可能希望将美国号码转换为“ddd.ddd.dddd”的形式。

当我们希望在输入序列中查找并替换一个正则表达式时,可以调用 regex_replace。类似搜索函数,它接受一个输入字符序列和一个 regex 对象,还接受一个描述我们想要输出形式的字符串。

下面是正则表达式替换操作:

操作含义
m.format(dest, fmt, mft)

m.format(fmt, mft)
使用格式字符串 fmt 生成格式化输出。匹配在 m 中,可选的 match_flag_type 标志在 mft 中。

第一个版本写入迭代器 dest 指向的目的位置并接受 fmt 参数,可以是一个 string,也可以是表示字符数组范围的一对指针。

第二个版本返回一个 string,保存输出,并接受 fmt 参数,可以是一个 string,也可以是一个指向空字符结尾的字符数组指针。mft 的默认值为 format_default
regex_replace(dest, seq, r, fmt ,mft)

regex_replace( seq, r, fmt , mft)
遍历 seq,用 regex_search 查找和 regex 对象 r 匹配的子串。使用格式字符串 fmt 和可选 match_flag_type 标志来生成输出。

第一个版本写入迭代器 dest 指向的目的位置并接受一对迭代器 seq 表示范围。

第二个版本返回一个 string,保存输出,且 seq 既可以是一个 string,也可以是一个指向空字符结尾的字符数组指针。

在所有情况下, fmt 既可以是一个 string,也可以是一个指向空字符结尾的字符数组指针。mft 的默认值为 match_default。

替换字符串由我们想要的字符组合与匹配的子串对应的子表达式而组成。在本例中,我们希望在替换字符串中使用第二个、第五个和第七个子表达式。而忽略第一个、第三个、第四个和第六个子表达式。我们用一个符号 $ 后跟子表达式的索引号来表示一个特定的子表达式:

string fmt = "$2.$5.$7";  // 将号码格式改为 ddd.ddd.dddd

可以像下面这样使用我们的正则表达式模式和替换字符串:

regex r(phone);  // 用来寻找模式的 regex 对象
string number = "(908) 555-1800";
cout << regex_replace(number, r, fmt) << endl;

程序的输出为:

908.555.1800
用来控制匹配和格式的标志

标准库定义了用来在替换过程中控制匹配或格式的标志,这些标志可以传递给函数 regex_search 或 regex_match 或是类 smatch 的 format 成员。

匹配和格式化标志的类型为 match_flag_type。这些值都定义在名为 regex_constants 的命名空间里。类似 bind 的 placeholders,regex_constants 也定已在命名空间 std 中的命名空间,例如:

using namespace std::regex_contants::format_no_copy;
标志含义
match_default等价于 format_default
match_not_bol不将首字符作为行首处理
match_not_eol不将尾字符作为行尾处理
match_not_bow不将首字符作为单词首处理
match_not_eow不将首字符作为单词尾处理
match_any如果存在多于一个匹配,则可返回任意一个匹配
match_not_null不匹配任何空序列
match_continuous匹配必须从输入的首字符开始
match_prev_avail输入序列包含第一个匹配之前的内容
format_default用 ECMAScript 规则替换字符串
format_sed用 POSIXsed 规则替换字符串
format_no_copy不输出输入序列中未匹配的部分
format_first_only只替换子表达式第一次出现

4 随机数

程序通常需要一个随机数源。在新标准出现之前,C 和 C++ 在老版本都依赖一个简单的 C 库函数 rand 生成随机数。此函数生成均匀分布的伪随机整数,每个随机数的范围在 0 和一个系统相关的最大值(至少为 32767)之间。

定义在头文件 random 中的随机数库通过一组协作的类来解决非随机性的问题:随机数引擎(类型,生成随机 unsigned 整数序列)随机数分布类(类型,使用引擎返回服从特定概率分布的随机数)
注: C++ 程序不应该使用库函数 rand,而应该使用 default_random_engine 类和恰当的分布类对象。

4.1 随机数引擎和分布

随机数引擎是函数对象类,它们定义了一个调用运算符,该运算符不接受参数并返回一个随机 unsigned 整数。我们可以通过调用一个随机数引擎对象来生成原始随机数:

default_random_engine e;  // 生成随机无符号数
for(size_t i = 0; i < 10; ++i)
	// e() 调用对象来生成下一个随机数
    cout << e() << " ";

标准库定义了多个随机数引擎类,区别在于性能和随机性质量不同。每个编译器都会指定其中一个作为 default_random_engine 类型。此类型一般具有最常用的特性。随机数引擎操作如下:

操作含义
Engine e;默认构造函数;使用该引擎类型默认的种子
Engine e(s);使用整型值 s 作为种子
e.seed(s)使用种子s重置引擎状态
e.min()
e.max()
此引擎可生成的最小值和最大值
Engine::result_type此引擎生成的 unsigned 整型类型
e.discard(u)将引擎推进 u 步;u 的类型是 unsigned long long

对于大多数场合,随机数引擎的输出是不能直接使用的,这也是为什么早先我们称之为原始随机数。问题出在生成的随机数的值范围通常与我们需求的不符。

分布类型和引擎

为了得到在一个指定范围内的数,我们使用一个分布类型的对象:

// 生成 0 到 9 之间均匀分布的随机数
uniform_int_distribution<unsigned> u(0, 9);
default_random_engine e;  // 生成无符号随机整数
for( size_t i = 0; i < 10; ++i)
    cout << u(e) << " ";

u 的类型是 uniform_int_distribution<unsigned>。此类型生成均匀分布的 unsigned 值。当我们定义一个这种类型对象时,可以提供想要的最小值和最大值。
注: 当我们说随机数发生器时,是指分布对象和引擎对象的组合。

比较随机数引擎和 rand 函数

调用一个 default_random_engine 对象的输出类似 rand 的输出。随机数引擎生成 unsigned 证书在一个系统定义的范围内,而 rand 生成的数的范围在 0 到 RAND_MAX 之间。

引擎生成一个数值序列

随机数发生器有一个特性,即使生成的数看起来是随机的,但对一个给定的发生器,每次运行程序它都会返回相同的数值序列。序列不变这一事实在调式时非常有用。但另一方面,使用随机数发生器的程序也必须考虑这一特性。

vector<unsigned> good_randVec() {
    static default_random_engine e;
    static uniform_random_distribution<unsigned> u(0, 9);
    vector<unsigned> ret;
    for( size_t i = 0; i< 100; ++i)
        ret.push_back(u(e));
    return ret;
}

一个给定的随机数发生器一直会生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为 static 的。否则,每次调用函数都会生成相同的序列。

设置随机数发生器种子

一旦我们的程序调式完毕,我们通常希望每次运行程序都会生成不同的随即结果,可以通过提供一个种子来达到这一目的。种子就是一个数值,引擎可以利用它从序列中一个新位置重新开始生成随机数。

为引擎设置种子有两种方式:

  1. 在创建引擎对象时提供种子
  2. 调用引擎的 seed 成员

如下所示我们定义了四个引擎,前两个引擎 e1 和 e2 的种子不同,因此应该生成不同的序列。后两个引擎 e3 和 e4 有相同的种子,它们将生成相同的序列:

default_random_engine e1;  // 使用默认种子
default_random_engine e2(2147483646);  // 使用给定的种子值
// e3 和 e4 将生成相同的序列,因为它们使用了相同的种子
default_random_engine e3;  // 使用默认种子
e3.seed(32767);  // 调用 seed 设置一个新种子值
default_random_engine e4(32767);  // 使用给定种子值
for(size_t i = 0; i < 100; ++i) {
    if(e1() == e2())
        cout << "unseeded match at iteration:" << i << endl;
    if(e3() == e4())
        cout << "seeded differs at iteration:" << i << endl;
}

最常用的选择种子的方法时调用系统函数 time。这个函数定义在头文件 ctime 中,他返回从一个特定时刻到当前经过了多少秒。函数 time 接受单个指针参数,他指向用于写入时间的数据结构。如果此指针为空,则函数简单地返回时间:

default_random_engine e1(time(0));  // 稍微随机写的种子

由于 time 返回以秒计的时间,因此这种方式只适用于生成种子的间隔为秒级或更长的应用。
注: 如果程序作为一个自动过程的一部分反复运行,将 time 的返回值作为种子的方式就无效了;它可能多次使用的都是相同的种子。

4.2 其他随机数分布

随机数引擎生成 unsigned 数,范围内的每个数被生成的概率都是相同的。

生成随机实数

使用新标准库设施,可以很容易地获得随机浮点数。我们可以定义一个 uniform_real_distribution 类型对象,并让标准库来处理从随机整数到随机浮点数的映射。与除了 uniform_int_distribution 一样,在定义对象时,我们指定最小值和最大值:

default_random_engine e;  // 生成无符号随机整数
// 0 到 1 的均匀分布
uniform_real_distribution<double> u(0, 1);
for(size_t i = 0; i < 10; ++i)
    cout << u(e) << " ";
操作含义
Dist d;默认构造函数: 使 d 准备好被使用
其他构造函数依赖 Dist 的类型
分布类型的构造函数是 explicit 的
d(e);用相同的 e 连续调用 d 的话,会根据 d 的分布式类型生成一个随机数序列;e 是一个随机数引擎对象
d.min()
d.max()
返回 d(e) 能生成的最小值和最大值
d.reset()重建 d 的状态,使得随后对 d 的使用不依赖 d 已经生成的值
生成非均匀分布的随机数

除了正确生成在指定范围内的数之外,C++11 标准库还可以生成非均匀分布的随机数。实际上,定义了 20 种分布类型。

default_random_engine e;  // 生成随机整数
normal_distribution<> n(4, 1.5);  // 均值 4,标准差 1.5
bernoulli_distribution 类

bernoulli_distribution 类不接受模板参数,因为它是一个普通类,而非模板。此份不总是返回一个 bool 值。它返回 true 的概率是一个常数,此概率的默认值是 0.5。它也允许我们调整概率:

bernoulli_distribution b(.55);

5 IO 库再探

08 IO库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值