目录
Sec8 IO库
8.1 IO类
-
wchar_t
:
宽字符版本(对应函数为前面加一个w) -
IO对象无拷贝或赋值
不能初始化ofstream参数
不能拷贝流对象 -
条件状态
确定一个流对象的状态的最简单的方法是将它作为一个条件来使用的while(cin >> word) // ok: 读操作成功
-
查询流的状态
iostate
-
管理条件状态
-
管理输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据。- endl
- flush:刷新缓冲区但不输出任何额外的字符
- ends
想缓冲区插入一个空字符,然后刷新缓冲区 - 崩溃:输出缓冲区不会被刷新!
-
关联输入和输出流
8.2 文件输入输出
-
可以继承istream和ostream对象
-
成员函数open、close
对open的成功与否进行检测是一个好习惯ifstream in(ifile); // 构筑一个ifstream并打开给定文件 ofstream out; // 输出文件流未与任何文件相关联 out.open(ifile + ".copy"); if (out) // 检测 /* 如果open失败,条件会未为假,就不会使用out 一旦一个文件流已经打开,它就保持与对应文件的关联。 要将文件流关联到另一个文件,必须先关闭已经关联的文件。一旦成功关闭,就可以 打开新文件。 */
8.3 stringstream
暂略
Sec9 顺序容器
9.4 Vector对象如何增长?
一次两倍
Sec10 泛型算法
10.2 初识
-
find(iter1, iter2, val)
-
accumulate(iter1, iter2, init_val)
-
equal(iter1_begin, iter1_end, iter2_begin)
-
fill(iter1, iter2, val)
fill_n(iter, size, val)
-
auto it = back_inserter(container); // 创建一个迭代器
*it = val; // 等价于直接调用push_back
vector<int> vec; auto it = back_inserter(vec); // 通过它赋值会将元素添加到vec fill_n(it, 10, 0);
-
copy(iter_begin, iter_begin, iterDestination_begin)
返回的是其目的位置迭代器(递增后)的值 -
replace
读入一个序列,将其中所有等于给定值的元素都改为另一个值
前俩为迭代器,表示输入序列,后俩个一个是要搜索的值,另一个是新值
replace(iter_begin,iter_end,val_before,val_replace)
如果希望原序列不变,可以用
replace_copy(iter_begin, iter_end, save_iter, val_before, val_replace)
-
sort
用
<
运算符来排序消除重复单词
先用sort排序,将重复的元素相邻,再用unique
标准库算法。使得不重复的元素出现在vector的开始部分。
用vector的erase成员来完成真正的删除操作void elimDups(vector<string> &words){ sort(words.begin(), words.end()); auto end_unique = unique(word.begin(), words.end()); words.erase(end_unique, words.end()); }
10.3 定制操作
-
向算法传递函数
sort的第二个版本,第三个参数为一个谓词 predicate-
谓词:一个可调用的表达式,返回结果是一个能用作条件的值。
-
分类:一元谓词 unary predicate(单一参数) 二元谓词 binary predicate(两个参数)
-
例子:
bool isShorter(const string &s1, const string &s2){ return s1.size() < s2.size(); } sort(words.begin(), words.end(), isShorter);
-
-
-
stable_sort(iter1, iter2, func)
稳定排序算法维持相等元素的原有顺序 -
find_if(iter_begin, iter_end, func)
第三个参数必须是一元谓词
对输入序列每一个元素调用谓词,返回第一个使得谓词返回非0值的元素。如果不存在则返回尾迭代器 -
lambda表达式
我们可以向一个算法传递任何类别的可调用对象 callable object[capture list](parameter list) -> return type { function body }
一个lambda表达式表示一个可调用的代码单元。即未命名的内联函数
-
capture list: 函数内定义的局部变量的列表,通常为空
-
return type, parameter list 和 function body 与任何普通函数一样,分别表示返回类型、参数列表和函数体。
lambda必须用到尾置返回来指定返回类型
可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体auto f = [] {return 42}; f(); // 调用
-
传递参数:
[] (const string &a, const string &b) { return a.size() < b.size(); }
-
使用捕获列表
-
编写一个传递给find_if的可调用表达式,希望这个表达式能将输入序列中的每个string的长度与biggies函数中的sz参数的值进行比较
一个lambda将局部变量包含在其捕获列表中来指出将会使用这些变量,捕获列表指引lambda在其内部包含访问局部变量所需要的信息[sz] (const string &a) { return a.size() >= sz; }
[]中可以使用逗号分隔的名字列表,这些名字都是它所在函数中定义的。
一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中 -
auto wc = find_if(words.begin(), words.end(), [sz](const string &a) { return a.size() >= sz; } )
-
-
-
for_each:
此算法接受一个可调用对象,并对输入序列中每个元素调用此对象for_each(wc, words_end(), [](const string &s) {cout << s << " ";}); cout << endl;
10.3.3 lambda捕获与返回
当向函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象,传递的参数就是此编译器生成的类类型的未命名对象。
-
捕获方式
-
值捕获
前提是变量可以拷贝
[v1] { return v1; };
-
引用捕获
[&v1] { return v1; };
void biggies(vector<string> &words, vector<string>::size_type sz. ostream &os = cout, char c = ' ') { for_each(words.begin(), words.end(), [&os, c](const string &s) { os << s << c}); }
若函数返回一个lambda,则不能包含引用捕获
-
隐式捕获
[=](cosnt string &s){return s.size() >= sz; };
用=或者&,&告诉编译器采用捕获引用方式,=则采用值捕获方式,可以混合使用!
[&, c]
[=-, &os]
-
-
可变lambda
值拷贝变量不会改变其值,如果希望改变一个被捕获的变量的值,就必须再参数列表首加上关键字mutable
[v1] () mutable {return ++v1;}
所以可变lambda可以省略参数列表 -
指定lambda返回类型:
如果lambda体是单一的return语句,则返回一个条件表达式的结果,无须指定返回类型。若有多条,且没指定返回类型,就会被编译器推定为返回void。
定义lambda返回类型一定要用尾置返回类型[](int i) -> int { if (i < 0) return -i; else return i; }
-
参数绑定
如果捕获列表为空,可以用函数代替lambda。
但如果对于捕获局部变量的lambda,用函数来替换旧不是那么容易了。比如find_if的第三个参数 -
标准库函数
bind
可以将bind堪称一个通用的函数适配器,接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。
auto newCallable = bind(callable, arg_list);
arg_list为参数列表,对应给定的callable参数
其中,_n
类型的名字,为占位符,表示newCallable的参数,占据了传递给newCallable的参数的位置,数值n表示生成的可调用对象中参数的位置,_1
为newCallable的第一个参数。-
例子:
auto check6 = bind(check_size, _1, 6); // 只有一个占位符表示check6只接受单一参数,占位符出现再arg_list的第一个位置,表示对应check_size函数的第一个参数,check6会将这个参数传递给check_size。 // 如: string s = "dwafda"; bool b1 = check6(s); // 调用check_size(s, 6) // 此时因为只有一个参数,所以可以将check6用于基于lambda的find_if端调用
-
bind的参数
auto g = bind(f, a, b, _2, c, _1); // 生成一个新的可调用对象,有2个参数,分别用占位符_2和_1表示。这个新的可调用对象将它自己的参数作为第三个和第五个参数传递给f。 f的第一个第二个和第四个参数为a,b,c // 即: g(_1, _2); // 等价于 f(a,b,_2,c,_1);
-
实际应用例子
用bind重排参数顺序
sort(words.begin(),words.end(), isShorter); sort(words.begin,words.end(), bing(isShorter, _2, _1));
-
注意:
bind拷贝其参数。如果我们希望传递给bind一个对象而又不拷贝它,那必须使用标准库函数ref
例子:// 错误! for_each(words.begin(), words.end(), bind(print, os, _1, ' ')); // 正确 for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));
-
10.4 再探迭代器
- 种类
- 容器定义的迭代器
- 插入迭代器
- 流迭代器
- 反向迭代器:除了forward_list都有反向迭代器
- 移动迭代器
10.4.1 插入迭代器
it = t; // it指的位置插入t
*it, ++it, it++ // 不会做任何事,只会返回it
-
back_inserter
(push_back) -
fornt_inserter
(push_front) -
inserter
创建一个使用insert的迭代器。此函数接受第二个参数,此参数必须是一个指定给定容器的迭代器。
然后元素插入到给定迭代器所表示元素之前 -
工作过程:
it = inserter(c, iter); // 得到一个迭代器 *it = val; // 插入到指定元素之前 // 相同的工作: it = c.insert(it, val); // it指向新加入的元素 ++it; // 递增it使之指向原来的元素
list<int> lst == {1,2,3,4}; list<int> lst2, lst3; // 空list // lst2包含4,3,2,1 copy(lst.cbegin(), lst.cend(), front_inserter(lst2)); // 插入迭代器 // lst3包含1,2,3,4 copy(lst.cbegin(), lst.cend(), inserter(lst3, lst3.begin()));
10.4.2 iostream迭代器
istream_iterator
和 ostream_iterator
-
使用:
-
从迭代器范围构造vec
istream_iterator<int> in_iter(cin), eof; // 从cin读取int vector<int> vec(in_iter, eof);
-
使用算法操作流迭代器
istream_iterator in(cin), eof; cout << accumulate(in, eof, 0) << endl;
-
istream_iterator允许使用懒惰求值
当绑定到一个流时,标准库并不保证迭代器立刻从流读取数据,具体实现可以推迟从流中读取数据,知道我们使用迭代器时才真正读取。
-
-
ostream_iterator操作
-
输出值的序列
ostream_iterator<int> out_iter(cout, " "); for(auto e : vec) *out_iter++ = e; // 将元素写到cout cout << endl;
注意:out_iter可以忽略解引用和递增运算
++,* 实际上不做任何工作 -
或者直接调用copy来打印vec中的元素
copy(vec.begin(), vec.end(), outer_iter); cout << endl; // 比循环打印简单
-
-
可以为任何定义了>> 的对象创建istream_iteraotr,同理<<
10.4.3 反向迭代器
(除forward_list之外都支持)
调用rbegin,rend,crbegin,crend来获得反向迭代器
-
需要递减运算符
-
// 在逗号分隔的列表中查找最后一个元素 auto rcomma = find(line.crbegin(), line.crend(),',');
-
注意,正常打印别用反向迭代器,不然会逆序输出
cout << string(line.crbegin(), rcomma) << endl; // 用base成员函数可以让rcomma转换为普通迭代器,能正向移动
10.5 泛型算法结构
-
依照算法所要求的迭代器操作,可以分为5个迭代器类别(iterator category)
- 输入迭代器
读取序列中的元素 - 输出迭代器
只写不读 - 前向迭代器
读写,一个方向,而且课已多次读写一个元素 - 双向迭代器
双向++-- - 随机访问迭代器
提供常量时间内访问序列中任意元素的额能力
- 输入迭代器
-
算法形参模式:
arg(beg, end, other args); arg(beg, end, dest, other args); arg(beg, end, beg2, other args); // 假定两个范围大小相同 arg(beg, end, beg2, end2, other args);
-
算法命名规范
-
使用重载形式传递一个谓词
unique(beg, end); // == unique(beg, end, comp); // comp
-
_if
版本的算法find(beg, end, val); // val第一次出现的位置 find(beg, end, pred); // 查找第一个令pred为真的元素
-
区分拷贝元素的版本和不拷贝的版本
reverse(beg, end); reverse_copy(beg, end, dest); // 逆序拷贝进dest
-
同时提供:
remove_copy_if(v1.begin(), v1.end(), back_inserter(v2), [](int i){ return i % 2; });
-
-
特定容器算法
list, forward_list
lst.merege(lst2); // 必须两个有序 lst.merge(lst2.comp); // 用给定的比较操作 lst.remove(val); lst.remove_if(pred); lst.reverse(); lst.sort(); lst.sort(cmp); lst.unique(); // 使用 == lst.unique(pred); // 使用给定的二元谓词
-
splice 成员
链表数据结构所特有的lst.splice(args) (之前)或者 flst.splice_after(args) (之后) args: (p, lst2) (p, lst2, p2) (p, lst2, b, e)
链表特有操作会改变容器。