IO 库
包括三种流:iostream(基类)、fstream(文件)、sstream(字符串),这三种流每种都有宽字符和窄字符两类,每一类又可以细分为输入流、输出流、输入输出流
各种不同类型的流有继承关系
IO 对象没有拷贝或者赋值:
ofstream out1, out2;
out1 = out2; // 错误,不能对流对象赋值
IO 流的条件状态、iostate、clear 清除错误、setstate 方法
输出缓冲区刷新的原因:
- 程序正常结束,作为
main函数return操作的一部分,缓冲刷新被执行 - 缓冲区满
endl、flush、ends等操作显式刷新缓冲区- 可以用操纵符
unitbuf设置流的内部状态,告诉流接下来的每次写操作后都进行一次flush操作来清空缓冲区。默认情况下cerr是设置unitbuf的,写到cerr的内容是立即刷新的 - 输出流可能会关联到另一个流,读写被关联的流时,关联到流的缓冲区会被刷新。一般
cin和cerr是关联cout的
文件流在析构时自动关闭打开的文件
顺序容器
标准库中的顺序容器:
vector:可变大小数组,支持快速随机访问,在尾部之外的位置插入或删除元素可能很慢deque:双端队列,支持快速随机访问list:双向链表,只支持双向顺序访问forward_list:单向链表,只支持单向顺序访问array:固定大小数组,不能添加或删除元素string:与vector相似的容器,但专门用于保存字符
如果没有默认构造函数,那么必须传给容器一个初始化函数
内置数组不支持拷贝,但是 array 支持
array 容器类模板需要指定容器大小,如:
array<int, 42> arr;
容器类型别名
iterator:迭代器const_iteratorreverse_iteratorsize_typedifference_type:带符号的整数类型,足够保存两个迭代器之间的距离value_type:元素类型reference:元素的左值类型,与value_type &含义相同const_reference:元素的const左值类型
顺序容器操作
swap 方法:
- 除
string和array外,指向容器的迭代器、引用和指针在swap操作后都不会失效,它们仍指向swap操作之前所指向的元素,但是在swap之后已经属于不同的容器了 string执行swap后会导致迭代器、引用和指针失效array执行swap会真正交换它们的元素。因此指针、引用和迭代器所绑定的元素保持不变,但进行了值交换
向顺序容器中添加元素:
push_back:除array和forward_list外都支持push_front:list、forward_list和deque支持insert:传入一个迭代器,将元素插入到该迭代器之前的位置。还可以传入数量。它返回第一个新加入元素的迭代器,如果数量为 0,则直接返回传入的迭代器emplace操作:将参数传递给元素类型的构造函数,容器直接在相应内存空间构造元素
当用一个对象初始化容器时,或将对象插入到容器中时,实际上将元素拷贝进容器
访问元素:
front:每个顺序容器都有back:除去forward_list都有at和下标访问:支持快速随机访问的容器(string、vector、deque、array)提供。at会检查下标是否越界,抛出out_of_range异常
如果容器为空,那么访问 front 和 back 都是未定义行为
这几个方法返回的都是引用,如果容器是 const,那么返回的是 const 引用
删除元素:
pop_back、pop_fronterase:可以传入一个迭代器或两个迭代器(表示范围),返回删除的最后一个元素之后一个位置的迭代器
forward_list 的特殊操作:
由于插入、删除操作需要访问前驱,所以它有所不同,定义了 insert_after、emplace_after、erase_after 等方法
并且,定义了首前(off-the-beginning)迭代器 before_begin() 来让我们将元素放到 head 之前
容器操作可能使得迭代器失效
添加元素后:
vector和string在添加元素后可能会扩容,此时重新分配空间,原来的迭代器、指针和引用会失效- 对于
deque:- 它是分段存储,内部存储了段指针数组,而迭代器依赖于这个指针数组
- 插入到首尾位置之外的任何位置,可能在段之间移动元素,位置发生变化,所以可能会导致迭代器、指针和引用失效
- 插入首尾位置,可能会分配新的段,但是底层数据结构不会整体移动,所以引用和指针不会失效,但是迭代器会失效
- 对于列表,添加元素后迭代器、指针和引用仍然有效
删除元素后:
- 对于
vector和string,指向被删元素之前元素的迭代器、指针和引用仍有效 - 对于
deque:- 在首尾之外的位置删除元素,指向被删元素外的其他元素的迭代器、指针和引用失效
- 在首位置删除元素,其他元素不影响
- 在尾位置删除元素,除了
end()迭代器外,其他元素不影响
- 对于列表,其他元素不影响
注意,不要保存 end 返回的迭代器
容器大小管理操作
如果没有空间容纳新元素,那么容器会分配新空间保存已有的元素和新元素,将已有元素从旧位置移动到新空间中,释放旧存储空间
一般一次扩容会分配 1.5 或 2 倍空间
容器大小管理操作:
shrink_to_fit:只适用于vector、string和deque,将capacity()减少为与size()相同大小。具体实现并不一定退回内存空间capacity:返回不重新分配空间的前提下可以保存元素的数量reserve:传入整数 n,分配至少能容纳 n 个元素的空间
下面两个只适用于 vector 和 string
原则:只有当迫不得已时才可以分配新的内存空间
额外的 string 操作
substrappend、replacefind、rfind、find_first_of等compare函数- 转换函数:
to_string、stoi、stol、stof等
容器适配器
除了顺序容器外,标准库还定义了三个顺序容器适配器:
stack:接受除array和forward_list外的顺序容器,默认基于dequequeue:接受deque(默认)和listpriority_queue:接受vector(默认)和deque
泛型算法
只读算法、写容器元素的算法、重排容器元素的算法
谓词:可调用的表达式,一元谓词、二元谓词
可调用对象、lambda 表达式
lambda 表达式不能有默认参数
lambda 实际上是个匿名对象,可以捕获环境(值方式和引用方式)
对于值方式捕获的变量,如果想要改变被捕获的变量,需要加 mutable 关键字:
void fcn3() {
size_t v1 = 42;
auto f = [v1] () mutable { return ++v1; }
v1 = 0;
auto j = f(); // j 为 43
}
引用捕获的直接改就行
参数绑定
bind 函数进行参数绑定:
int f(int, int, int, int, int);
auto g = bind(f, a, b, _2, c, _1);
上面的代码将有五个参数的函数 f 转换为有两个参数的 g
其中,_1 和 _2是占位符,在 std::placeholders 命名空间下
默认情况下,bind 通过拷贝的方式将非占位符参数传递给原函数,但是我们可以通过 ref 函数让它通过引用方式传递:
ostream & print(ostring & os, const string & s, char c) {
return os << s << c;
}
for_each(words.begin(), words.end(), bind(print, os, _1, ' ')); // 错误,os 不能拷贝
for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' ')); // 正确
除此之外,还有 cref 函数,传递 const 引用
这些函数都定义在头文件 functional 中
旧版本的 bind1st 和 bind2nd 等已被弃用
额外的迭代器
除了为每个容器定义的迭代器外,还定义了额外的几种迭代器:
插入迭代器
一种迭代器适配器,实现给容器添加元素
it = t 表示在 it 处插入值 t,根据类型会调用 push_back、push_front、insert
*it、++it、it++ 都不会对 it 做任何事情,每个操作都返回 it
有三种类型:back_inserter、front_inserter、inserter
注意插入顺序: copy(a.begin(), a.end(), front_inserter(...)) 会将 a 中的元素依次插入到头部,这会导致顺序颠倒
流迭代器
用于 IO 类型对象的迭代器
istream_iterator 用于输入流,ostream_iterator 用于输出流。这两个迭代器要求容器实现了 >> 或者 <<
istream_iterator:
默认构造函数构造了一个处于尾后位置的迭代器,如:
istream_iterator<int> in_iter(cin), eof; vector<int> vec(in_iter, eof);允许懒惰读取
ostream_iterator:
- 构造函数允许传入一个输出流
os和一个字符串d(可选),字符串d表示输出时每个值后面都输出一个d - 解引用、递增无效
反向迭代器
除了 forward_list 之外其他容器都支持反向迭代器,使用 rbegin、rend 等来获取反向迭代器
反向迭代器需要递减运算符,因此不能从 forward_list 和流中构造反向迭代器
使用 base 方法可以转回正常的迭代器
移动迭代器
后面的章节会介绍
泛型算法结构
算法要求的迭代器操作可以分为 5 个迭代器类别:
- 输入迭代器:只读不写,单遍扫描,只能递增
- 输出迭代器:只写不读,单遍扫描,只能递增
- 前向迭代器:可读写,多遍扫描,只能递增
- 双向迭代器:可读写,多遍扫描,可递增递减
- 随机访问迭代器:可读写,多遍扫描,支持全部迭代器运算
C++ 标准指明了泛型和数值算法的每个迭代器参数的最小类别
如果名字后面加一个 _copy,那就是算法的拷贝版本
链表的特定算法
list 和 forward_list 定义了几个成员函数形式的算法,如 sort、merge、remove、reverse 等
它们还定义了 splice 算法
链表特有的操作会改变容器
关联容器
标准库提供 8 个管理容器:
mapsetmultimap、multiset:关键字可重复出现的map和setunordered_map、unordered_set、unordered_multimap、unordered_multiset:哈希组织的关联容器
有序的关联容器使用 < 运算符来比较两个关键字,并且关键字的比较函数必须严格弱序
关联容器的类型别名:
key_valuemapped_type:每个关键字关联的类型(仅map)value_type:set中与key_value相同,map中为pair<const key_type, mapped_type>
set 的迭代器是 const 的,map 的迭代器中关键字也是 const 的
通常不对关联容器使用泛型算法
插入元素
可以使用 insert 和 emplace,对于非 multi 容器,它们的返回值通常是一个 pair:
- 第一个元素是迭代器,指向具有给定关键字的元素
- 第二个元素是一个
bool值,表示是否插入成功
而对于 multi 容器,无需返回 bool 值,它们直接返回一个指向新元素的迭代器
删除元素
关联容器定义了三个版本的 erase,可以传入迭代器、元素值、首尾迭代器
返回实际删除的元素数量
访问元素
map 的下标操作:
如果关键字不在 map 中,那么下标操作会添加一个具有此关键字的元素到 map 中
而 at 方法会进行参数检查,如果没有该关键字,返回 out_of_range 异常
下标操作和 at 方法都返回一个左值
可以使用 map 的 find 方法代替下标操作,此外还提供 count、lower_bound 之类的访问元素的方法
在 multi 容器中,使用迭代器加 count 来访问元素:
string search_item("aaa");
auto entries = authors.count(search_item);
auto iter = authors.find(search_item);
while (entries) {
cout << iter->second << endl;
++iter;
--entries;
}
或者使用 lower_bound 和 upper_bound 的组合
或者直接使用 equal_range 来直接得到两个迭代器表示区间
动态内存
两种智能指针:shared_ptr 和 unique_ptr,此外还有一个 weak_ptr
shared_ptr 和 unique_ptr 都支持的操作:
- 默认构造函数返回一个空智能指针
operator bool()返回内部的指针是否为空指针get方法返回内部的裸指针swap方法用来交换两个只能指针中的指针
shared_ptr 类支持:
make_shared<T>(...)方法,传入参数,使用这些参数来初始化一个T类型的对象shared_ptr<T> p(q):拷贝构造函数,会递增计数器,并且q内的指针要能转换成T*p = q:拷贝赋值运算符,会递减p原来的引用计数,递增q的引用计数unique方法:返回是否只有当前这一个引用use_count方法:返回当前的引用计数,可能很慢- 还有一些传入删除器的方法和构造函数,下面讲
unique_ptr 类支持:
- 带删除器的构造函数
release方法:放弃控制权,返回指针reset、reset(q)、reset(nullptr):释放当前指针,然后换成q或者空指针
unique_ptr 不能被拷贝,只能被移动;shared_ptr 可以被拷贝和移动
new 的一些操作
可以使用 auto 推断要 new 的对象的类型,但只要当括号中仅有单一初始化器时才可以使用
auto p1 = new auto(obj);
使用 new 分配内存时如果发生错误,会抛出 bad_alloc 异常,可以这样阻止:
int * p1 = new int; // 如果分配失败,抛出异常
int * p2 = new (nothrow) int; // 如果分配失败,返回空指针
这是定位 new 的一种
shared_ptr 和 new 结合使用
只支持显式调用构造函数:
shared_ptr<int> p1(new int(42)); // 正确
shared_ptr<int> p2 = new int(42); // 错误
还可以:
- 使用
shared_ptr<T> p(q, d)构造函数来用d替代shared_ptr中的delete操作 reset()方法:更新引用计数,并替换为空指针reset(q):更新引用计数,并替换为qreset(q, d):注意此时d是用来释放q的
不要混合使用普通指针和智能指针
不要使用 get 初始化另一个智能指针或为智能指针赋值
发生异常时,智能指针也可以自动释放资源
上面的 d 被叫做删除器(deleter),如:
void end_connection(connection * p ) { disconnect(*p); }
void f(destination & d) {
connection c = connect(&d);
shared_ptr<connection> p(&c, end_connection);
// 使用连接
// 当 f 退出时(即使是由于异常而退出),connection 会被正确关闭
}
weak_ptr

weak_ptr 不能直接访问对象,需要使用 lock 方法:
if (shared_ptr<int> np = wp.lock()) {
// ...
}
使用 lock 时会增加相应 shared_ptr 的引用计数
动态数组
创建大小为 0 的动态数组是合法的:
char arr[0]; // 错误
char * cp = new char[0]; // 正确
此时 cp 是一个合法的非空指针,但是它不能解引用
unique_ptr 有适用于动态数组的版本,如:
unique_ptr<int[]> up(new int[10]);
up.release();
注意,此时 release 方法会自动销毁这个动态数组!
shared_ptr 不直接支持管理动态数组,我们必须提供一个删除器:
shared_ptr<int> sp(new int[10], [](int * p) { delete [] p; });
sp.reset();
并且必须使用 get 获得原始指针后再使用下标
allocator 类
allocator 类帮助我们将内存分配和对象构造分离开来,它的用法如下:

它默认分配的内存是未构造的(unconstructed),也可以接受额外参数来构造
我们只能对已经真正构造了的元素进行 destory 操作
标准库还为 alloctor 类定义了两个伴随算法:
