第四部分(高级主题)
1).标准库的附加特性,
- 求解大规模问题很有用
- 或者适用于特殊问题
2).我们使用的标准库的名字,实际上都是使用名字为std的命名空间中的名字。
第十七章(标准库特殊设施)
/1.tuple类型
1).tuple 类似于pair 成员。不同tuple 类型的成员类型也不同,但一个tuple 可以由任意数量的成员。每一个确定的tuple 类型的成员数量也是固定的。
- 可以应用在一些数据组合成单一的对象,但是有不必构建一个类。
tuple 类型和它的伴随类,函数都定义在头文件tuple 中。- 相关的操作见p636。
//1.定义和初始化tuple
1).
{
tuple<size_t, size_t, size_t> threeD;
tuple<string, vector<double>, int, list<int>> someval("contents", {1, 3, 4}, 34, {1, 2, 3});
tuple<size_t, size_t, size_t> threeD = {1, 2, 3};
tuple<size_t, size_t, size_t> threeD{1, 2, 3};
auto item = make_tuple("isbn", 2, 23.00);
}
2).访问tuple的成员
tuple 的成员都是未命名的,使用名为get 的标准库 函数模板。
{
auto book = get<0>(item);
auto = cnt = get<1>(item);
get<2>(item) *= .8;
typedef decltype(item) trans;
size_t sz = tuple_size<trans>::value;
tuple_element<1, trans>::type cnt = get<1>(item);
}
3).关系和相等运算符
tuple 的比较运算符,比较左右两个tuple 里的成员,这一点和容器对应;但是只有两个tuple中的成员数量一样时,我们才可以比较它们。并且比较时,我们需要保证对于每一个成员相应的比较运算是有定义的。
{
tuple<string, string> dou("1", "2");
tuple<size_t, size_t> twoD(1, 2);
bool b = (dou == twoD);
tuple<size_t, size_t, size_t> threeD(1, 2, 3);
b = (twoD < twoD);
tuple<size_t, size_t> origin(0, 0);
b = (origin < twoD);
}
//2.使用tuple返回多个值
1).
{
vector<vector<Sales_data>> files;
typedef tuple<vector<Sales_data>::size_type, vector<Sales_data>::const_iterator, vector<Sales_data>::const_iterator
> matches;
vector<matches>
findBook(const vector<vector<Sales_data>> &files,
const string &book) {
vector<matches> ret;
for (auto it = files.cbegin(); it != files.cend(); ++it) {
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.secod);
}
return ret;
}
}
3).对返回的结果进行处理
{
void reportResult(istream &is, ostream &os, const vector<vector<Sales_data>> &files) {
string s;
while (is >> s) {
auto trans = findBook(files, s);
if (trans.empty()) {
cont << s << "not found in ant stores" << endl;
continue;
}
for (const auto &store : trans) {
os << "store: " << getr<0>(store) << "sales: " << accumulate(get<1>(store), get<2>(store), Sales_data(s)) << endl;
}
}
}
}
/2.bitset类型
1).bitset 类可以处理超过最长整型类型大小的位集合。而且位运算也变得更加肉容易。bitset 类定义在头文件bitset 中。
//1.定义和初始化bitset
1).bitset ,是一个类模板,它类似array 类,具有固定的大小。当我们定义一个bitset 时,需要声明它包含多少个二进制位。
{
bitset<32> bitvec(1U);
}
2).初始化方式表见p641表格。
- 使用
unsigned 值初始化bitset
{
bitset<13> bitvec1(0xbeef);
bitset<20> bitvec2(0xbeff);
bitset<128> bitvec3(~0ULL);
}
- 用一个
string 来初始化bitset
{
bitset<32> bitvec4("1100");
string str("10010010010010010101");
bitset<32> bitvec5(str, 5, 4);
bitset<32> bitvec6(str, str.size() - 4);
}
练习,
- 17.9,如果输入的不是二进制数,会抛出,
invalid_argument 的异常。
//2.bitset操作
1).有多种检测或者设置位的方法。也支持位运算,并且含义也是与我们将这些运算符作用于unsigned 的含义是相同的。。
{
bitset<32> bitvec(1U);
bool is_set = bitvec.any();
boll is_not_set = bitvec.none();
bool all_set = bitvec.all();
size_t onBits = bitvec.count();
size_t sz = bitvec.size();
bitvec.flip();
bitvec.reset();
bitvec.set();
bitvec.flip(0);
bitvec.set(bitvec.size() - 1);
b.set(0, 0);
b.reset(i);
b.test(0);
b[0] = 0;
b[31] = b[0];
b[0].flip();
~b[0];
bool a = b[0];
}
2).提取bitset 的值
{
unsigned long ulong = bitvec3.to_ulong();
}
3).bitset 的IO运算符
{
bitset<16> bits;
cin >> bits;
cout << "bits: " << bits << endl;
}
4).使用bitset
{
bool status;
unsigned long quizA = 0;
quizA |= 1UL << 27;
status = quizA & (1UL << 27);
quizA &= ~(1UL << 27);
bitset<30> quizB;
quizB.set(27);
status = quizB[27];
quizB.reset(27);
}
练习,
- 17.13,好题。锻炼模板的使用,类外定义成员函数的注意事项。
/3.正则表达式
1).重点介绍如何使用。RE库(正则表达式库)定义在头文件regex 中。包含的组件见p645。 2).regex 可以做一些什么。
regex 类表示一个正则表达式。除了赋值和初始化之外,还支持一些其他的操作。见p647。- 函数
regex_match,regex_search 确定一个给定的字符序列与一个给定的regex 是否匹配。如果整个输入序列和表达式匹配,则regex_match 返回true ,如果输入序列中的一个字串与表达式匹配,则regex_search 函数返回true 。
3).p646列出了,regex 的函数的参数。这些函数都是返回bool ,。而且被重载了。其中一个版本接受类型为smatch 的附加参数。如果匹配成功,这些函数将成功匹配的相关信息保存在给定的smatch 对象中。
//1.使用正则表达式库
1).从简单的例子开始。
{
string pattern("[^c]ei");
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern);
smatch results;
string test_str = "receipt freind theif receive";
if (regex_search(test_str, results, r))
cout << results.str() << endl;
}
2).指定regex 对象的选项见表格p647
- 当我们定义一个
regex 或者对一个regex 使用assign 赋新值的时候,可以指定一些标志来影响regex 如何操作。这些标志控制regex 对象的处理过程。详见p647。
- 对于指出编写正则表达式所用的语言的6个标志,我们必须设置其中一个,而且只能设置一个。默认情况下,
ECMAScript 标志被设置。从而,regex 会使用ECMA-262规范。这也是很多Web 浏览器所使用的正则表达式语言。 - 其他的三个标志允许我们指定正则表达式处理过程中与语言无关的方面。例如,我们可以指定希望正则表达式匹配过程中以大小写无关的方式进行。
{
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;
}
}
}
3).我们可以将正则表达式本身看作是,用一种简单程序设计语言编写的“程序”。这种语言不是由c++编译器解释的。正则表达式是在运行时,当一个regex 对象被初始化或者被赋予一个新模式时,才被“编译”的。与任何其他程序设计语言一样,我们用这种语言编写的正则表达式也可能会有错误。需要意识到的是,一个正则表达式的语法是否正确是在运行时才解析的。
- 如果我们编写的正则表达式存在错误,则在运行时会抛出一个
regex_error 的异常。 - 类似于标准异常类型,
regex_error 有一个what 操作描述发生了什么错误;还有一个名为code 的成员,返回某一个错误类型对应的数值编码;它返回的值是由具体实现定义的。RE库能抛出的标准错误,见表17.7(p649)
{
try {
regex r("[[:alnum:]+\\.(cpp|cxx|cc)&", regex::icase);
} catch (regex_error e) {
cout << e.what() << "\ncode :" << e.code << endl;
}
regex_error(error_brack)
The expression contained mismatched [ and ].
code: 4
}
4).正则表达式类型和输入序列类型
- 输入的序列可以是普通的
char 数据,也可以是wchar_t 数据 - 字符可以是保存在
string ,也可以是在char 数组中。(宽字符版本,wstring ,wchar_t 数组中。)
regex 类保存类型char 的正则表达式;wregex 类保存类型wchar_t 的正则表达式,它的操作和regex 完全相同。唯一的差别是wregex 的初始值必须是哟共wchar_t 而不是char 。smatch 表示string 类型的输入序列;cmatch 表示字符数组的输入序列;wsmatch 表示宽字符串wstring 的输入序列;wcmatch 表示宽字符数组的输入序列。
- 我们使用的RE库类型,必须和输入序列类型匹配。详见p650。
{
regex r("[[:alnum:]]+\\.(cpp|cxx|cc)$", regex::icase);
smatch results;
if (regex_reach("myfile.cc", results, r));
}
//2.匹配和Regex迭代器类型
1).我们可以使用sregex_iterator 迭代器类获取所有的匹配。
regex 迭代器是一种迭代器适配器,被绑定到一个输入序列和一个regex 对象上。迭代器的操作见表17.9(p651)- 当我们将一个
sregex_iterator 绑定到一个string 序列和一个regex 对象上时,迭代器自动定位到string 中的第一个匹配的位置。即,sregex_iterator 的构造函数自动对给定的string 和regex 调用regex_search 。当我们解引用迭代器时,会得到一个对应一次所有结果的samtch 对象,当我们递增迭代器时,regex_search 会输入序列string 中查找下一个匹配。
2).使用sregex_iterator 。
{
string pattern("[^c]ei");
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern, regex::icase);
for (sregex_iterator it(file.begin(), file.end(), r), end_it;
it != end_it; ++it) {
cout << it->str() << endl;
}
for (sregex_iterator it(file.begin(), file.end(), r), end_it);
it != end_it; ++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;
}
}
//3.使用子表达式
1).正则表达式中的模式通常包含一个或者多个子表达式。一个子表达式是模式的一部分,本身也具有意义。正则表达式语法通常用括号表示子表达式。
- 例如,匹配文件后缀时,就是用括号类分组可能的文件扩展名。每当我们使用括号分组多个可行选项时,同时也就声明了这些选项形成子表达式。
{
regex r("([[:alnum:]]+)\\.(cpp|cxx|cc)$",regex::icase);
if (regex_search(filename, results, r))
cout << results.str(1) << endl;
}
2).子表达式用于,数据验证
{
"(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
string phone = ...;
regex r(phone);
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();
}
}
}
}
练习
- 17.22,任意多个空白字符。
(\s) 还是(\s)*
//4.使用regex_replace
1).用来查找并且替换一个序列的时候。详见p657(表17.12)。它接受一个输入字符序列和一个regex 对象,以及我们想要的输出形式的字符串。
- 替换字符串由我们想要的字符组合和匹配的字符串中对应的子表达式组成。
{
string fmt = "$2.$5.$7";
regex r(phone);
string number = "(908) 555-1800";
cout << regex_replace(number, r, fmt) << endl;
908.555.1800
}
2).只是替换输入序列中的一部分
{
int main() {
string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\d{3})([-. ])?(\d{4})";
regex r(phone);
string s;
smatch m;
string fmt = "$2.$5.$7";
while (getline(cin, s)) {
cout << regex_replace(s, r, fmt) << endl;
}
return 0;
}
}
3).用来控制匹配和格式的标志
- 与控制
regex 对象匹配过程的标志一样。替换过程中有相似的控制匹配和格式的控制。(这些都是标准库定义的)。详见表格17.13。 - 这些标志可以传递给函数
regex_search,regex_match或者类smatch的format成员 - 匹配和格式化标志的类型为
match_flag_type 。这些值均定义在regex_constants 命名空间中。与bind 的placeholders ,regex_constants 也是在命名空间std 中的命名空间。 using std::regex_constants::format_no_copy;
{
string fmt2 = "$2.$5.$7 ";
cout << regex_replace(s, r, fmt2, format_no_copy) << endl;
}
/4.随机数
1).在新标准之前,c和c++都依赖于一个简单的c库函数rand 。此函数生成均匀分布的伪随机数,范围在0-32767之间(系统相关的最大值)。
- 需要随机的浮点数,需要非均匀分布的数。为了转换生成的随机数,类型或者分布,常常会引入非随机性。
- 解决,定义在头文件中
random 的随机数库通过一组协作的类来解决这些问题,
- 随机数引擎类,生成随机的
unsigned 整数序列,范围内的每一个数被生成的概率是相同的。 - 随机数分布类,使用引擎返回指定类型,给定范围的,服从特定概率分布的随机数。
- c++程序不应该使用库函数
rand ,应该使用default_random_engine 类和恰当的分布类对象。
//1.随机数引擎和分布
1).随机数 引擎 是一个函数对象类。定义了一个调用运算符函数。
- 该运算符函数不接受参数
- 返回一个随机的
unsigned 整数。
{
default_random_engine e;
for (size_t i = 0; i < 10; ++i)
cout << e() << " ";
}
2).分布类型和引擎
{
uniform_int_distribution<unsigned> u(0, 9);
default_random_engine e;
for (size_t i = 0; i < 10; ++i) {
cout << u(e) << " ";
}
}
- 分布类型也是函数对象类。它接受一个随机数引擎类作为参数,分布对象使用它的引擎参数生成随机数,并将其映射到指定的分布中。
{
u(e);
u(e());
}
3).比较随机数引擎和rand 函数
比较类型 | rand | 引擎对象 |
---|
生成数的范围 | 在0-RAND_MAX之间 | 它生成的unsigned在一个系统定义的范围内,可以调用该类型对象的min() ,max() 返回值来得到。依赖于系统。 |
4).引擎生成一个数值序列
{
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;
}
}
5).设置随机数发生器种子
- 提供种子,
seed 。种子就是一个数值, 引擎 利用它从序列中的一个新位置重新开始生成随机数。 - 两种方式提供
seed
- 创建时设置种子
- 调用引擎的
seed 成员
{
default_random_engine e1;
default_random_engine e2(234235242);
default_random_engine e3;
e3.seed(32767);
default_random_engine e4(32767);
default_random_engine e(time(0));
}
//3.其他随机分布
1).解决不同分布和不同类型的问题(随机引擎只是生成均匀分布的unsigned )。标准库定义了不同的随机数分布类来满足这个需求。 2).生成随机实数
- 解决0-1之间的随机数。最常用但不正确的从
rand 获得一个随机浮点数的方法是使用rand()/RAND_MAX 。不正确的原因是随机整数的精度通常低于随机浮点数,这样一些浮点值就永远不会生成。 - 使用新标准库设施,支持的操作见p664。
{
default_random_engine e;
uniform_real_distribution<double> d(0, 1);
u(e);
uniform_real_distribution<> u(0, 1);
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 < val.size())
++vals[v];
}
for (size_t i = 0; i != vals.size(); ++i) {
cout << i << ": " << string(vals[i], "*") << endl;
}
string resp;
default_random_engine e;
bernoulli_distribution b;
do {
bool first = b(e);
cout << (first ? "We go first" : "You get to go first") << endl;
cout << ((play(first)) ? "sorry you lost" : "congrats, you won") << endl;
cout << "play again ? Enter 'yes' or 'no'" << endl;
} while (cin >> resp && resp[0] == 'y');
}
/5.IO库再探
//1.格式化输入与输出
1).除了条件状态之外,每一个iostream 对象还维护一个格式状态来控制IO如何格式化的细节。格式状态控制格式化的某一些方面,
- 整数的进制
- 浮点值的精度
- 输出元素的宽度
2).标准库定义了一组操作符,来修改流的格式状态。见表17.17(p670)、17.8。一个操作符是一个函数或者一个对象。它们可以用作输入或输出运算符的运算对象,也返回流的对像。因此我们可以在一条语句中组合操纵符和数据。 3).操纵符用于两大类输出控制。
- 控制数值的输出形式
- 控制补白的数量和位置
- 大多数的改变格式状态的操纵符都是设置和复原成对的。当操纵符改变流的格式状态时,通常改变后的状态对所有后续的IO都生效。
- 通常在不需要特殊格式时尽快将流恢复到默认的状态下。
4).应用
- 控制布尔值的格式
{
cout << "default bool value: " << true << " " << false
<< "\nalpha bool value: " <<
boolalpha <<
true << " " << false
<< endl;
cout << boolalpha << true << noboolalpha ;
}
- 控制整型的进制
{
cout << "default:" << 20 << " " << 1024 << endl;
cout << "octal(8):" << otc << 20 << 1024 << endl;
cout << "hex(16):" << hex << 20 << 1024 << endl;
cout << "decimal(10):" << dec << 20 << 1024 << ednl;
20 1024
24 2000
14 400
20 1024
cout << showbase;
...
cout << noshowbase;
20 1024
024 02000
0x14 0x400
20 1024
cout << uppercase << showbase << hex << .....
<< nouppercase << noshowcase << dec << endl;
0X14 0X400
}
- 控制浮点数格式
{
cout << "Precision: " << cout.precision() << ",value:" << sqrt(2.0) << endl;
cout.precision(12);
cout << "..." << ....
cout << setprecision(3);
.....
6,1.41421
12, 1.41421356237
3, 1.41
cout << 100 * sqrt(2.0) << '\n'
<< scientific
<< fixed
<< hexfloat
<< defaultfloat
<< endl;
141.421
1.414214e+002
141.421356
0x1.ad7bcp+7
141.421
cout << 10.0 << endl;
cout << showpoint << 10.0
<< noshowpoint << endl;/10.000000
}
- 控制补白
{
int i = -16;
double d = 3.14159;
....
cout << internal << left << right ....
cout << sefill('#') << setw(12) << d << endl;
cout << setw(12) << i << endl;
cout << setfill(' ');
#########-16
#####3.14159
-16#########
3.14159#####
-#########16
#####3.14159
}
- 控制输入格式
{
char ch;
while (cin >> ch) {
cout << ch;
}
a b c
d
abcd
cin >> noskipws;
while (cin >> ch)
cout << ch;
cin >> skipws;
a b c
d
}
//2.未格式化的输入和输出操作
1).到目前为止我们只使用过格式化IO操作。
- 输入运算符忽略空白符(包含空格符…)
- 输出运算符应用补白,精度规则。
2).标准库还提供了一组底层操作,支持未格式化IO。这些操作允许我们将一个流当作一个无解释的字节序列来处理。 3).单字节操作
- 有几个未格式化操作每次一个字节的处理流。见表格17.19(p673)。它们会处理而不是忽略空白符。
{
char ch;
while (cin.get(ch))
cout,put(ch);
}
4).将字符放回输入流
- 有时候,我们需要读取一个字符才知道还没有准备好处理它。此时我们希望可以退回到流中。标准库提供了三种退回字符的方式。有细微的差别
peek 返回输入流中下一个字符的副本,但是不会将他从流中删除。unget 使得输入流向后移动,从而最后读取的值又回到流中。即使我们不知道最后从流中读取什么值,仍然可以调用unget putback ,是更加特殊的unget ,它退回流中读取的最后一个值,但它必须接受一个参数,此参数必须和最后读取的值相同。
- 一般情况下,在读取下一个值之前,标准库保证我们可以退回最多一个值,即,标准库 不保证 中间 不进行 读取操作的情况下, 能 连续调用
putback 或者unget 。
5).从输入操作返回的int值
- 函数
peek 和无参数的get 版本都以int 从输入流返回一个字符。 - 原因,可以返回文件尾标记,我们使用
char 范围中的每一个值来表示一个真实字符,因此,取值范围中没有额外的值可以标志文件尾。 - 返回int的函数将它们要返回的字符先转换为
unsigned char ,然后再将结果提升到int 。因此,即使字符集中有字符映射到负值,这些操作返回的int也是正值。而标准库使用负值表示文件尾,这样就可以保证与任何合法字符的值都不一样。头文件cstdio 定义了一个名为EOF 的const ,我们可以使用它来确定返回的是否是文件尾,而不必记忆文件尾的实际数值。
{
int ch;
while ((ch = cin.get() != EOF))
cout.put(ch);
}
6).多字节操作
- 一次处理大的数据块。速度快,但是类似于其他底层操作,这些操作容易出错。这些操作要求我们自己分配并管理保存和提取数据的字符数组。表17.20,(p674)列出了这些操作。
get 和getline 函数接受相同的参数,它们的行为类似但是不相同。sink 都是一个char 数组,用来保存数据,两个函数都一直读取数据,知道以下条件之一发生。
- 数组已经满了
- 遇到文件的末尾
- 遇到分隔符(由我们指定)
get 将分割符留作istream 的下一个字符getline 则将其读取并且丢弃。
- 无论哪一个函数都不会将分隔符保存再
sink 中。 - 一个常见的错误是本想着从流中删除分隔符,但是没有做。
7).确定读入了多少个字符
- 调用
gcount 来确定最后一个未格式输入操作读取了多少个字符。 - 应该再任何后续未格式化输入操作之前调用
gcount 。(明确读取的字符数目) - 将字符退回流的单字符操作也是属于未格式化操作。如果在调用
gcount 之前调用了peek,unget,getback ,则gcount 返回值为0。
8).底层函数容易出错。
- 例如,将
get,peek 的返回值赋予一个char 而不是int 。这样做是错误的,但是编译器不会发现这个错误。具体发生什么错误,取决于机器和输入数据。
- 在一个
char 被实现为unsigned 的机器上,while ((ch = cin.get() != EOF)) 永远都不会停止循环。 - 在一个视
char 为signed char 上,会发生什么?有的不能确定循环的行为;有的遇到变量的值越界了,依赖编译器如何处理这种越界;有的恰好可以正常工作,除非遇到和EOF 相等的情况,虽然在普通数据中共这种字符不太可能,但是底层的IO通常用于读取二进制值,二进制值不能直接映射到普通字符和数值。… - 总之,读写有类型的值,格式化的数值,这样的错误就不会发生。可以使用标准库更加安全,更加高层的操作。就使用标准库的操作。
练习
//3.流随机访问操作
1).各种流类型通常都支持对流中数据的随机访问。**我们可以重定位流,使之跳过一些数据,首先读取最后一行,然后读取第一行,以此类推。**标准库提供了一对函数(成员函数。),
seek 定位到流中的给定位置tell 返回我们当前的位置。
- 随机IO本质上是依赖于系统的,为了理解如何使用这些特性,必须查询系统文档。
2).虽然标准库为所有的流类型都定义了seek 和tell 函数,但他们是否会做有意义的事情,依赖于流绑定的设备。绑定到cin,cout,cerr,clog 的流设备不支持随机访问——当我们cout 直接输出数据时,类似向回跳十个位置这样的操作是没有意义的…对这些流我们可以调用seek,tell 函数,但是在运行时会出错,将流置为无效状态。
- 由于
istream,ostream 类型通常不支持随机访问,以下内容只是用于fstream,和sstream 类型。
3).seek,tell 函数,详见表格17.21(p676)
- 为了支持随机访问,IO类型维护一个标记来确定下一个读写操作要在哪里进行。还提供了以上两个函数。实际上,标准库定义类两对的
seek,tell 分别用于输入流,输出流。差别在于后缀一个是g(get)读取,输入;一个是p(put)写入,输出。 - 对于iostream,fstream,stringstream既可以读又可以写的关联的流,可以对这些类型使用g和p。
4).标准库虽然区分读写的版本,但是在流中只有单一的标记–不存在独立的读标记和写标记。
- 当我们处理一个只读或者只写的流时,这种特性没有体现出来;因为我们试图对输出流使用g,或者对输入流使用p将会报错。
- 而对于
fstream,stringstream 。它们有单一的缓冲区,用来保存读写的数据,标记只有一个,表示缓冲区当前的位置。标准库将g和p版本的读写都映射到这个单一的标记。由于只有单一的标记,我们要在读写操作间进行切换时,就必须使用seek 操作来重定位标记。
5).重定位标记
{
seekg(new_position);
seekp(new_position);
seekg(offset, from);
seekp(offset, from);
ostringstream writeStr;
ostringstream::pos_type mark = writeStr.tellp();
if (cancelEntry) {
writeStr.seekp(mark);
}
abcd
efg
hi
j
5 9 12 14
int main() {
fstream inOut("copyOut", fstream::ate | fstream::in | fstream::out);
if (!inOut) {
cerr << "..." << endl;
return EXIT_FALLURE;
}
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();
intOut.seekp(0, fstream::end);
inOut << cnt ;
if (mark != end_mark) inOut << " ";
inOut.seekg(mark);
}
inOut.seekp(0, fstream::end);
inOut << '\n';
return 0;
}
}
/6.小结
1).match 对象是sub_match 对象的容器(反映的是整个正则表达式(0号元素)/子表达式的匹配结果。),而且它还有成员prefix(),suffix() 返回匹配之前和之后的信息。也是一个sub_match 对象。
|