第十七章? 标准库特殊设施
17.1 tuple类型
tuple类似于大杂烩, 什么都可以往里装.
可以用于打包动作
tuple<T1, T2, ..., Tn> t;
tuple<T1, T2, ..., Tn> t(v1, v2..., vn);
make_tuple(v1, v2, ...,vn)
t1 == t2? 数量相同, 对应的成员也相等
t1 != t2
t1 relop t2? 关系运算使用字典序, 两个tuple必须具有相同数量的成员, 使用<运算符比较成员
get<i>(t) 返回第i个数据成员的引用
tuple_size<tupleType>::value? 表示tuple类型中成员的数量, 即 有多少列, 从零开始.
tuple_element<i, tupleType>::type? 表示tuple类型中指定成员的类型 用于声明变量.
tuple<string, vector<double>, int, list<int>> item("constants", { 3.14,2.71 }, 42, { 0, 1, 2,3 });
auto str = std::get<0>(item); // string
typedef decltype(item) itemType; // itemType = tuple<string, vector<double>, int, list<int>>
size_t sz = tuple_size<itemType>::value; // 4 列
tuple_element<1, itemType>::type cnt = get<1>(item); // vector<double> cnt = {3.14, 2.71}
17.1.1 定义和初始化tuple
定义时需要指出其每个成员的类型
17.1.2 使用tuple返回多个值
17.2 bitset类型
bitset<n> b? ?b有n位, 每一位都是0
bitset<n>b(u)? b是unsigned long long 值u的低n位的拷贝. 如果n大于u的长度, 则高位被置为0
bitset<n> b(s, pos, m, zero, one); b是string s 从pos开始m个字符的拷贝. s只能包含字符zero或one. 否则会抛出异常. pos默认0, m默认string::npos, zero默认'0', one 默认'1'
bitset<n> b(cp, pos, m, zero, one); cp指向数组.
- 使用unsigned值初始化bitset
- 该unsigned 会被转换为unsigned long long 类型
- bitset中的二进制位是该unsigned值的副本
- bitset的长度大于unsigned long long的长度时, 剩余的高位均被置为0
- bitset的长度小于unsigned long long的长度时, 剩余的高位被舍弃
string 初始化 bitset:
字符串中下标最小的字符对应高位:
bitset<32>bitvec("1100");? // 即: 1100
string中的下标 是从左开始, bitset中的下标 是从右开始的,其遵照二进制的顺序
- b.any ()? ? ? ? b中是否存在1的二进制位
- b.all()? ? ? ? ? ?b中全部都是1的吗?
- b.none()? ? ? b中没有1的吗?
- b.count()? ? ?b中1个个数
- b.size()? ? ? ?b的长度
- b.test(pos) b中pos的位置是否为1
- b.set(pos, v) 将pos位置置为bool类型v的值, v默认为true
- b.set()? 所有位置置为为1
- b.reset() 所有位置置为为0
- b.reset(pos) pos位置置为0
- b.flip(pos) 改变pos位置的状态
- b.flip() 所有位置改变状态
- b[pos]? 访问pos位
- b.to_ulong()
- b.to_ullong()
- b.to_string(zero,one)
- os<<b
- is>>b
17.3 正则表达式
头文件<regex>
组件:
- regex? ?表示有一个正则表达式的类
- regex_match? 将一个字符序列与一个正则表达式匹配
- regex_search? 寻找第一个与正则表达式匹配的子序列
- regex_replace? 使用给定格式替换一个正则表达式
- sregex_iterator? 迭代器适配器, 调用regex_search来遍历一个string中所有匹配的子串
- smatch? 容器类, 保存string中搜索的结果
- ssub_match? string中匹配的子表达式的结果
regex_search和regex_match的参数:
- (seq, m, r, mft)? 在字符序列seq中查找regex对象r中的正则表达式.
- ? ? seq 可以是string, 一对迭代器, 指向空字符结尾的字符数组的指针
- ? ? m是一个match对象, 用来保存匹配结果的相关细节, m和seq必须具有兼容的类型
- ? ? mft 是一个可选的regex_constants::match_flag_type值
- (seq, r, mft)
17.3.1 使用正则表达式库
regex的一些配置:
- regex r(re)? ? re表示一个正则表达式, 可以是string/表示字符的迭代器对/指向空字符结尾的字符数组/字符指针和一个计数器/字符列表.? f 指出对象如何处理的标志, f默认为ECMAScript.
- regex r(re, f)
- r1 = re? 将r1的正则表达式替换为re, re可以是 正则表达式/string/空字符结尾的数组指针/字符列表
- r1.assign(re, f)
- r.mark_count() r中子表达式的数目
- r.flags? r的标志集
regex可以使用的标志:
- 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:]]*";
// '/\w*[^c]ei\w*/' 通常所使用的正则表达式
// 等同于 string pattern("\\w*[^c]ei\\w*");
regex r(pattern);
smatch results;
string test_str = "receipt freind theif receive";
if (regex_search(test_str, results, r)) cout << results.str() << endl;
regeex_error异常,? 错误的正则表达式抛出的异常. what 中有描述, code有错误编码:
搜索结果xmatch与搜索类型的匹配:
- 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
17.3.2 匹配与Regex迭代器类型
- sregex_iterator it(b, e, r) 一个sregex_iterator 遍历迭代器b和e所表示的string, 其调用sregex_search(b, e, r) 将it定位到输入中第一个匹配的位置.
- sregex_iterator end;? 尾后迭代器. 只需要定义, 不需要赋值, 如实例.
- *it
- it->
- ++it
- it++
- it1 == it2
- it1 != it2
string pattern("\\w*[^c]ei\\w*");
regex r(pattern);
smatch results;
string test_str = "receipt freind theif receive";
sregex_iterator it(test_str.begin(), test_str.end(), r);
sregex_iterator it_end; // 只是定义了一下, 并未做处理
for (; it != it_end; ++it) {
auto pos = it->prefix().length();
pos = pos > 40 ? pos - 40 : 0;
cout << it->prefix().str().substr(pos)
<< "\n\t\t>>> " << it->str() << " <<<\n"
<< it->suffix().str().substr(0,40)
<< endl;
}
prefix,? suffix 获取匹配处前/后缀的字符串. 其余的操作还有
- m.ready()? 如果已经通过调用regex_serach或regex_match, 则返回true, 否则返回false, 如果是false, 则m的操作都是未定义的
- m.size()? 匹配失败 返回0, 否则返回最近一次匹配的子表达式的数目
- m.empty()? return (m.size() ==0)? true: false
- m.suffix() 一个ssub_match对象, 表示匹配之后的部分
- m.prefix() 一个ssub_match对象, 表示匹配之前的部分
- m.format(...)??
- m.length(n)? 第n个匹配的子表达式的大小
- m.position(n)? 第n个匹配的子表达式距序列开始的距离
- m.str(n)? 第n个匹配的子表达式匹配的string, 正则表达式中第n个子表达式匹配的内容
- m[n]? 第n个匹配的子表达式的ssub_match对象
- m.begin(), m.end()? 表示m中sub_match元素范围的迭代器.
- m.cbegin(), m.cend()
17.3.3 使用子表达式
第一个子匹配位置为0? 表示整个模式对应的匹配, 随后是每个子表达式对应的匹配.
子匹配的一些操作:
- matched? 指出ssub_match 是否匹配
- first? 指向匹配序列首元素和尾后位置的迭代器, 如果未匹配, first == second
- second
- length()? 匹配的大小
- str()? 返回一个包含输入中匹配部分的string, 如果matched为false, 则返回空string
- s = ssub? ?等价于 s = ssub.str()
17.3.4 使用regex_replace
regex_replace(dest, r, fmt, mft)
dest? 需要替换字符串的原始字符串, 函数执行后 该字符串会被修改
r? 正则表达式对象
fmt? 一个字符串, 其中的$ 代表子表达式匹配的内容, 需要为子表达式的序号
mft 表示一些匹配控制标志, 这些标志均在std::regex_constants::match_flag_type中.
string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
regex rphone(phone);
string dest = "(908) 555-1888";
string fmt = "$2.$5.$7"; // 使用第2, 5, 7个子表达式匹配的内容组成新内容
cout << regex_replace(dest, rphone, fmt) << endl; // 输出 908.555.1888
?
17.4 随机数
<random>
default_random_engine 随机数引擎类:? 类型 生成随机unsigned整数序列
随机数分布类:? 类型, 使用引擎返回服从特定概率分布的随机数
17.4.1 随机数引擎和分布
default_random_engine
随机数引擎操作:
- 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
#include<random>
using namespace std;
int main(int argc, char** argv) {
uniform_int_distribution<unsigned> u(0, 9); // 分布类型生成均匀分布的unsigned值
default_random_engine e;
for (size_t i = 0; i < 10; ++i)
cout << u(e) << " - "; // Engine e(s) 生成 2 - 2 - 4 - 5 - 4 - 1 - 9 - 5 - 8 - 3 -
// 分布类型接收一个随机数引擎作为参数, 并使用它生成随机数, 然后映射到指定的分布
}
注意: 传递给分布对象的是引擎对象, 即 u(e) 而不是u(e()).? 传递的是引擎本身, 而不是它生成的下一个值(e()), 因为某些分布可能需要调用引擎多次才能得到一个值.
随机数发生器: 指 分布对象和引擎对象的组合
比较随机数引擎和rand函数:
default_random_engine? 生成的随机数范围为unsigned整数, e.min(), e.max()可得到该范围
random生成的数在0 到 RAND_MAX之间.
引擎生成一个数值序列:
?即使生成的数看起来是随机的, 但对一个给定的发生器, 每次运行程序它返回相同的数值序列.
这个有点超出想象了
vector<unsigned> bad_randVec() {
// 每次生成的对象 都会随机出相同的 随机数序列
default_random_engine e;
uniform_int_distribution<unsigned>u(0, 9);
vector<unsigned>ret;
for (size_t i = 0; i < 100; ++i) ret.push_back(u(e));
return ret;
}
vector<unsigned> good_randVec() {
// 使用static后 每次调用都使用相同的对象, 对象的状态得以保存, 会继续向后随机
static default_random_engine e;
static uniform_int_distribution<unsigned>u(0, 9);
vector<unsigned>ret;
for (size_t i = 0; i < 100; ++i) ret.push_back(u(e));
return ret;
}
int main(int argc, char** argv) {
vector<unsigned> v1(bad_randVec());
vector<unsigned> v2(bad_randVec());
cout << "equal? " << (v1 == v2) << endl; // v1 和v2 相等
vector<unsigned> v3(good_randVec());
vector<unsigned> v4(good_randVec());
cout << "equal? " << (v3 == v4) << endl; // v3 和 v4 不相等
}
设置随机数发生器种子:
种子是一个数值, 引擎可以利用它从序列中一个新位置重新开始生成随机数.
有两种方式: 创建时提供种子,? 使用引擎的seed成员
种子相同, 引擎生成的序列相同.
对于秒间隔以上的可以使用time(0) 作为种子, 其余情况需要重新想办法
default_random_engine e(time(0));
17.4.2 其他随机数分布
生成随机实数: 随机浮点数
注意使用rand()/RAND_MAX的方法是不可取的, 因为精度低.有些浮点值永远不会被生成.
default_random_engine e;
uniform_real_distribution<double> u(0, 1);
for (size_t i = 0; i < 10; ++i) cout << u(e) << endl;
生成非均匀分布的随机数:
一个正态分布例子:
// 生成一组200个正态分布的点, 值以4为中心, 标准差1.5, 99%在[0, 8]
default_random_engine e;
normal_distribution<>n(4, 1.5); // 正态分布
vector<unsigned> vals(9);
for (size_t i = 0; i != 200; ++i) {
unsigned v = lround(n(e));
if (v < vals.size()) ++vals[v];
}
size_t total = 0;
for (size_t j = 0; j != vals.size(); ++j)
{
cout << j << ": " << string(vals[j], '*') << endl;
total += vals[j];
}
cout << "total: " << total << endl;
一个返回true/false的随机分布, 返回true的概率总是50%:
string resp;
default_random_engine e;
bernoulli_distribution b; // 可以定义概率b(.55) 即 55:45 55为true
do {
bool first = b(e);
cout << (first ? "We go first" : "You get to go first") << endl;
cout << "play something...." << endl;
cout << "play again ? Enter 'yes' or 'no'" << endl;
} while (cin >> resp && resp[0] == 'y');
17.5?IO库
17.5.1 格式化输入和输出
操纵符用于两大类输出控制: 控制数值的输出形式以及控制补白的数量和位置. 大多数操纵符成对出现, 一个用于设置, 一个用于复原. 一旦设置 后续流则全部被改变.
boolalpha 更改true/ false的默认输出值, 不再是1, 0, 而是 true false
noboolalpha 取消
hex, oct, dec 改变进制输出
uppercase/nouppercase 可以改变16进制中0x, 为大写 0X
showbase/noshowbase 可以显示进制信息
cout << showbase;
cout << "default: 20: " << 20 << " 1024: " << 1024 << endl;
cout << "in octal: 20: " <<oct << 20 << " 1024: " << 1024 << endl;
cout << "in hex: 20: " << hex << 20 << " 1024: " << 1024 << endl;
cout << "in decimal: 20: " << dec << 20 << " 1024: " << 1024 << endl;
cout << noshowbase;
/*default: 20 : 20 1024 : 1024
in octal : 20 : 024 1024 : 02000
in hex : 20 : 0x14 1024 : 0x400
in decimal : 20 : 20 1024 : 1024*/
控制浮点数输出的三种格式:
- 以多高精度打印浮点值
- 数值打印为十六进制, 定点十进制还是科学记数法形式
- 没有小数部分的浮点值是否打印小数点
cout.precision() 返回当前打印的精度值
cout.precision(n) 设置打印精度为n位数字
- boolalpha? ? 将true/false 输出为字符串 而非 1/0
- noboolalpha
- showbase? ? 输出整型数字的前缀 比如 0x 十六进制, 0 八进制
- noshowbase
- showpoint? ? 对浮点值总是显示小数点 即使是没有小数部分
- noshowpoint
- showpos? ? 非负数前面显示+号
- noshowpos
- uppercase? ? 十六进制中的0x大写为0X, 科学记数中的e变为E
- nouppercase
- dec? ? 整数显示为十进制
- hex? ??整数显示为十六进制
- oct? ??整数显示为八进制
- left? ? 在值的右侧添加填充字符? 左对齐输出
- right? ??在值的左侧添加填充字符? 右对齐输出
- internal? ? 在符号和值之间添加填充字符
- fixed? ? 浮点值显示为定点十进制
- scientific? ? 浮点值显示为科学记数法
- hexfloat? ? 浮点值显示为十六进制
- defaultfloat? ? 重置浮点数格式为十进制
- unitbuf? ? 每次输出操作后都刷新缓冲区
- nounitbuf
- skipws? ? 出入运算符跳过空白符
- noskipws
- flush? ? 刷新ostream缓冲区
- ends? ? 插入空字符, 然后刷新ostream缓冲区
- endl? ? 插入换行, 然后刷新ostream缓冲区
#include <iomanip>
int i = -16;
double d = 3.1415926;
cout << left
<<"i: " << setw(12) << i <<" next col" << '\n'
<<"d: " << setw(12) << d<< " next col" <<
endl;
cout << right
<< "i: " << setw(12) << i << " next col" << '\n'
<< "d: " << setw(12) << d << " next col" <<
endl;
cout << internal
<< "i: " << setw(12) << i << " next col" << '\n'
<< "d: " << setw(12) << d << " next col" <<
endl;
cout << setfill('#')
<< "i: " << setw(12) << i << " next col" << '\n'
<< "d: " << setw(12) << d << " next col" <<
endl;
//i: -16 next col
//d : 3.14159 next col
//i : -16 next col
//d : 3.14159 next col
//i: - 16 next col
//d : 3.14159 next col
//i : -#########16 next col
//d : #####3.14159 next col
17.5.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返回, 但不从流中删除它
int ch; //使用int 而非char保存get的返回值
while ((ch = cin.get()) != EOF) cout.put(ch);
多字节操作:
- is.get(sink, size, delim)? ? 从is中读取最多size个字节, 并保存在字符数组中, 字符数组的起始地址有sink确定, 读取过程直至遇到字符delim或读取了size个字节或者遇到文件尾时停止. delim 仍然保留在流中, 不会被保存到sink中.? sink 为 char数组
- is.getline(sink, size, delim)? 同get版本, 但是会丢弃delim
- is.read(sink, size)? ? 读取size个字节, 存入数组sink中, 返回is
- is.gcount()? ? 返回上一个未格式化读取操作 从is读取的字节数, 应该在peek, unget, puback之前调用, 否则gcount() 返回值为 0
- os.write(source, size)? ? 将字符数组source中的size个字节写入到os, 返回os
- is.ignore(size, delim)? ? 读取并忽略最多size个字节, 包括delim. size 默认为1, delim 默认EOF
17.5.3 流随机访问
seek? 定位流位置
tell 报告当前位置
两个函数依赖于设备的系统. istream/ ostream通常不支持, 仅fstream/sstream支持.
- tellg()? ? 返回输入流中(tellg)或输出流中(tellp) 标记的当前位置
- tellp()
- seekg(pos)? ? 在一个输入流(seekg)或输出流(seekp)中 将标记重定位到给定的绝对位置. pos通常是前一个tellg或tellp返回的值.
- seekp(pos)
- seekp(off, from)? ? 在一个输入流(seekp)或输出流(seekg)中将标记定位到from之前或之后 off个字符, from: beg? 偏移量相对于流开始的位置,? cur 偏移量相对于流当前位置, end 偏移量相对于流结尾的位置
- seekg(off, from)
istream和派生自istream的类型 ifstream, istringstream? 使用g版本
ostream和派生自ostream的 ofstream, ostringstream 使用p版本
iostream, fstream, stringstream ,? 两者都可用
因为只有单一的标记, 所以只要在读写操作间切换, 就必须进行seek操作重新定位标记.
tellg tellp 返回一个机器相关的位置值 ostringstream::pos_type mark = .
// in, out 以读写方式打开文件, ate, 打开后移动到文件结尾
fstream inOut("E:\\c++\\primer\\Project_17\\Debug\\p678.txt", fstream::ate | fstream::in | fstream::out);
if (!inOut) {
cerr << "unable to open file!" << endl;
return EXIT_FAILURE;
}
// 记住文件当前的位置
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;
// 记住当前位置 以便于下一个循环时返回到当前的位置继续向下读取
auto mark = inOut.tellg();
// 跳到文件尾 准备写 行号
inOut.seekp(0, fstream::end);
// 写行号
inOut << cnt;
if (mark != end_mark) inOut << " - ";
// 跳到当初计算cnt的地方
inOut.seekg(mark);
}
inOut.seekp(0, fstream::end);
inOut << "\n";
return 0;
|