17.1 tuple类型
- 不同tuple类型的成员类型也不相同,但一个tuple可以有任意数量的成员
- 每个确定的tuple类型的成员数目是固定的,但一个tuple类型的成员数目可以与另一个tuple类型不同
- 将一些数据组合成单一对象
17.1.1 定义和初始化tuple
- 当定义一个tuple对象时,需要指出每个成员的类型
- tuple的默认构造函数会对每个成员进行值初始化
- 也可以为每个成员提供初始值,这个构造函数是explicit的,必须使用直接初始化语法
- make_tuple函数使用初始值的类型来推断tuple的类型
- 访问tuple的成员
- 一个tuple类型的成员数目是没有限制的
- get标准库函数模板,尖括号中的值必须是一个整型常量表达式
- 如果不知道一个tuple准确的类型细节信息,可以使用两个辅助类模板来查询tuple成员的数量和类型
size_t sz = tuple_size<trans>::value; // tuple中数据个数
tuple_element<1, trans>::type cnt = get<1>(item); //cnt的类型是tuple中第1个元素对应的类型
- 关系和相等运算符
-
只有两个tuple具有相同数量的成员时,才可以比较它们
-
为了使用相等或不等运算符,对每对成员使用==必须都是合法的
-
为了使用关系运算符,对每对成员使用<必须都是合法的
-
由于tuple定义了< 和 == 运算符,因此可以将tuple序列传递给算法,并且可以在无序容器中将tuple作为关键字类型
-
17.1.2 使用tuple返回多个值
-
返回tuple的函数
- equal_range算法,返回一个迭代器pair,表示元素的范围;如果两个迭代器相等表示空范围;否则第一个迭代器指向第一条匹配的记录,第二个迭代器指向匹配的尾后位置
-
使用函数返回的tuple
17.2 bitset类型
17.2.1 定义和初始化bitset
-
bitset类是一个类模板,具有固定的大小,当我们定义一个bitset时,需要声明它包含多少个二进制位
- 大小必须是一个常量表达式
- 编号从0开始的二进制位称为低位,编号到31结束的二进制位被称为高位
-
用unsigned值初始化bitset
- 当使用一个整型值来初始化bitset时,该值被转换为unsigned long long类型并被当作位模式处理
-
从一个string初始化bitset
- 当使用字符串表示数时,字符串中下标最小的字符对应高位,反之亦然
17.2.2 bitset操作
-
函数size是一个constexpr函数,因此可以在任何要球常量表达式的地方使用
-
下标运算符对const属性进行了重载
- const版本的下标运算符在指定位置位时返回true
- 非const版本返回bitset定义的一个特殊类型,允许操作指定位的值
-
提取bitset的值
- to_ulong、to_ullong
- 如果bitset中的值不能放入给定类型中,则这两个操作会抛出一个overflow_error异常
-
bitSet的IO运算符
- 输入运算符从一个输入流读取字符,保存到一个临时的string对象中。直到读取的字符数达到对应bitset大小时,或是遇到不是1或0的字符时,或是遇到文件尾或输入错误时,读取过程才停止。随即用临时string对象来初始化bitset
-
使用bitset
17.3 正则表达式
- C++正则表达式库(RE库),定义在头文件regex中
- 函数regex_match 和 regex_search 确定一个给定字符序列与一个给定regex是否匹配。如果整个输入序列与表达式匹配,则regex_match函数返回true;如果输入序列中一个子串与表达式匹配,则regex_search函数返回true
17.3.1 使用正则表示式库
-
默认情况下,regex使用的正则表达式语言是ECMAScript
-
指定regex对象的选项
- regex的构造函数和赋值操作可能抛出类型为regex_error的异常
-
指定或使用正则表达式时的错误
- 正则表达式是在运行时,当一个regex对象被初始化或被赋予一个新模式时,才被”编译“的
- 也就是说,一个正则表示的语法是否正确是在运行时解析的
- 如果正则表达式存在错误,则在运行时标准库会抛出一个类型为regex_error的异常
- 正则表达式的编译是一个非常慢的操作,所以应该避免创建很多不必要的regex
-
正则表达式类和输入序列类型
- 使用RE库类型必须与输入序列类型匹配
17.3.2 匹配与Regex迭代器类型
-
regex迭代器是一种迭代器适配器,被绑定到一个输入序列和一个regex对象上
-
使用sregex_iterator
- 空的sregex_iterator可以起到尾后迭代器的作用
-
使用匹配数据
- 匹配类型有两个名为prefix 和 suffix的成员,分别返回表示输入序列中当前匹配之前和之后部分的ssub_match对象。
- 一个ssub_match对象有两个名为str和length的成员,分别返回匹配的string和该string的大小
17.3.3 使用子表达式
-
正则表达式中的模式通常包含一个或多个子表达式。一个子表达式是模式的一部分,本身也具有意义
-
正则表达式的语法通常用括号表示子表达式
-
每当我们用括号分组多个可行选项时,同时也就声明了这些选项形成子表达式
-
匹配对象除了提供匹配整体的相关信息外,还提供访问模式中每个子表达式的能力。子匹配是按位置来访问的。第一个子匹配位置为0,表示整个模式对应的匹配,随后是每个子表达式对应的匹配
-
子表达式用于数据验证
- 验证必须匹配特定格式的数据
-
使用子匹配操作
17.3.4 使用regex_replace
-
在输入序列中查找并替换一个正则表达式
-
只替换输入序列的一部分
-
用来控制匹配和格式的标志
- 匹配和格式化标志的类型为match_flag_type 定义在名为regex_constants的命名空间中
- std :: regex_constants ::
-
使用格式标志
- 默认情况下,regex_replace输出整个输入序列,未与正则表达式匹配的部分会原样输出;匹配的部分按照格式字符串指定的格式输出
- format_no_copy:不输出输入序列中未匹配的部分
17.4 随机数
-
rand函数存在的问题:
- 转换rand生成的随机数的范围、类型或分布时,常常会引入非随机性
-
随机数引擎类 和 随机数分布类
- 一个引擎类可以生成unsigned 随机数序列
- 一个分布类使用一个引擎类生成指定类型的、在给定范围内的、服从特定概率分布的随机数
17.4.1 随机数引擎和分布
-
随机数引擎是函数对象类,定义了一个调用运算符,该运算符不接受参数并返回一个随机unsigned整数
-
分布类型和引擎
- 分布类型也是函数对象类
- 同样定义了一个调用运算符,接受一个随机数引擎作为参数
- 使用它的引擎参数生成随机数,并将其映射到指定的分布
- 随机数发生器 = 分布对象 + 引擎对象
uniform_int_distribution<unsigned> u(0, 9);
default_random_engine e;
u(e);
-
比较随机数引擎和rand函数
- 随机数引擎生成的unsigned整数在一个系统定义的范围内
- rand函数生成的数的范围在0到RAND_MAX之间
-
引擎生成一个数值序列
- 即使生成的数看起来是随机的,但对一个给定的发生器,每次运行程序它都会返回相同的数值序列
- 可以将引擎和关联的分布对象定义为static的
- 从而每次调用都会生成新的数
- 在函数调用之间会保持住状态
-
设置随机数发生种子
-
为引擎设置种子有两种方式:
- 在创建引擎对象时提供种子
- 调用引擎的seed成员
-
调用系统函数time
- 返回以秒计的时间,因此这种方法只适用于生成种子的间隔为秒级或更长的应用
-
17.4.2 其他随机数分布
-
生成随机实数
-
最常用的方式:rand()的结果除以RAND_MAX
- 这种方法其实不正确
- 随机整数的精度通常低于随机浮点数,有一些浮点值永远不会被生成了
-
可以使用uniform_real_distribution类型的对象
-
-
使用默认分布的默认结果类型
- 分布类型都是模板,具有单一的模板类型参数,表示分布生成的随机数的类型
- 每个分布模板都有一个默认模板实参
-
生成非均匀分布的随机数
- normal_distribution 正态分布
- lround()函数,将每个随机数舍入到最接近的整数
-
bernoulli_distribution类
- 该分布不接受模板参数,是一个普通类,而非模板
- 总是返回一个bool值,返回true的概率是一个常数,默认值是0.5
- 由于引擎返回相同的随机数序列,所以必须在循环外声明引擎对象,否则每步循环都会创建一个新引擎,从而每步循环都会生成相同的值。类似的,分布对象也要保持状态,因此也应该在循环外定义
- bernoulli_distribution是可以调整概率的
bernoulli_distribution b(.55);
17.5 IO库再探
- 格式控制、未格式化IO和随机访问
17.5.1 格式化输入与输出
-
每个iostream对象维护着一个格式状态来控制IO如何格式化细节
-
标准库定义了一组操纵符,来修改流的格式状态
- 一个操纵符是一个函数或是一个对象,会影响流的状态,并能用作输入或输出运算符的运算对象
-
很多操纵符改变格式状态
-
操纵符用于两大类输出控制:
- 控制数值的输出形式
- 控制补白的数量和位置
-
当操纵符改变流的格式状态时,通常改变后的状态对所有后续IO都生效
- 因此最好在不再需要特殊格式时尽快将流恢复到默认状态
-
-
控制布尔值的格式
- boolalpha操纵符
- 打印布尔值的操作变成打印true或false,而非1或0
- 取消时使用noboolalpha
- boolalpha操纵符
-
指定整型值的进制
- 可以使用操纵符hex、oct、dec改为十六进制、八进制、十进制
- 只会影响整型运算对象,浮点值的表示形式不受影响
-
在输出中指出进制
- showbase操纵符 在输出结果中显示进制
- noshowbase恢复
- uppercase 字母输出大写
-
控制浮点数格式
-
默认情况下,浮点值按照六位数字精度打印;如果没有小数部分,则不打印小数点;根据浮点数的值选择打印成定点十进制或科学计数法形式
-
指定打印精度
- 打印时,浮点值按照当前精度舍入而非截断
- 可以通过precision成员或使用setprecision操纵符来改变精度
-
指定浮点数计数法
- 操纵符scientific 使用科学计数法
- 操纵符fixed使用定点十进制
- 操纵符hexfloat 强制使用十六进制格式
- 操纵符defaultfloat 将流恢复到默认状态
- 在执行scientific、fixed、hexfloat后,精度值控制的是小数点后面的数字位数,而默认情况下精度值指定的是数字的总位数
-
打印小数点
- 操纵符showpoint
-
-
输出补白
-
控制输入格式
- 操纵符noskipws令输入运算符读取空白符 skipws恢复
17.5.2 未格式化的输入/输出操作
-
允许将一个流当作一个无解释的字节序列来处理
-
单字节操作
- 读取而不是忽略空白符
-
将字符放回输入流
-
三种方法:
- peek返回输入流中下一个字符的副本,但不会将它从流中删除,peek返回的值仍然留在流中
- unget使得输入流向后移动,从而最后读取的值又回到流中,即使不知道最后从流中读出了什么值,仍然可以调用unget
- putback 更特殊版本的unget,退回从流中读取的最后一个值,接受一个参数,此参数必须与最后读取的值相同
-
一般情况下在读取下一个值之前,标准库保证我们可以退回最多一个值,标准库不保证在中间不进行读取操作的情况下能连续调用putback或unget
-
-
从输入操作返回的int值
- 以int类型从输入流返回一个字符
- 可以返回文件尾标记(char范围中的每个值都表示一个字符,没有额外的值来表示文件尾)
- 将要返回的字符先转换为unsigned char,然后再将结果提升到int
- 使用负值表示文件尾
- 以int类型从输入流返回一个字符
-
多字节操作
- get将分隔符留作istream中的下一个字符
- getline则读取并丢弃分隔符
-
确定读取了多少个字符
- gcount 最后一个未格式化输入操作读取了多少个字符
- 应该在任何后续未格式化输入操作之前调用gcount
17.5.3 流随机访问
-
随机IO本质上是依赖于系统的
-
在大多数系统上,绑定到cin、cout、cerr和clog的流不支持随机访问
-
istream ostream 通常不支持随机访问
-
seek 和 tell函数
-
seek:通过将标记seek到一个给定位置来重定位它
-
tell:tell标记的当前位置
-
标准库实际上定义了两对seek 和 tell 函数,分别用于输入流和输出流
-
-
只有一个标记
- 在一个流中只维护单一的标记,并不存在独立的读标记和写标记
- fstream 和 stringstream 可以读写同一个流, 由单一的缓冲区用于保存读写的数据
- 标记只有一个,用于表示缓冲区中的当前位置
- 标准库将g和p版本都映射到这个单一的标记
- 由于只有单一标记,因此要在读写间切换,就必须进行seek操作来重定位标记
-
重定位标记
-
访问标记
- tell函数通常用来记住一个位置,以便稍后再定位回来