第十七章 标准库特殊设施
一、tuple类型
tuple
是类似pair
的模板,每个pair
的成员类型都不相同,但每个pair
都恰好有两个成员。
- 不同的
tuple
类型的成员类型也不相同,一个tuple
可以有任意数量的成员。
- 每个确定的tuple类型的成员数目是固定的,但一个tuple类型的成员数目可以与另一个tuple类型不同。
- 可以将
tuple
看做一个"快速而随意"的数据结构。
tuple
支持的操作:
操作 |
解释 |
tuple<T1, T2, ..., Tn> t; |
t是一个tuple,成员数为n,第i个成员的类型是Ti所有成员都进行值初始化。 |
tuple<T1, T2, ..., Tn> t(v1, v2, ..., vn); |
每个成员用对应的初始值vi进行初始化。此构造函数是explicit的。 |
make_tuple(v1, v2, ..., vn) |
返回一个用给定初始值初始化的tuple。tuple的类型从初始值的类型推断。 |
t1 == t2 |
当两个tuple具有相同数量的成员且成员对应相等时,两个tuple相等。 |
t1 relop t2 |
tuple的关系运算使用字典序。两个tuple必须具有相同数量的成员。 |
get<i>(t) |
返回t的第i个数据成员的引用:如果t是一个左值,结果是一个左值引用;否则,结果是一个右值引用。tuple的所有成员都是public的。 |
tuple_size<tupleType>::value |
一个类模板,可以通过一个tuple类型来初始化。它有一个名为value的public constexpr static数据成员,类型为size_t,表示给定tuple类型中成员的数量。 |
tuple_element<i, tupleType>::type |
一个类模板,可以通过一个整型常量和一个tuple类型来初始化。它有一个名为type的public成员,表示给定tuple类型中指定成员的类型。 |
1. 定义和初始化tuple
// 定义和初始化示例
tuple<size_t, size_t, size_t> threeD; // 三个成员都设置为0
tuple<string, vector<double>, int, list<int>> someVal("constants", {3.14, 2.7}, 42, {0, 1, 2});
// 由于tuple的每个构造函数都是explicit的,所以必须使用直接初始化语法
tuple<size_t, size_t, size_t> threddD = {1, 2, 3}; // 错误
tuple<size_t, size_t, size_t> threddD{1, 2, 3}; // 正确
auto item = make_tuple("TTT", 3, 342.7);
// 访问tuple成员
auto book = get<0>(item); // 返回item的第一个成员
auto cnt = get<1>(item); // 返回item的第二个成员
auto price = get<2>(item) / cnt; // 返回item的最后一个成员
get<2>(item) *= 0.8;
2. 使用tuple返回多个值
二、bitset类型
- 处理二进制位的有序集;
bitset
类也是一个类模板,它类似array类,具有固定的大小。定义一个bitset
时,需要声明它包含多少个二进制位。
bitset<32> bitVect(1U); // 32位,低位为1,其他位为0
操作 |
解释 |
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。此构造函数是一个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字符。 |
// bitVec1比初始值小;初始值中的高位被丢弃
bitset<13> bitVec1(0xbeef); // 二进制位序列为 1111011101111
// bitVec2比初始值大;它的高位被置为0
bitset<20> bitVec2(oxbeef); // 二进制位序列为 00001101111011101111
// 在64位机器中,long long 0ULL是64个0比特,因此~0ULL是64个1
bitset<128> bitVec3(~0ULL); // 0-63位为1;63-127位为0
// 从一个string初始化bitset
bitset<32> bitVec4("1100"); //2、3两位为1,剩余两位为0
- 注意:
string
的下标编号习惯与bitset
恰好相反,string
下标最大的字符用来初始化bitset
中的低位。
bitset
操作:
操作 |
解释 |
b.any() |
b中是否存在1 |
b.all() |
b中都是1 |
b.none() |
b中是否没有1 |
b.count() |
b中1的个数 |
b.size() |
b的位数 |
b.test(pos) |
pos下标是否是1 |
b.set(pos) |
pos置1 |
b.set() |
所有都置1 |
b.reset(pos) |
将位置pos处的位复位 |
b.reset() |
将b中所有位复位 |
b.flip(pos) |
将位置pos处的位取反 |
b.flip() |
将b中所有位取反 |
b[pos] |
访问b中位置pos处的位;如果b是const的,则当该位置位时,返回true;否则返回false |
b.to_ulong() |
返回一个unsigned long值,其位模式和b相同。如果b中位模式不能放入指定的结果类型,则抛出一个overflow_error异常 |
b.to_ullong() |
类似上面,返回一个unsigned long long值 |
b.to_string(zero, one) |
返回一个string,表示b中位模式。zero和one默认为0和1 |
os << b |
将b中二进制位打印为字符1或0,打印到流os |
is >> b |
从is读取字符存入b。当下一个字符不是1或0时,或是已经读入b.size()个位时,读取过程停止 |
bitset<32> bitVec(1U); // 32位,低位为1,剩余位为0
bool is_set = bitVec.any(); // true
bool is_not_set = bitVec.none(); // false
bool all_set = bitVec.all(); // false
size_t onBits = bitVec.count(); // 1
size_t sz = bitVec.size(); // 32
bitVec.filp(); // 翻转bitVec中的所有位
bitVec.reset(); // 将所有位复位
bitVec.set(); // 将所有位置为1
三、正则表达式
- 正则表达式是一种描述字符序列的方法,是一种极其强大的计算工具。
- 作为新标准的一部分,RE库定义在头文件
regex
中。
- 正则表达式库组件:
组件 |
解释 |
regex |
表示一个正则表达式的类 |
regex_match |
将一个字符序列与一个正则表达式匹配 |
regex_search |
寻找第一个与正则表达式匹配的子序列 |
regex_replace |
使用给定格式替换一个正则表达式 |
sregex_iterator |
迭代器适配器,调用regex_searcg来遍历一个string中所有匹配的子串 |
smatch |
容器类,保存在string中搜索的结果 |
ssub_match |
string中匹配的子表达式的结果 |
- regex_search和regex_match的参数:
操作 |
解释 |
注意:这些操作返回bool值,指出是否找到匹配 |
|
(seq, m, r, mft) 、(seq, r, mft) |
在字符序列seq 中查找regex 对象r 中的正则表达式。 seq 可以是一个string 、标识范围的一对迭代器、一个指向空字符结尾的字符数组的指针。 m 是一个match 对象,用来保存匹配结果的相关细节。m 和seq 必须具有兼容的类型。 mft 是一个可选的regex_constants::match_flag_type 值。 |
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(test_str, results, r) { // 如果有匹配子串
cout << results.str() << endl; // 打印匹配的单词,输出freind
}
操作 |
解释 |
regex r(re) regex r(re, f) |
re 表示一个正则表达式,它可以是一个string 、一对表示字符范围的迭代器、一个指向空字符结尾的字符数组的指针、一个字符指针和一个计数器、一个花括号包围的字符列表。f 是指出对象如何处理的标志。f 通过下面列出来的值来设置。如果未指定f ,其默认值为ECMAScript 。 |
r1 = re |
将r1 中的正则表达式替换re 。re 表示一个正则表达式,它可以是另一个regex 对象、一个string 、一个指向空字符结尾的字符数组的指针或是一个花括号包围的字符列表。 |
r1.assign(re, f) |
和使用赋值运算符(= )的效果相同:可选的标志f 也和regex 的构造函数中对应的参数含义相同。 |
r.mark_count() |
r 中子表达式的数目。 |
r.flags() |
返回r 的标志集。 |
操作 |
解释 |
icase |
在匹配过程中忽略大小写 |
nosubs |
不保存匹配的子表达式 |
optimize |
执行速度优先于构造速度 |
ECMAScript |
使用ECMA-262 指定的语法 |
basic |
使用POSIX 基本的正则表达式语法 |
extended |
使用POSIX 扩展的正则表达式语法 |
awk |
使用POSIX 版本的awk 语言的语法 |
grep |
使用POSIX 版本的grep 的语法 |
egrep |
使用POSIX 版本的egrep 的语法 |
- 可以将正则表达式本身看做是一种简单程序语言设计的程序。在运行时,当一个
regex
对象被初始化或被赋予新模式时,才被“编译”。
- 如果编写的正则表达式存在错误,会在运行时抛出一个
regex_error
的异常。
- 避免创建不必要的正则表达式。构建一个
regex
对象可能比较耗时。
2. 匹配与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 的尾后迭代器。 |
*it, it-> |
根据最后一个调用regex_search 的结果,返回一个smatch 对象的引用或一个指向smatch 对象的指针。 |
++it , it++ |
从输入序列当前匹配位置开始调用regex_search 。前置版本返回递增后迭代器;后置版本返回旧值。 |
it1 == it2 |
如果两个sregex_iterator 都是尾后迭代器,则它们相等。两个非尾后迭代器是从相同的输入序列和regex 对象构造,则它们相等。 |
// 查找不在字符c之后的字符串ei
string pattern("[^c]ei");
// 需要包含pattern的整个单词
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern, regex::icase); // 构造一个用于查找模式的regex,忽略大小写
// 它将反复调用regex_search来寻找文件中的所有匹配
if (sregex_iterator it(file.begin(), file.end(), r), end_it; it != end_it; ++it) { // 如果有匹配子串
cout << it->str() << endl; // 打印匹配的单词,输出freind theif
}
操作 |
解释 |
m.ready() |
如果已经通过调用regex_search 或regex_match 设置了m ,则返回true ;否则返回false 。如果ready 返回false ,则对m进行操作是未定义的 |
m.size() |
如果匹配失败,则返回0;否则返回最近一次匹配的正则表达式中子表达式的数目 |
m.empty() |
等价于m.size() == 0 |
m.prefix() |
一个ssub_match 对象,标识当前匹配之前的序列 |
m.suffix() |
一个ssub_match 对象,标识当前匹配之后的部分 |
m.format(...) |
|
m.length(n) |
第n 个匹配的子表达式的大小 |
m.position(n) |
第n 个子表达式距离序列开始的长度 |
m.str(n) |
第n 个子表达式匹配的string |
m[n] |
对应第n 个子表达式的ssub_match 对象 |
m.begin(), m.end() |
表示m 中ssub_match 元素范围的迭代器 |
m.cbegin(), m.cend() |
常量迭代器 |
3. 使用子表达式
- 正则表达式语法通常用括号表示子表达式
- 子表达式的索引从1开始
- 在
fmt
中用$
后跟子表达式的索引号来标识一个特定的子表达式
- 例子:
if(regex_search(filename, results, r))
cout << results.str(1) << endl; // .str(1)获取一个子表达式匹配结果
操作 |
解释 |
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 的 |
4. 使用regex_replace
操作 |
解释 |
m.format(dest, fmt, mft) , m.format(fmt, mft) |
使用格式字符串fmt 生成格式化输出,匹配在m 中,可选的match_flag_type 标志在mft 中。第一个版本写入迭代器dest 指向的目的为止,并接受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 标志来生成输出。mft 的默认值是match_default |
string phone = "(\()?(\d{3})(\))?([-. ])?(\d{3})([-. ]?)(\d{4})"
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
操作 |
解释 |
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 |
用POSIX sed 规则替换字符串 |
format_no_copy |
不输出输入序列中未匹配的部分 |
format_first_only |
只替换子表达式的第一次出现 |
四、随机数
- 新标准之前,C和C++都依赖一个简单的C库函数
rand
来生成随机数,且只符合均匀分布。
- 新标准:随机数引擎 和 随机数分布类,定义在
random
头文件中。
- C++程序应该使用
default_random_engine
类和恰当的分布类对象。
1. 随机数引擎和分布
操作 |
解释 |
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 |
// 初始化分布类型
uniform_int_distribution<unsigned> u(0, 9);
// 初始化引擎
default_random_engine e;
// 随机生成0-9的无符号整数
cout << u(e) << endl;
- 一个给定的随机数发生器一直会生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为
static
的,否则,每次调用函数都会生成相同的序列。
- 如果程序作为一个自动过程的一部分反复运行,将time的返回在作为种子的方式就无效了;它可能多次使用的都是相同的种子。
2. 其他随机数分布
操作 |
解释 |
Dist d |
默认构造函数;使d 准备好被使用。其他构造函数依赖于Dist 的类型;分布类型的构造函数是explicit 的。 |
d(e) |
用相同的e 连续调用d 的话,会根据d 的分布式类型生成一个随机数序列;e 是一个随机数引擎对象。 |
d.min(),d.max() |
返回d(e) 能生成的最小值和最大值。 |
d.reset() |
重建d 的状态,是的随后对d 的使用不依赖于d 已经生成的值。 |
- 由于引擎返回相同的随机数序列,所以我们必须在循环外声明引擎对象。否则每步循环都会创建一个新引擎,从而每步循环都会生成相同的值。类似的,分布对象也要保持状态,因此也应该在循环外定义。
五、IO库再探
1. 格式化输入输出
- 使用操纵符改变格式状态
- 控制布尔值的格式:
cout << boolalpha << true << endl;
- 指定整型的进制:
cout << dec << 20 << endl;
- 定义在
iostream
中的操纵符: 其中 * 代表默认的流状态
操纵符 |
解释 |
boolalpha |
将true 和false 输出为字符串 |
* noboolalpha |
将true 和false 输出为1,0 |
showbase |
对整型值输出表示进制的前缀 |
* noshowbase |
不生成表示进制的前缀 |
showpoint |
对浮点值总是显示小数点 |
* noshowpoint |
只有当浮点值包含小数部分时才显示小数点 |
showpos |
对非负数显示+ |
* noshowpos |
对非负数不显示+ |
uppercase |
在十六进制中打印0X ,在科学计数法中打印E |
* nouppercase |
在十六进制中打印0x ,在科学计数法中打印e |
* dec |
整型值显示为十进制 |
hex |
整型值显示为十六进制 |
oct |
整型值显示为八进制 |
left |
在值的右侧添加填充字符 |
right |
在值的左侧添加填充字符 |
internal |
在符号和值之间添加填充字符 |
fixed |
浮点值显示为定点十进制 |
scientific |
浮点值显示为科学计数法 |
hexfloat |
浮点值显示为十六进制(C++11) |
defaultfloat |
充值浮点数格式为十进制(C++11) |
unitbuf |
每次输出操作后都刷新缓冲区 |
* nounitbuf |
恢复正常的缓冲区刷新方式 |
* skipws |
输入运算符跳过空白符 |
noskipws |
输入运算符不跳过空白符 |
flush |
刷新ostream 缓冲区 |
ends |
插入空字符,然后刷新ostream 缓冲区 |
endl |
插入换行,然后刷新ostream 缓冲区 |
2. 未格式化的输入/输出操作
操作 |
解释 |
is.get(ch) |
从istream is 读取下一个字节存入字符ch 中。返回is 。 |
os.put(ch) |
将字符ch 输出到ostream os 。返回os 。 |
is.get() |
将is 的下一个字节作为int 返回。 |
is.putback(ch) |
将字符ch 放回is 。返回is 。 |
is.unget() |
将is 向后移动一个字节。返回is 。 |
is.peek() |
将下一个字节作为int 返回,但不从流中删除它。 |
操作 |
解释 |
is.get(sink, size, delim) |
从is 中读取最多size 个字节,并保存在字符数组中,字符数组的起始地址由sink 给出。读取过程直到遇到字符delim 或读取了size 个字节或遇到文件尾时停止。如果遇到了delim, 则将其留在输入流中,不读取出来存入sink 。 |
is.getline(sink, size, delim) |
与接收三个参数的get 版本类似,但会读取并丢弃delim 。 |
is.read(sink, size) |
读取最多size 个字节,存入字符数组sink 中。返回is 。 |
is.gcount() |
返回上一个未格式化读取从is 读取的字节数。 |
os.write(source, size) |
将字符数组source 中的size 个字节写入os 。返回os 。 |
is.ignore(size, delim) |
读取并忽略最多size 个字符,包括delim 。与其他未格式化函数不同,ignore 有默认参数:size 默认值是1,delim 的默认值为文件尾。 |
3. 流随机访问
istream
和ostream
类型通常不支持随机访问。
- 通过将标记
seek
到一个给定位置来重定位它。
tell
告诉我们标记的当前位置。
操作 |
解释 |
tellg(),tellp |
返回一个输入流中(tellg )或输出流中(tellp )标记的当前位置。 |
seekg(pos) ,seekp(pos) |
在一个输入流或输出流中将标记重定位到给定的绝对地址。pos 通常是一个当前teelg 或tellp 返回的值。 |
seekp(off, from) ,seekg(off, from) |
在一个输入流或输出流中将标记定位到from 之前或之后off 个字符,from 可以是下列值之一:beg ,偏移量相对于流开始位置;cur ,偏移量相对于流当前位置;end ,偏移量相对于流结尾位置。 |