C++_Primer17.specialized_facilities
标准库特殊设施
specialized library facilities
高级主题部分介绍一些附加特性,分为两类:用于求解大规模问题和适用于特殊问题而非通用问题:
- bitset 类和三个新标准库设施(tuple, 正则表达式和随机数),以及IO库中某些不常用的部分(格式控制,未格式化IO和随机访问)
- 异常处理、命名空间和多重继承
- 特殊工具和技术
- 重定义内存分配机制
- C++对运行时类型识别(run-time type identification, RTTI)的支持,允许在运行时才确定一个表达式的类型
- 定义和使用指向类成员的指针(类成员指针需要反映其所属的类)
- 三种附加的聚合类型:联合、嵌套类和局部类
- 不可移植的语言特性:volatile修饰符、位域和链接指令
tuple 类型
tuple 有任意数量个成员,每个成员类型可以不同,常用于将一些对象组合成单一对象,但又不想麻烦地定义一个新数据结构表示他们:
tuple<size_t, size_t, size_t> threeD; // 都默认初始化成0
tuple<string, vector<double>, int, list<int>>
val("constants", {3.14, 2.718}, 42, {0,1,2,3});
auto item = make_tuple("1234567", 3, 20.00); // tuple<const char*, int, double>
tuple支持的操作:
| 操作 | 说明 |
|---|---|
tuple<T1,T2,...Tn> t; |
成员进行值初始化(默认初始化?) |
tuple<T1,T2,...Tn> t(v1,v2,...vn); |
用初始值进行初始化,此构造函数是explicit的 |
| make_tuple(v1,v2,...vn) | 返回一个用给定初始值初始化的 tuple,成员类型由给定值推断 |
| t1 == t2 t1 != t2 |
成员数量相等,且成员对应相等时,两个tuple相等。 成员是否相等用 == 操作完成。一旦某个成员不相等,后续成员不再比较。 两个tuple必须具有相同数量的成员。 |
| t1 relop t2 | relop是关系运算符,用字典序进行比较。两个tuple必须具有相同数量的成员。 使用<运算符比较对应成员。 |
get<i>(t) |
返回t的第i个数据成员的引用,如果t是左值,则返回左值,否则返回右值 |
tuple_size<tupleType>::value |
一个类模板,通过一个tuple类型来初始化。 它有一个名为value的 public constexpr static 数据成员, 类型为size_t,表示给定tuple类型中成员的数量。tupleType是tuple的类型 |
tuple_element<i, tupleType>::type |
一个类模板,通过一个整型常量和一个tuple类型来初始化。 它有一个名为type的public成员,表示给定tuple类型中指定成员的类型。 |
直接初始化的构造函数(带参数的构造器)是 explicit 的:
tuple<size_t, size_t, size_t> threeD = {1,2,3}; // 错误
// 等号右侧先进性直接初始化,初始化为 tuple<int, int, int>,再拷贝初始化给左侧,类型不匹配
tuple<size_t, size_t, size_t> threeD{1,2,3}; // 正确
访问tuple成员
auto item = make_tuple("1234567", 3, 20.00);
auto book = get<0>(item); // "1234567"
auto cnt = get<1>(item); // 3
auto price = get<2>(item)/cnt; // 20.00
get<2>(item) *= 0.8; // item是左值,所以get返回左值,item成员被改变。打8折
// 查询tuple成员的数量和类型
typedef decltype(item) trans; // trans 是 item 的类型
size_t sz = tuple_size<trans>::value; // 3
tuple_element<1, trans>::type cnt = get<1>(item); // cnt 是 int
tuple<string, string> duo("1", "2");
tuple<size_t, size_t> twoD(1, 2);
bool b = (duo == twoD); // 错误,不能比较 size_t 和 string
tuple<size_t, size_t, size_t> threeD(1,2,3);
b = (twoD < threeD); // 错误,成员数量不同
tuple<size_t, size_t> origin(0, 0);
b = (origin < twoD); // 正确,b为true
由于 tuple 定义了<和==运算符,可以将 tuple 序列传递给算法,并且可以在无序容器中将 tuple 作为关键字类型
使用tuple返回多个值
// matches保存书店的索引(第几个店)和两个指向书店vector中元素的迭代器
typedef tuple<vector<Sales_data>::size_type,
vector<Sales_data>::const_iterator,
vector<Sales_data>::const_iterator> matches;
// files 保存每家书店的销售记录
// findBook 返回一个vector,保存销售了指定书籍的店及对应书籍的迭代器
matches<matches> findBook(const vector<vector<Sales_data>>&
files, const string& book) {
vector<matches> ret;
// 遍历每个书店,查找指定书籍
for (auto it = files.cbegin(); it != files.cend(); ++it) {
// 在指定范围中查找book,使用compareIsbn进行比较
auto found = equal_range(it->cbegin(), it->cend(),
book, compareIsbn);
if (found.first != found.second) {
ret.push_back(make_tuple(it-files.cbegin(),
found.first, found.second));
}
}
return ret;
}
// 使用tuple对象
void reportResults(istream& in, ostream& os,
const vector<vector<Sales_data>>& files) {
string s;
while (in >> s) {
auto trans = findBook(files, s);
if (trans.empty()) {
cout << s << " not found in any stores" << endl;
continue;
}
for (const auto& store: trans) {
// 每个店对该书籍合计,并打印(使用了Sales_data的加法运算符)
os << "store: " << get<0>(store) << " sales: " <<
accumulate(get<1>(store), get<2>(store), Sales_data(s))
<< endl;
}
}
}
bitset类型
头文件:bitset
bitset 类使得我们对运算对象的位运算更加方便,并且能够处理超过最长整型类型大小的位集合。
bitset 是个类模板,类似array类,具有固定大小。定义bitset时需要声明它包含多少个二进制位:
bitset<32> bitvec(1U); // 32位,0x00000001(低位为1,其他为0)
bitset<13> bitvec1(0xbeef); // 超出13位的丢弃
bitset<20> bitvec2(0xbeef); // 不足20位的高位填0
bitset<128> bitvec3(~0ULL); // 0~63为1, 64~127为0
当用一个整型值来初始化bitset时,此值被替换为 unsigned long long 类型并被当作位模式来处理。bitset中的二进制位将是此模式的一个副本。
bitset的构造函数:
| 构造器 | 说明 |
|---|---|
bitset<n> b; |
b有n位,每位都是0.此构造函数是一个 constexpr |
bitset<n> b(u); |
b是 unsigned long long 值u的低n位的拷贝。 超出u长度的高位被置为0。若u的位数大于n,则只使用低n位。 此构造函数是一个 constexpr |
bitset<n> b(s, pos, m, zero, one); |
b是string s从位置 pos 开始的 m 个字符的拷贝。 s只能包含字符 zero 或 one,如果包含其他字符,则抛出 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初始化bitset
bitset<32> bitvec4("1100"); // 0x0000000c (低四位:1100)
string str("1111111000000011001101");
bitset<32> bitvec5(str, 5, 4); // 从str[5]开始的四个二进制位,1100
bitset<32> bitvec6(str, str.size()-4); // 使用最后4个字符,1101
bitset操作
检测或设置一个或多个二进制位的方法:
| 操作 | 说明 |
|---|---|
| b.any() | b中是否存在置位(值为1)的二进制位 |
| b.all() | b中是否所有位置都置位 |
| b.none() | b中不存在置位的二进制位吗 |
| b.count() | b中置位的位数,size_t类型 |
| b.size() | 一个 constexpr 函数,返回b的位数,size_t类型 |
| b.test(pos) | 若pos位置的位是置位的,返回true,否则返回false |
| b.set(pos, v) b.set() |
将位置pos处的位设为bool值v。v默认为true。 如果未传递参数,则置位所有位 |
| b.reset(pos) b.reset() |
将pos位置的位复位,或复位所有位 |
| b.flip(pos) b.flip() |
改变pos处的位的状态或改变每一位的状态 |
| b[pos] | 访问pos处的位。若b是const的,返回bool值true(置位)或false(复位) |
| b.to_ulong() b.to_ullong() |
按b的位模式返回 unsigned long 或 unsigned long long。 若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()个位时,读取过程停止 |
// 反转操作,以下等价
bitvec.flip(0); // 反转下标为0的位
bitvec[0].flip();
~bitvec[0];
正则表达式
头文件:regex
正则表达式库组件:
| 组件 | 说明 |
|---|---|
| regex | 表示有一个正则表达式的类 |
| regex_match | 将一个字符序列与一个正则表达式匹配 |
| regex_search | 寻找第一个与正则表达式匹配的子序列 |
| regex_replace | 使用给定格式替换一个正则表达式 |
| sregex_iterator | 迭代器适配器,调用 regex_search 来遍历一个 string 中所有匹配的子串 |
| smatch | 容器类,保存在 string 中搜索的结果 |
| ssub_match | string 中匹配的子表达式的结果 |
| 参数: (seq, m, r, mft) (seq, r, mfg) |
在字符序列seq中查找regex对象r中的正则表达式。 seq可以是一个string、表示范围的一对迭代器或一个指向空字符结尾的字符数组的指针。 m是一个match对象,用来保存匹配结果。m和seq必须具有兼容的类型。 mft是一个可选的 regex_constants::match_flag_type 值。 |
regex(和wregex)选项:
| 操作 | 说明 |
|---|---|
| regex r(re) regex r(re, f) |
re是一个正则表达式,可以是一个 string,一个字符范围的迭代器对, 一个指向空字符结尾的字符数组的指针,一个字符指针和一个计数器或一个花括号包围的字符列表。 f是表示对象如何处理的标志。默认是 ECMAScript |
| r1 = re | 将r1中的正则表达式替换为re.re表示一个正则表达式,可以是另一个 regex对象、 一个string、一个指向空字符结尾的字符数组的指针,或是一个花括号包围的字符列表 |
| r1.assign(re, f) | 与赋值(=)相同,可选标志f与regex构造器中相同 |
| r.mark_count() | r中子表达式的数目 |
| r.flags() | 返回r的标志集 |
| 标志 | 定义在regex 和 regex_constants::syntax_option_type 中 |
| icase | 忽略大小写 |
| nosubs | 不保存匹配的子表达式 |
| optimize | 执行速度由于构造速度 |
| ECMAScript | 使用 ECMA-262 指定的语法 |
| basic | 使用 POSIX 基本的正则表达式语法 |
| extended | 使用 POSIX 扩展的正则表达式语法 |
| awk | 使用 POSIX 版本的 awk 语言的语法 |
| grep | 使用 POSIX 版本的 grep 的语法 |
| egrep | 使用 POSIX 版本的 egrep 的语法 |
使用正则表达式库
// 查找不在字符c之后的字符串ei
string pattern("[^c]ei");
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern); // 构造查找模式
// regex r(pattern, regex::icase); // 忽略大小写
smatch results;
string test_str = "receipt freind theif receive";
if (regex_search(test_str, results, r)) {
cout << results.str() << endl;
}
regex使用的正则表达式语言是 ECMAScript,其中,模式 [[::alpha:]] 匹配任意字母,在查找字符串两端加上它表示查找的是单词(不包含空格)。
regex_search 在输入序列中只要找到一个匹配子串就会停止查找,所以程序最终只输出一个单词:freind.
查找所有匹配子串:
for (sregex_iterator it(test_str.begin(), test_str.end(), r), end_it;
it != end_it; ++it) {
cout << it->str() << endl;
}
构造一个 regex 对象以及赋予一个新的正则表达式是非常耗时的,应当尽量减少正则表达式的使用
异常
正则表达式语法的正确性是在运行时解析的。如果正则表达式存在错误,则抛出类型为 regex_error 的异常。
regex_error有一个 what 操作来描述发生了什么错误;还有一个 code 成员,用来返回某个错误类型对应的数值编码。(code 由具体实现定义)
try {
// 匹配数字或字母开头的C++文件名(.cpp,.cxx,.cc文件),忽略大小写
// 漏掉了右方括号
regex r("[[:alnum:]+\\.(cpp|cxx|cc)$", regex::icase);
} catch (regex_error e) {
cout << e.what() << "\ncode: " << e.code() << endl;
}
| 错误类型 | 说明(定义在 regex 和 regex_constants::error_type 中) |
|---|---|
| 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 | 栈空间不足 |
code成员代表上述错误的编号,从0开始。
用宽字符串生成正则
正则表达式的输入可以是普通char数据或wchar_t数据,字符可以保存在 string 中或 char 数组中。
wregex,与 regex 操作类似,区别是它只接受 wchar_t 字符组成的串(wstring 或 wchar_t 数组)。
smatch 表示 string 类型的输入序列;
cmatch 表示字符数组序列;
wsmatch 表示宽字符串 wstring;
wcmatch 表示宽字符数组。
组件的使用必须与保存结果的类型匹配:
regex r("[[:alnum:]]+\\.(cpp|cxx|cc)", regex::icase);
smatch results; // 匹配结果存放在 string 中,而不是 char*
if (regex_search("myfile.cc", results, r)) { // 错误:输入为 char*
cout << results.str() << endl;
}
将 smatch 类型改为 cmatch 即可。
输入与使用的类型相匹配:
| 输入类型 | 使用正则表达式类 |
|---|---|
| string | regex, smatch, ssub_match, sregex_iterator |
| const char* | regex, cmatch, csub_match, cregex_iterator |
| wstring | wregex, wsmatch, wssub_match, wsregex_iterator |
| const wchar_t* | wregex, wcmatch, wcsub_match, wcregex_iterator |
smatch 的操作及子表达式
| smatch 操作 | 说明 |
|---|---|
| m.ready() | 如果已经通过调用 regex_search 或 regex_match 设置了m,则返回 true; 否则返回 false. 如果 ready 返回 false,则对 m 进行操作是未定义的。 |
| m.size() | 如果匹配失败,则返回0;否则返回最近一次匹配的正则表达式中子表达式的数目 |
| m.empty() | m.size() 为0,则返回 true |
| m.prefix() | 一个 ssub_match 对象,表示当前匹配之前的序列 |
| m.suffix() | 一个 ssub_match 对象,表示当前匹配之后的序列 |
| m.format(...) | TODO |
| n为索引 | n默认为0且必须小于m.size();第一个子匹配(0)表示整个匹配 |
| m.length(n) | 第n个匹配的子表达式的大小 |
| m.position(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
// 两个子表达式,用小括号表示
regex r("([[:alnum:]]+)\\.(cpp|cxx|cc)", regex::icase);
smatch results; // 匹配结果存放在 string 中,而不是 char*
if (regex_search("myfile.cc", results, r)) { // 错误:输入为 char*
cout << results.str(1) << endl; // 只打印第一个子匹配,即文件名
}
子匹配的操作
| 子匹配操作 | 说明 |
|---|---|
| matched | public bool 数据成员,指出了 ssub_match 是否存在匹配 |
| first second |
public 数据成员,指向匹配序列首尾元素位置的迭代器。如果为匹配,则他们相等 |
| length() | 匹配的大小。如果 matched 为 false,则返回0 |
| str() | 返回一个包含匹配部分的 string。如果 matched 为 false,则返回空 string |
| s = ssub | 将ssub_match 对象 ssub 转化为 string 对象 s.等价与 s=ssub.str(). 转换运算符不是 explict 的 |
适用于 ssub_match, csub_match, wssub_match, wcsub_match
// 美国电话号码例:(908) 555-1800; 908.555.1800
// 区号可用括号包裹,如包裹则其后间隔符只能用空格或没有间隔符;否则两个间隔符必须匹配
string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
regex r(pattern);
smatch results;
string s;
while (getline(cin, s)) {
for (sregex_iterator it(s.begin(), s.end(), r), end_it; it != end_it; ++it) {
if (valid(*it)) {
cout << "valid: " << it->str() << endl;
} else {
cout << "not valid: " << it->str() << endl;
}
}
}
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
| 正则表达式替换操作 | 说明 |
|---|---|
| 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";
string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
regex r(phone);
string number = "(908) 555-1800 862-555-0123";
cout << regex_replace(number, r, fmt) << endl; // 输出 908.555.1800 862.555.0123
控制匹配和格式的标志
match_flag_type,定义在 std::regex_constants 命名空间中,使用方法:
using std::regex_constants::format_no_copy;
// 或
using namespace std::regex_constants;
| 匹配标志 | 说明 |
|---|---|
| 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++程序不应该使用库函数 rand,而应使用 default_random_engine 类和恰当的分布类对象
随机数库的组成:
- 引擎:生成随机 unsigned 整数序列
- 分布:使用引擎返回服从特定概率分布的随机数
随机数引擎操作:
| 操作 | 说明 |
|---|---|
| Engin e; | 默认构造函数;使用该引擎类型默认的种子 |
| Engin e(s); | 使用 s 作为种子 |
| e.seed(s) | 使用 s 作为种子重置引擎的状态 |
| e.min() e.max() |
此引擎可生成的最小值和最大值 |
| Engine::result_type | 此引擎生成的 unsigned 整型类型 |
| e.discard(u) | 将引擎推进u步;u的类型为 unsigned long long |
// 生成随机无符号数
default_random_engine e;
for (size_t i = 0; i < 10; ++i) {
cout << e() << " ";
}
分布类型和引擎
// 指定范围的均匀分布
uniform_int_distribution<unsigned> u(0, 9);
default_random_engine e; // 生成无符号随机数
for (size_t i = 0; i < 10; ++i) {
cout << u(e) << " ";
}
随机数发生器:分布对象和引擎对象的组合
C库函数 rand 生成的是0到 RAND_MAX 之间的整数,而C++ 的范围可以调用 min 和 max 函数获得。
使用种子生成序列
一个给定的随机数发生器会一直生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为 static 的。否则每次调用都会生成相同的序列。
一旦程序调试完毕,我们希望每次运行程序都会生成不同随机结果。可以通过提供一个种子来达到这一目的:
default_random_engine e1;
default_random_engine e2(1234567);
default_random_engine e3;
e3.seed(1234); // 设置一个新种子值
default_random_engine e4(1234); // e3 和 e4 会生成相同的序列
// 通常,使用系统函数 time 来设置种子(头文件 ctime)
default_random_engine e5(time(0)); // 稍微随机些的种子
time 函数返回的是距离某个特定时间的秒数。
如果程序作为一个自动过程的一部分反复运行,将 time 的返回值作为种子的方式就无效了;它可能多次使用的都是相同的种子。
其他随机分布
生成随机实数
生成0到1之间的随机数的常用但不正确的一个方法是用 rand() 的结果除以 RAND_MAX,其不正确的原因是随机整数的精度通常低于随机浮点数,这样,有一些浮点值就永远不会生成了。
使用 uniform_real_distribution 生成均匀分布的实数:
default_random_engine e;
uniform_real_distribution<double> u(0, 1);
// uniform_real_distribution<> u(0, 1); // 使用默认模板参数类型 double
for (size_t i = 0; i < 10; ++i) {
cout << u(e) << " ";
}
| 操作 | 说明 |
|---|---|
| Dist d; | 默认构造函数;分布类型的构造函数是 explicit 的 |
| d(e) | 用相同的e连续调用d的话,会根据d的分布类型生成一个随机数序列 |
| d.min() d.max() |
返回 d(e) 能生成的最小值和最大值 |
| d.reset() | 重建d的状态,使随后对d的使用不依赖于d已经生成的值 |
具体分布方法参考附录
正态分布随机数
default_random_engine e;
normal_distribution<> n(4, 1.5); // 均值4,标准差1.5;使用默认模板类型 double
vector<unsigned> vals(9);
for (size_t i = 0; i < 200; ++i) {
unsigned v = lround(n(e)); // 四舍五入取整
if (v < vals.size()) {
++vals[v]; // 统计0到8各出现了多少次
}
}
for (size_t j = 0; j != vals.size(); ++j) {
cout << j << ": " << string(vals[j], '*') << endl; // 按照数量打印星号
}
bernoulli_distribution 类
bernoulli 分布类是一个普通类,不是模板,所以不接受模板参数。该分布总是返回 bool 值,返回 true 的概率是一个常数,默认0.5.
default_random_engine e;
bernoulli_distribution b; // 默认 50/50 的概率
bernoulli_distribution b2(0.55); // 55/45 的概率
bool b = b(e);
IO库
本节讨论三个特殊的IO库特性:
- 格式控制
- 未格式化IO
- 随机访问
格式化输入输出
标准库定义了一组操纵符来修改流的格式状态。
操纵符用于两大类输出控制:控制数值的输出形式和控制补白的数量和位置。
大多数改变格式状态的操纵符都是成对的,一个用来将格式状态设置为新值,另一个用来将其复原。
当操纵符改变流的格式状态后,通常对所有后续IO都生效。在不需要特殊格式时尽快将流恢复到默认状态。
控制布尔值格式
cout << "default bool values: " << true << " " << false
<< "\nalpha bool values: " << boolalpha << true << " " << false << endl;
输出:
default bool values: 1 0
alpha bool values: true false
使用 noboolalpha 恢复布尔默认格式:
bool b = get_status();
cout << boolalpha << b << noboolalpha;
指定整型值的进制
cout << showbase; // 打印整型时显示进制
cout << "default: " << 20 << " " << 1024 << endl;
cout << "oct: " << oct << 20 << " " << 1024 << endl; // 8进制
cout << "hex: " << hex << 20 << " " << 1024 << endl; // 16进制
cout << "decimal: " << dec << 20 << " " << 1024 << endl; // 10进制
输出:
default: 20 1024
oct: 024 02000
hex: 0x14 0x400
decimal: 20 1024
hex, oct, dec 只会影响整型运算对象,浮点值不受影响
设置大写,然后恢复状态:
cout << uppercase << showbase << hex << "hex: " << 20 << " " << 1024
<< nouppercase << noshowbase << dec << endl;
输出:
hex: 0X14 0X400
控制浮点数格式
可以控制浮点数的三种格式:
- 打印精度(打印多少数字)
- 16进制打印、定点10进制还是科学计数法形式
- 对没有小数部分的浮点值是否打印小数点
浮点值默认按6位数字精度打印;如果没有小数部分则不打印小数点;根据浮点数的值选择打印成10进制或科学计数法形式。
标准库会选择一种可读性更好的格式:非常大和非常小的值打印为科学计数法形式,其他打印为定点10进制形式。
值的精度高于打印精度时会四舍五入而不是截断。
指定打印精度
// 获取默认精度值,默认6位数字;1.41421
cout << "precision: " << cout.precision() << ", value: " << sqrt(2.0) << endl;
// 设置打印精度为12位数字
cout.precision(12);
cout << "precision: " << cout.precision() << ", value: " << sqrt(2.0) << endl;
// 另一种设置精度的方法,打印精度设置为3位数字;1.41
cout << setprecision(3);
cout << "precision: " << cout.precision() << ", value: " << sqrt(2.0) << endl;
| 操纵符 | 说明 |
|---|---|
| boolalpha | 将true和false设置为字符串 |
| * noboolalpha | 将true和false设置为1,0 |
| showbase | 对整型值输出表示进制的前缀 |
| * noshowbase | 不生成前缀 |
| showpoint | 对浮点值总是显示小数点 |
| * noshowpoint | 只有浮点值包含小数部分时才显示小数点 |
| showpos | 对非负数显式+ |
| * noshowpos | 对非负数不显式+ |
| uppercase | 16进制前缀打印大写0X,科学计数法打印E |
| * nouppercase | 16进制前缀打印小写0x,科学计数法打印e |
| * dec | 恢复10进制 |
| hex | 设置为16进制 |
| oct | 设置为8进制 |
| left | 在值右侧添加填充字符 |
| right | 在值左侧添加填充字符 |
| internal | 在符号和值之间填充字符 |
| fixed | 浮点值显示为定点10进制 |
| scientific | 浮点值显示为科学计数法 |
| hexfloat | 浮点值显式示16进制(C++11) |
| defaultfloat | 重置浮点值显示为10进制(C++11) |
| unitbuf | 每次输出操作后都刷新缓冲区 |
| * nounitbuf | 恢复正常缓冲区刷新方式 |
| * skipws | 输入运算符跳过空白符 |
| noskipws | 输入运算符不跳过空白符 |
| flush | 刷新 ostream 缓冲区 |
| ends | 插入空字符,然后刷新 ostream 缓冲区 |
| endl | 插入换行,然后刷新 ostream 缓冲区 |
- 表示默认流状态
输出补白
- setw(int) 指定下个数字或字符串值的最小空间
- left 左对齐输出
- right 右对齐输出,右对齐是默认格式
- internal 控制负数的符号的位置,它左对齐符号,右对齐值,用空格填满所有中间空间
- setfill(char) 允许指定一个字符代替默认的空格来补白输出
- setbase(b) 整数输出为b进制
未格式化的输入输出
标准库提供了一组底层操作,支持未格式化IO,这些操作允许我们将一个流当作一个无解释的字节序列来处理。
单字节操作
// 使用 get 和 put 读取和写入一个字符,这样不会跳过空白字符,类似 noskipws
char ch;
while (cin.get(ch)) {
cout.put(ch);
}
| 操作 | 说明 |
|---|---|
| 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 返回,但不从流中删除它 |
unget 使输入流向后移动,使得最后读取的值又回到流中,即使我们不知道最后从流中读取什么值,仍然可以调用 unget。
putback 时更特殊版本的 unget,它也是回退一个字节,但接受一个参数,该参数必须与最后读取的值相同。
一般情况下,在读取下一个值之前,标准库保证我们可以退回最多一个值。即不保证能连续调用 putback 或 unget.
从输入操作返回的 int 值
返回 int 的函数先将字符转换为 unsigned char,然后再提升到 int。
peek 和无参数的 get 都返回一个 int 而不是 char,这样可以返回文件尾标记。char 的每一个值表示一个真实字符,所以没有额外的值来表示文件末尾。
// EOF 定义在头文件 cstdio 中
#include <cstdio>
// ch 使用 int 而不是 char。get 的返回值是 int,可以表示不可见字符
int ch;
while ((ch = cin.get()) != EOF) {
cout.put(ch);
}
多字节操作
| 多字节操作 | 说明 |
|---|---|
| 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 默认为文件尾 |
其中 get 和 getline 函数会一直读取数据,直到遇到以下条件之一:
- 已读取 size-1 个字符
- 遇到文件尾
- 遇到分隔符 delim
对于回退操作,gcount 函数会将其计数在内。对于单字节操作,如果在 gcount 之前调用了 peek, unget 或 putback,则 gcount 的返回值为 0.
一般情况,尽量使用高层抽象,而不是底层操作。
一个常见的错误是将 get 或 peek 的返回值赋予一个 char 而不是 int,并且编译器不能发现这个错误。在一台 char 被实现为 unsigned char 的机器上运行以下循环时永远不会停止:
char ch; while ((ch = cin.get()) != EOF) { cout.put(ch); }get 返回 EOF 时,此值被转换为一个 unsigned char,其值与 EOF 的 int 值不再相等,所以循环不会停止。
在一台 char 实现为 signed char 的机器上,以上行为不能确定,取决于机器中编译器的实现。因为可能会出现某个字符经过转换后与 EOF 的值相等的情况,循环会提前结束。
流随机访问
随机IO本质上是依赖于系统的,想要理解和使用这些特性,需要查询系统文档。
虽然标准库为所有流类型都定义了 seek 和 tell 函数,但他们是否有意义依赖于绑定到哪个设备。
大多数系统中,绑定到 cin, cout, cerr 和 clog 的流不支持随机访问,但他们可以调用 seek 和 tell 函数,但在运行时会出错,将流置于一个无效状态。
istream 和 ostream 通常不支持随机访问,随机访问一般只适用于 fstream 和 sstream 类型。
seek 和 tell 函数:
| 函数 | 说明 |
|---|---|
| tellg() tellp |
返回一个输入流中(tellg)或输出流中(tellp)标记的当前位置,pos_type 类型 |
| seekg(pos) seekp(pos) |
在一个输入流或输出流中将标记重定位到给定的绝对地址。pos 通常是前一个 tellg 或 tellp 返回的值 |
| seekp(off, from) seekg(off, from) |
在一个输入流或输出流中将标记定位到 from 之前或之后 off 个字符,from 可以使下列值之一: beg, 便宜量相对于流开始位置 cur, 偏移量相对于流当前位置 end, 偏移量相对于流结尾位置 |
g 表示 get,从输入流获取;p 表示 put,往输出流放置
pos 的类型是 pos_type;off 的类型是 off_type,可以为负
读写文件例:
// 读取文件,将每行的累计字符数写到文件末尾
int main() {
// ate 表示定位到文件末尾
fstream inout("copyOut", fstream::ate | fstream::in | fstream::out);
if (!inout) {
cerr << "Unable to open file!" << endl;
return EXIT_FAILURE; // cstdlib 头文件
}
auto end_mark = inout.tellg(); // 记住文件末尾的位置
inout.seekg(0, fstream::beg); // 重定位到文件开头
size_t cnt = 0; // 计数器,表示已读取的字符数
string line;
while (inout && inout.tellg() != end_mark && getline(inout, line)) {
cnt += line.size() + 1; // 加1表示换行符
auto mark = inout.tellg(); // 记住当前位置,之后再回到这里
inout.seekp(0, fstream::end); // 重定位到文件末尾
inout << cnt;
if (mark != end_mark) { // 如果不是最后一行,加入一个空格作为分隔符
inout << " ";
}
inout.seekg(mark);
}
inout.seekp(0, fstream::end);
inout << "\n";
return 0;
}
小结
本章介绍了一些特殊IO操作和四个标准库类型:tuple, bitset, 正则表达式和随机数
tuple 是个模板,可以将多个不同类型的成员捆绑成单一对象,每个 tuple 包含成员的数量没有限制。
bitset 允许我们定义指定大小的二进制位集合,其长度不必和整型类型相等。除了支持普通位运算符,还定义了一些命名的操作,允许我们操纵 bitset 中特定位的状态。
正则表达式提供了一组类和函数。regex 类用来管理正则表达式。 regex_search 和 regex_match 函数用来与字符序列进行匹配。cregex_iterator 是迭代器适配器,用来遍历匹配的子序列。regex_replace 函数允许正则替换。
随机数库由一组随机数引擎类和分布类组成。随机数引擎返回一个均匀分布的整型值序列。标准库定义了多个引擎,其中 default_random_engine 是适合大多数普通情况的引擎。标准库还定义了20个分布类型,他们都使用一个引擎来生成指定类型的随机数。

浙公网安备 33010602011771号