tuple 类型
tuple
是类似pair
的模板。
每个pair
的成员类型都不相同,但每个 pair
都恰好有两个成员。不同tuple
类型的成员类型也不相同,但一个tuple
可以有任意数量的成员。
每个确定的tuple
类型的成员数目是固定的,但一个tuple
类型的成员数目可以与另一个tuple
类型不同。
tuple
类型及其伴随类型和函数都定义在tuple
头文件中。tuple
支持地操作如下:
定义和初始化 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,5})
必须使用直接初始化语法: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_pair
,标准库定义了 make_tuple
函数,可以生成 tuple
对象:
auto item = make_tuple("0-999-78345-X", 3, 20.00);
访问 tuple 的成员
tuple
的成员都是未命名的:因为一个tuple
类型的成员数目是没有限制的。
要访问一个tuple
的成员,就要使用一个名为get
的标准库函数模板。
auto book = get<0>(item); //返回 item 的第一个成员
auto cnt = get<1>(item); //返回 item 的第二个成员
auto price = get<2>(item)/cnt; //返回 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
确定一个对象的类型的最简单方法就是使用decltype
tuple_size
有一个名为value
的public static
数据成员,它表示给定tuple
中成员的数量。
tuple_element
模板除了一个tuple
类型外,还接受一个索引值。它有一个名为type
的 public
类型成员,表示给定tuple
类型中指定成员的类型。类似get
,tuple_element
所使用的索引也是从0
开始计数的。
关系和相等运算符
只有两个tuple
具有相同数量的成员时,才可以比较它们。
而且,为了使用tuple
的相等或不等运算符,对每对成员使用==
运算符必须都是合法的;为了使用关系运算符,对每对成员使用<
必须都是合法的。
使用 tuple 返回多个值
tuple
的一个常见用途是从一个函数返回多个值。
一些使用嵌套 pair 的地方可以使用 tuple 来代替。
bitset 类型
标准库定义了bitset
类,使得位运算的使用更为容易,并且能够处理超过最长整型类型大小的位集合。bitset
类定义在头文件bitset
中。
定义和初始化 bitset
bitset
类是一个类模板,类似array
类,具有固定的大小。定义一个bitset
时,需要声明它包含多少个二进制位,大小必须是一个常量表达式:
bitset<32> bitvec(1U) //32 位;低位位1,其他位为 0
//定义bitvec为一个包含32位的 bitset
二进制位的位置是从0
开始编号的。因此,bitvec包含编号从0
到31
的32
个二进制位。编号从0
开始的二进制位被称为低位,编号到31结束的二进制位被称为高位。
初始化 bitset 的方法有:
用 unsigned 值初始化 bitset
当我们使用一个整型值来初始化bitset
时,此值将被转换为unsigned long long
类型并被当作位模式来处理。bitset
中的二进制位将是此模式的一个副本。
如果bitset
的大小大于一个unsigned long long
中的二进制位数,则剩余的高位被置为0
;
如果bitset
的大小小于一个unsigned long long
中的二进制位数,则只使用给定值中的低位,超出bitset
大小的高位被丢弃。
从一个 string 初始化 bitset
我们可以从一个string
或一个字符数组指针来初始化bitset
。两种情况下,字符都直接表示位模式。
与往常一样,当我们使用字符串表示数时,字符串中下标最小的字符对应高位,反之亦然。
如果string
包含的字符数比bitset
少,则bitset
的高位被置为0。
string
的下标编号习惯与bitset
恰好相反:string
中下标最大的字符(最右字符)用来初始化bitset
中的低位(下标为0的二进制位)。
bitset 操作
提取 bitset 的值
to_ulong
和to_ullong
操作都返回一个值,保存了与bitset
对象相同的位模式。
只有当bitset
的大小小于等于对应的大小(to_ulong
为unsigned long
,to_ullong
为unsigned long long
)时,才能使用这两个操作。
如果 bitset
中的值不能放入给定类型中,则这两个操作会抛出一个overflow_error
异常。
bitset 的 IO 运算符
输入运算符从一个输入流读取字符,保存到一个临时的string
对象中。直到读取的字符数达到对应bitset
的大小时,或是遇到不是1
或0
的字符时,或是遇到文件尾或输入错误时,读取过程才停止。
随即用临时string
对象来初始化bitset
。如果读取的字符数小于bitset
的大小,则与往常一样,高位将被置为0
。
正则表达式
正则表达式是一种描述字符序列的方法,是一种极其强大的计算工具。C++ 的正则表达式库(RE库)定义在头文件 regex 中。
正则表达式库组件
使用正则表达式库
regex_search 和 regex_match
//这两个操作返回都是 bool 值
regex_match(seq, m, r, mft); //如果整个字符序列 seq 与 regex 对象 r 中的正则表达式匹配就返回 true。
regex_search(seq, m, r, mft); //如果输入序列 seq 中的一个子串与 regex 对象 r 中的正则表达式匹配就返回 true
//seq 可以是一个 string、字符指针或是一对表示范围的迭代器
//上面的参数 m 和 mft 都可以省略。m 是一个 match 对象,用来保存匹配结果的相关细节。
// i 除非在 c 之后,否则必须在 e 之前
//查找不在字符c之后的字符串eistring 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(test_str,results,r))//如果有匹配子串
cout <<results.str()<<endl;//打印匹配的单词
//首先定义了一个string来保存希望查找的正则表达式。正则表达式[^c]表明我们希望匹配任意不是' c'的字符,而[^c]ei指出我们想要匹配这种字符后接ei的字符串。
//此模式描述的字符串恰好包含三个字符。我们想要包含此模式的单词的完整内容。
//为了与整个单词匹配,我们还需要一个正则表达式与这个三字母模式之前和之后的字母匹配。
//这个正则表达式包含零个或多个字母后接我们的三字母的模式,然后再接零个或多个额外的字母。
//默认情况下,regex使用的正则表达式语言是ECMAScript。
//在 ECMAScript中,模式[[::alpha:]] 匹配任意字母,符号+和*分别表示我们希望“一个或多个”或‘零个或多个”匹配。因此 [[::alpha:]]*将匹配零个或多个字母。
随机数
传统生成随机数的方法:rand()
生成结果:一个伪随机整数,范围在 0 到一个与系统相关的最大值之间
存在的问题:生成其他范围需要程序员来手动转换,常引入非随机性。
C++ 新标准的方法:使用随机数引擎类和随机数分布类协作来生成
引擎: 类型,生成 unsigned
随机数序列
分布: 类型,使用引擎生成指定类型的、在给定范围内的、服从特定概率分布的随机数
随机数引擎和分布
随机数引擎是函数对象类,它们定义了一个调用运算符,该运算符不接受参数并返回一个随机 unsigned
整数。
可以通过调用一个随机数引擎对象来生成原始随机数:
default_random_engine e; //生成随机无符号数
for (size_t i=0; i<10; ++i)
// e()“调用”对象来生成下一个随机数
cout << e() << " ";
标准库定义了多个随机数引擎类,区别在于性能和随机性质量不同。每个编译器都会指定其中一个作为default_random_engine
类型。此类型一般具有最常用的特性。
随机数引擎操作
分布类型和引擎
//为了得到在一个指定范围内的数,我们使用一个分布类型的对象:
//生成0到9之间(包含)均匀分布的随机数
uniform_int_distribution<unsigned> u(0,9);
default random engine e; //生成无符号随机整数
for (size_t i=0; i<10; ++i)
//将u作为随机数源
//每个调用返回在指定范围内并服从均匀分布的值
cout << u(e) <<" ";
类似引擎类型,分布类型也是函数对象类。分布类型定义了一个调用运算符,它接受一个随机数引擎作为参数。分布对象使用它的引擎参数生成随机数,并将其映射到指定的分布。
一般情况下,说随机数发生器时,是指分布对象和引擎对象的组合。
一个给定的随机数发生器一直会生成相同的随机数序列。
一个函数如果定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为static
的。否则,每次调用函数都会生成相同的序列。
设置随机数发生器种子:引擎可以利用它从序列中一个新位置重新开始生成随机数。每次运行程序都会生成不同的随机结果。
为引擎设置种子有两种方式:1.在创建引擎对象时提供种子;2.调用引擎的seed
成员。
default_random_engine e1(time(0));//稍微随机些的种子
如果程序作为一个自动过程的一部分反复运行,将time
的返回值作为种子的方式就无效了;它可能多次使用的都是相同的种子。
随机数引擎生成unsigned
数,范围内的每个数被生成的概率都是相同的。
分布类型的操作
由于引擎返回相同的随机数序列,所以必须在循环外声明引擎对象。否则,每步循环都会创建一个新引擎,从而每步循环都会生成相同的值。类似的,分布对象也要保持状态,因此也应该在循环外定义。
IO库再探
IO 库的三个特殊的特性:格式控制、未格式化IO、随机访问
格式化输入和输出
除了条件状态外,每个 iostream 对象还维护一个格式状态来控制 IO 如何格式化的细节:整型值是几进制、浮点值的精度、一个输出元素的宽度等。
标准库定义了一组操纵符来修改流的格式状态:一个操纵符是一个函数或是一个对象,会影响流的状态,并能用作输入或输出运算符的运算对象。
操纵符改变格式状态
操纵符用于两大类输出控制:控制数值的输出形式以及控制补白的数量和位置。
大多数改变格式状态的操纵符都是设置/复原成对的;一个操纵符用来将格式状态设置为一个新值,而另一个用来将其复原,恢复为正常的默认格式。
当操纵符改变流的格式状态时,通常改变后的状态对所有后续 IO 都生效。
控制布尔值的格式
操纵符改变对象的格式状态的一个例子是 boolalpha
操纵符。默认情况下,bool
值打印为1
或0
。一个true
值输出为整数1
,而false
输出为0
。
可以通过对流使用boolalpha操纵符来覆盖这种格式:
cout<< "default bool values: " << true<< " " << false
<< "\nalpha bool values: "<<boolalpha
<< true << " " << false <<endl;
//<<default bool values: 1 0
//<<alpha bool values: true false
bool bool_val = get_status ();
cout << boolalpha //设置cout的内部状态
<<bool val
<<noboolalpha; //将内部状态恢复为默认格式
指定整型值的进制
默认情况下,整型值的输入输出使用十进制。可以使用操纵符hex
、oct
和 dec
将其改为十六进制、八进制或是改回十进制:
cout << 20; // 输出 10 进制
cout << oct << 20; // 输出 8 进制
cout << hex << 20; // 输出 16 进制
cout << dec << 20; // 恢复十进制
操纵符hex
、oct
和 dec
只影响整型运算对象,浮点值的表示形式不受影响。
在输出中指出控制
在输出中指定进制:showbase
,noshowbase
,
控制字母大小写:uppercase
, nouppercase
前导0x表示十六进制。
前导0表示八进制。
无前导字符串表示十进制。
cout << showbase << hex << 30; // 打印 0x1e
cout << uppercase << 30; // 打印 0X1E
控制浮点数格式
从三个方面设置浮点数格式:
1.以多高精度打印,精度控制的是打印的字数的总数。默认为总共打印 6 位数字;
2.打印为十六进制、定点十进制还是科学计数法形式。默认十进制,很大或很小时为科学计数法,其他为定点十进制;
3.没有小数部分的浮点值是否打印小数点。默认不打印小数点。
指定打印精度
除了操纵符 setprecision() 还可以调用 IO 对象的 precision() 成员来指定精度或获取当前精度。
cout << setprecision(4) << 3.1415; // 设置精度为 4 位,打印 3.142
cout << cout.precision(); // 打印当前精度:4
cout.precision(6); // 设置精度为 6 位
setprecision()
本来控制的是数字的整体精度(而不是小数位数),如果同时也用上 fixed,setprecision()
指定的就是小数位数了。
cout << fixed << setprecision(2) << 3.1415; // 设置小数位数为 2 位,打印 3.14
打印小数点
默认情况下,当一个浮点值的小数部分为0
时,不显示小数点。showpoint
操纵符强制打印小数点:
cout << 10.0 << endl; //打印10
cout << showpoint << 10.0 //打印10.0000
<< noshowpoint << endl; // 恢复小数点的默认格式
输出补白
setw(n)
:指定下一个数字或字符串值的最小空间
left
:左对齐输出
right
:右对齐输出。默认为右对齐
internal
:控制负数的符号位置:左对齐符号,右对齐值,中间为空格
setfill('#')
:指定用 '#'
代替空格来补白。
注意到 setw(n)
是一个例外,不改变格式状态,只决定下一个输出的大小。
控制输入格式
默认情况下,输入运算符会忽略空白符(空格符、制表符、换行符、换纸符和回车符)。
操纵符noskipws
会令输入运算符读取空白符,而不是跳过它们;为了恢复默认行为,可以使用skipws
操纵符。
未格式化的输入/输出操作
标准库还提供了一组低层操作,支持未格式化IO。这些操作允许将一个流当作一个无解释的字节序列来处理。
单字节操作
将字符放回输入流
有时需要读取一个字符才能知道还未准备好处理它。这种情况下,希望可以将字符放回流中。
标准库提供了三种方法退回字符,它们有着细微的差别:
1.peek
返回输入流中下一个字符的副本,但不会将它从流中删除,peek
返回的值仍然留在流中;
2.unget
使得输入流向后移动,从而最后读取的值又回到流中。即使不知道最后从流中读取什么值,仍然可以调用unget
;
3.putback
是更特殊版本的unget
:它退回从流中读取的最后一个值,但它接受个参数,此参数必须与最后读取的值相同。
一般情况下,在读取下一个值之前,标准库保证我们可以退回最多一个值。即,标准库不保证在中间不进行读取操作的情况下能连续调用putback
或unget
。
从输入操作返回的 int 值
peek()
和无参 get()
都以 int
类型从输入流返回一个字符。
这些函数返回一个int
的原因是:可以返回文件尾标记。
返回 int 要先将返回的字符转换为 unsigned char
,然后将结果提升到 int
。
即使字符集中有字符映射到负值,这些操作返回的int
也是正值
多字节操作
有一些未格式化 IO 操作一次处理大块数据,速度相比于普通输入输出更快。
流随机访问
各种流类型通常都支持对流中数据的随机访问。
重要术语
bitset 标准库类,保存二进制位集合,大小在编译时已知,并提供检测和设置集合中二进制位的操作。
cmatch csub_match
对象的容器,保存一个 regex
与一个 const char*
输入序列匹配的相关。
smatch ssub_match
对象的容器,提供—个regex
与一个string
输入序列匹配的相关信息。容器首元素描述了整个匹配结果。后续元素描述了子表达式的匹配结果。
cregex_iterator 类似sregex_iterator
,唯一的差别是此迭代器遍历一个char
数组。
csub_match 保存一个正则表达式与一个const char*
匹配结果的类型。可以表示整个匹配或子表达式的匹配。
未格式化IO 将流当作无差别的字节流来处理的操作。未格式化操作给用户增加了很多管理IO的负担。
正则表达式 一种描述字符序列的方式。