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个分布类型,他们都使用一个引擎来生成指定类型的随机数。

posted @ 2023-06-30 10:40  keep-minding  阅读(17)  评论(0)    收藏  举报