C++ Primer系列 第17章 标准库特殊设施

  • 17.1 tuple类型
    • 17.1.1 定义和初始化tuple
    • 17.1.2 使用tuple返回多个值
  • 17.2 bitset类型
    • 17.2.1 定义和初始化bitset
    • 17.2.2 bitset操作
  • 17.3 正则表达式
    • 17.3.1 使用正则表达式库
    • 17.3.2 匹配与Regex迭代器类型
    • 17.3.3 使用子表达式
    • 17.3.4 使用regex_replace
  • 17.4 随机数
    • 17.4.1 随机数引擎和分布
    • 17.4.2 其他随机数分布
  • 17.5 IO库再探
    • 17.5.1 格式化输入和输出
    • 17.5.2 未格式化的输入/输出操作
    • 17.5.3 流随机访问
  • 小结

虽然我们不能详细介绍所有标准库设施,但仍有一些标准库设施在很多应用中都是有用的:tuple,bitset,正则表达式以及随机数。我们还将介绍一些附加的IO库功能:格式控制,未格式化IO和随机访问。

17.1 tuple类型

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

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

17.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,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对象:

// 表示书店交易记录的tuple,包含:ISBN,数量和每册书的价格
auto item = make_tuple("0-999-78345-X", 3, 20.00);

类似make_pair,make_tuple函数使用初始值的类型来推断tuple的类型。在本例中,item是一个tuple,类型为tuple<const char*, int, double>

17.1.2 使用tuple返回多个值

tuple的一个常见用途是从一个函数返回多个值。例如,我们的书店可能是多家连锁书店中的一家。每家书店都有一个销售记录文件,保存每本书近期的销售数据。我们可能希望在所有书店中查询某本书的销售情况。

17.2 bitset类型

在4.8节中我们介绍了将整形运算对象当作二进制位集合处理的一些内置运算符。标准库还定义了bitset类,使得位运算的使用更为容易,并且能够处理超过最长整形类型大小的位集合。bitset类定义在头文件bitset中。

17.2.1 定义和初始化bitset

bitset类是一个类模板,它类似array,具有固定的大小。当我们定义一个bitset时,需要声明它包含多少个二进制位:
bitset<32> bitvec(1U); // 32位;底位为1,其他位为0
编号从0开始的二进制位被称为地位(low-order),编号到31结束的二进制位被称为高位(high-order)。

从一个string初始化bitset

我们可以从一个string或一个字符数组指针来初始化bitset。两种情况下,字符都直接表示位模式。与往常一样,当我们使用字符串表示数时,字符串中下标最小的字符对应高位,反之亦然:
bitset<32> bitvec4("1100"); // 2,3两位为1,剩余2位为0
如果string包含的字符数比bitset少,则bitset的高位被置为0.

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

17.2.2 bitset操作

使用bitset
为了说明如何使用bitset,我们重新实现4.8节中的评分程序,用bitset代替unsigned long表示30个学生的测验结果——“通过/失败”:

bool status;
// 使用位运算符的版本
unsigned long quizA = 0; // 此值被当作集合使用
quizA |= 1UL << 27; // 指出第27个学生通过了测验
status = quizA & (1UL << 27); // 检查第27个学生是否通过了测验
quizA &= ~(1UL << 27); // 第27个学生未通过测验// 使用标准库类bitset完成等价的操作
bitset<30> quizB; // 每个学生分配1位,所有位都被初始化为0
quizB.set(27); // 指出第27个学生通过了测验
status = quizB[27]; // 检查第27个学生是否通过了测验
quizB.reset(27); // 第27个学生未通过测验

17.3 正则表达式

正则表达式(regular expression)是一种描述字符序列的方法,是一种极其强大的计算工具。我们重点介绍如何使用C++正则表达式库(RE库),它是新标准库的一部分。RE库定义在头文件regex中,它包含多个组件,列于下表中:

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

17.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 friend theif receive";
// 用r在test_str中查找与pattern匹配的子串
if (regex_search(test_str, results, r)) // 如果有匹配子串cout << results.str() << endl; // 打印匹配的单词

默认情况下,regex使用的正则表达式语言是ECMAScript。在ECMAScript中,模式[[:alpha:]]匹配任意字母,符号+和* 分别表示我们希望“一个或多个”或“零个或多个”匹配。因此[[:alpha:]]* 将匹配零个或多个字母。

函数regex_search在输入序列中只要找到一个匹配子串就会停止查找。因此,程序的输出将是:
friend

指定regex对象的选项
作为一个例子,我们可以用icase标志查找具有特定扩展名的文件名。大多数操作系统都是按大小写无关的方式来识别扩展名的——可以将一个C++程序保存在.cc结尾的文件中,也可以保存在.Cc,.cC或是.CC结尾的文件中,效果是一样的。如下所示,我们可以编写一个正则表达式来识别上述任何一种扩展名以及其他普通文件扩展名。

// 一个或多个 字母或数字字符后接一个'.'再接"cpp"或"cxx"或"cc"
regex r("[[:alnum:]] + \\.(cpp|cxx|cc)$", regex::icase);
smatch results;
string filename;
while (cin >> filename) {if (regex_search(filename, results, r)) {cout << results.str() << endl; // 打印匹配结果}
}

就像C++语言中有特殊字符一样,正则表达式语言也通常也有特殊字符。例如,字符点(.)通常匹配任意字符。与C++一样,我们可以在字符之前放置一个反斜线来去掉其特殊含义。由于反斜线也是C++中的一个特殊字符,我们在字符串字面量中必须连续使用两个反斜线来告诉C++我们想要一个普通反斜线字符。因此,为了表示与句点字符匹配的正则表达式,必须写成\\.(2个斜线一个点)。

建议:避免创建不必要的正则表达式
如我们所见,一个正则表达式所表示的“程序”是在运行时而非编译时编译的。正则表达式的编译是一个非常慢的操作,特别是在你使用了扩展的正则表达式语法或是复杂的正则表达式时。因此,构造一个regex对象以及向一个已经存在的regex赋予一个新的正则表达式可能是非常耗时的。为了最小化这种开销,你应该努力避免创建很多不必要的regex。特别是,如果你在一个循环中使用正则表达式,应该在循环外创建它,而不是在每步迭代时都编译它。

17.3.2 匹配与Regex迭代器类型

使用sregex_iterator
作为一个例子,我们将扩展之前的程序,在一个文本文件中查找所有违反“i在e之前,除非在c之后”规则的单词。我们假定名为file的string保存了我们要搜索的输入文件的全部内容。这个版本将使用与前一个版本一样的pattern,但会使用sregex_iterator来进行搜索:

// 查找不在字符c之后的字符串ei
string pattern("[^c]ei");
//我们需要包含pattern的整个单词
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern, regex::icase); // 在进行匹配时将忽略大小写
// 它将反复调用regex_search来寻找文件中的所有匹配
for (sregex_iterator it(file.begin(), file.end(), r), end_it;it != end_it; ++it)cout << it->str() << endl; // 匹配的单词

如果我们对最初版本程序中的test_str运行此循环,则输出将是:
friend
theif

17.3.3 使用子表达式

正则表达式中的模式通常包含一个或多个子表达式(subexpression)。一个子表达式是模式的一部分,本身也具有意义。正则表达式语法通常用括号表示子表达式。
子表达式用于验证数据
子表达式的一个常见用途就是验证必须匹配特定格式的数据。例如:美国的电话号码有十位数字,包含一个区号和七位的本地号码。在编写电话号码模式之前,我们需要介绍一下ECMAScript正则表达式语言的一些特性:

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

为了获得匹配的组成部分,我们需要在定义正则表达式时使用子表达式。每个子表达式用一对括号包围:

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

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

  1. (\\()?表示区号部分可选的左括号
  2. (\\d{3})表示区号
  3. (\\))?表示区号部分可选的右括号
  4. ([-. ])?表示区号部分可选的分隔符
  5. (\\d{3})表示号码的下三位数字
  6. ([-. ])?表示可选的分隔符
  7. (\\d{4})表示号码的最后四位数字

17.3.4 使用regex_replace

正则表达式不仅用在我们希望查找一个给定序列的时候,还用在当我们想将找到的序列替换为另一个序列的时候。例如,我们可能希望将美国的电话号码转换为“ddd.ddd.dddd”的形式,即,区号和后面三位
数字用一个点分割。

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

替换字符串由我们想要的字符组合与匹配的子串对应的子表达式而组成。在本例中,我们希望在替换字符串中使用第二个,第五个和第七个子表达式。而忽略第1,3,4,6个子表达式,因为这些子表达式用来形成号码的原格式而非新格式中的一部分。我们用一个符号$ 后跟子表达式的索引号来表示一个特定的子表达式:

string fmt = "$2.$5.$7";

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

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

此程序的输出为:
908.555.1800

17.4 随机数

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

定义在头文件random中的随机数库通过一组协作的类来解决这些问题:随机数引擎类(random-number engines)和随机数分布类(random-number distribution)。

  • C++程序不应该使用库函数rand,而应该使用default_random_engine类和恰当的分布类对象。

17.4.1 随机数引擎和分布

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

// 生成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) << " ";
}

此代码生成下面这样的输出:
0 1 7 4 5 2 0 6 6 9

  • 当我们说随机数发生器时,是指分布对象和引擎对象的组合。

17.4.2 其他随机数分布

使用新标准库设施,可以很容易地获得随机浮点数。我们可以定义一个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) << " ";
}

这段代码与之前生成的unsigned值的程序几乎相同。但是,由于我们使用了一个不同的分布类型,此版本会生成不同的结果:
0.131538 0.45865 0.218959 0.678865 0.934693 0.518456 …

17.5 IO库再探

在本节中,我们将介绍三个更特殊的IO库特性:格式控制,未格式化IO和随机访问。

17.5.1 格式化输入和输出

定义在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 在符号和值之间添加填充字符
*表示默认流状态

17.5.2 未格式化的输入/输出操作

从输入操作返回的int值
返回int的函数要将它们返回的字符先转换为unsigned char,然后再将结果提升到int。因此,即使字符集中有字符映射到负值,这些操作返回的int也是正值。而标准库使用负值表示文件尾,这样就可以保证与任何合法字符的值都不同。头文件cstdio定义了一个名为EOF的const,我们可以用它来检测从get返回的值是否是文件尾,而不必记忆表示文件尾的实际数值。对我们来说重要的是,用一个int来保存从这些函数返回的值:

int ch; // 使用一个int,而不是一个char来保存get()的返回值
// 循环读取并输出输入中的所有数据
while ((ch = cin.get()) != EOF) {cout.put(ch);
}

17.5.3 流随机访问

各种流类型通常都支持对流中数据数据的随机访问。我们可以重定位流,使之跳过一些数据,首先读取最后一行,然后读取第一行,以此类推。标准库提供了一对函数,来定位(seek)到流中给定的位置,以及告诉(tell)我们当前位置。

seek和tell函数
tellg() 返回一个输入流中(tellg)或输出流中(tellp)标记的当前位置
tellp()
seekg(pos) 在一个输入流或输出流中将标记重定位到给定的绝对地址,pos通常是
seekp(pos) 前一个tellg或tellp返回的值
seekp(off, from) 在一个输入流或输出流中将标记定位到from之前或之后off个字符,
seekg(off, from) from可以是下列值之一
* beg, 偏移量相对于流开始的位置
* cur, 偏移量相对于流当前的位置
* end, 偏移量相对于流结尾的位置

小结

本章介绍了一些特殊IO操作和四个标准库类型:tuple,bitset,正则表达式和随机数。

tuple是一个模板,允许我们将多个不同类型的成员捆绑成单一对象。

bitset允许我们定义指定大小的二进制位集合。

正则表达式库提供了一组类和函数:regex类管理用某种正则表达式语言编写的正则表达式。匹配类保存了某个特定匹配的相关信息。

随机数库由一组随机数引擎和分布类组成。

C++ Primer系列 第17章 标准库特殊设施相关推荐

  1. 《C++Primer》第十七章 标准库特殊设施

    第十七章 标准库特殊设施 tuple类型 tuple是类似pair的模板,每个pair的成员类型都不相同,但是每个pair恰好有两个成员.我们希望将一些数据组合成单一对象,但又不想麻烦地定义一个新数据 ...

  2. C++ Primer 学习笔记 第十七章 标准库特殊设施

    标准库特殊设施 637 初始化tuple #include <iostream> #include <vector> #include <string> #incl ...

  3. [单刷APUE系列]第五章——标准I/O库

    目录 [单刷APUE系列]第一章--Unix基础知识[1] [单刷APUE系列]第一章--Unix基础知识[2] [单刷APUE系列]第二章--Unix标准及实现 [单刷APUE系列]第三章--文件I ...

  4. Lua_第17 章 数学库

    第17 章 数学库 在这一章中(以下关于标准库的几章中相同)我的主要目的不是对每个函数给出完整地说明,而是告诉你标准库可以提供什么功能.为了可以清楚地说明问题,我可能 会忽略一些小的选项或者行为.基本 ...

  5. C++ Primer 5th笔记(chap 17 标准库特殊设施)流随机访问

    1. 流类型通常都支持对流中数据的随机访问. 可以重定位流, 使之跳过一些数据, 首先读取最后一行, 然后读取第一行, 依此类推. 1.1 标准厍提供了一对函数, 来定位( seek )到流中给定的位 ...

  6. C++ Primer 5th笔记(chap 17 标准库特殊设施)多字节低层IO操作

    1. 多字节操作 一些未格式化 IO 操作一次处理大块数据,这些操作要求我们自己分配并管理用来保存和提取数据的字符数组 操作 解释 is.get(sink, size, delim) 从is中读取最多 ...

  7. C++ Primer 5th笔记(chap 17 标准库特殊设施)未格式化的输入/输出操作

    1. 格式化IO 输入和输出运算符(<< 和>>)根据读取或写入的数据类型来格式化它们. 输入运算符忽略空白符 输出应用补白 精度等规则操作 2. 未格式化 IO (unfor ...

  8. C++ Primer 5th笔记(chap 17 标准库特殊设施)IO库 之操纵符

    1. 控制浮点数格式 可以控制浮点数输出三个种格式 • 以多高精度( 多少个数字) 打印浮点值 • 数值是打印为十六进制. 定点十进制还是科学记数法形式 • 对于没有小数部分的浮点值是否打印小数点 1 ...

  9. C++ Primer 5th笔记(chap 17 标准库特殊设施)匹配标志

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

最新文章

  1. Swift中使用构建配置来支持条件编译-b
  2. elasticsearch 启动、停止及更改密码
  3. ajax后台怎么取mapp,后台管理实现
  4. C#日期格式化,时间
  5. python入门之装饰器
  6. 4.1下午英语阅读视频
  7. PPT中放射发散型的文字效果设计技巧
  8. 用css设置多段背景色
  9. 数学函数模块math
  10. 小丁在美国的惬意生活 日常学学英语吃吃BBQ-猎豹体育网
  11. 核心价值观与企业文化管理实践---基于华为的企业文化的管理实践-林 安老师
  12. 深度学习100问之深入理解Regularization(正则化)
  13. Macbook Apple Silicon 环境及常用软件安装
  14. Qt应用程序图标设置任务栏图标设置
  15. 基于深度学习的目标检测算法综述
  16. 一篇标准的审稿意见回复(Response to reviewer)是什么样的?
  17. (19)一篇掌握MySQL数据库基础下 基本操作(外键约束、建表原则、多表查询、子查询)
  18. Meego sailfish
  19. 微信小程序video组件调用腾讯视频的解决方案
  20. http中get/put/post区别

热门文章

  1. android Vibrator开启振动功能
  2. 头部企业走入无人区,国产数智化厂商挑大梁
  3. 达尔豪西大学 计算机科学,达尔豪西大学计算机科学本科专业.pdf
  4. 东原地产:错配的野心与千亿黄粱梦
  5. 搜狗上市总市值53亿美元;微信发布数据报告,9月份日活用户9亿丨价值早报
  6. AEIA2018 | 科大讯飞刘俊峰:人工智能建设美好的世界
  7. [转]FBWCBBWC
  8. 2022年春节记录9
  9. Ajax的Post跨域请求
  10. 2022四川最新食品安全管理员模拟考试试题及答案