C++ Standard Template Library
1 容器
std::array
1 |
|
大小固定的数组,支持快速随机访问,不能添加和删除元素。
std::vector
1 |
|
动态数组(Dynamic Arrays),支持快速随机访问,在尾部之外的位置插入或删除元素可能很慢。
当需要增加数据时,重新分配一块内存,将原来的数据拷贝过来并加上需要添加的数据,然后删除原来的数据。
问题:拷贝很耗时。
优化方案一:提前预留空间,减少分配内存拷贝数据次数。
优化方案二:使用emplace_back
而不是push_back
。
1 |
|
std::string
1 |
|
与 std::vector 相似,专门用于保存字符,随机访问快,在尾部插入或删除速度快。
std::deque
1 |
|
双端队列,支持快速随机访问,在头尾位置插入或删除速度很快。
std::list
1 |
|
双向链表,只支持双向顺序访问,速度较慢,在任何位置插入或删除速度都很快。
前面在讲 STL list 容器时提到,该容器的底层是用双向链表实现的,甚至一些 STL 版本中(比如 SGI STL),list 容器的底层实现使用的是双向循环链表。
std::unordered_map
1 |
|
底层实现为哈希表,元素无序,查找时间复杂度为 $O(1)$
std::map
1 |
|
底层实现为红黑树,元素有序,查找时间复杂度为 $O(log_{2}{n})$
- 容器中的元素是键值对 (key, value),关键字 (key) 起到索引的作用,值 (value) 则表示与索引相关联的数据;
- 迭代器不是 const 的,允许修改元素的 value,但不允许修改 key;
- 支持下标操作。
插入重复元素时,如果用insert
,则插入失败,如果用操作符,则覆盖已有元素。
1 |
|
获取不存在的元素时,如果用at
,会抛出std::out_of_range
错误,如果用操作符,会创建对应的元素。正常情况下,建议用at
获取元素。
1 |
|
std::set
1 |
|
底层实现为红黑树,元素有序,查找时间复杂度为 $O(log_{2}{n})$
- 容器中的元素为关键字 key 的简单集合,每个元素只包含一个关键字;
- 迭代器是 const 的,不允许修改元素的值;
- 不支持下标操作。
std::map 和 std::set 是根据关键字排序来保证其有序性的,如果允许修改 key 的话,那么首先需要删除该键,然后调节平衡,再插入修改后的键值,调节平衡,如此一来,严重破坏了容器的结构,导致 iterator 失效,不知道应该指向改变前的位置,还是指向改变后的位置。所以 STL 中将 set 的迭代器设置成 const,不允许修改迭代器的值;而 map 的迭代器则不允许修改 key 值,允许修改 value 值。
std::pair
1 |
|
std::make_pair
2 智能指针
1 |
|
std::auto_ptr
Since C++98, deprecated in C++11, removed in C++17.
std::unique_ptr
由 C++11 引入,旨在替代不安全的 std::auto_ptr。
unique_ptr
不共享它的指针,它无法复制到其他unique_ptr
,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL) 算法,只能移动unique_ptr
。这意味着,内存资源所有权将转移到另一unique_ptr
,并且原始unique_ptr
不再拥有此资源。
unique_ptr
实现了独享所有权的语义,一个非空的unique_ptr
总是拥有它所指向的资源。转移一个unique_ptr
将会把所有权也从源指针转移给目标指针(源指针被置空)。拷贝一个unique_ptr
将不被允许,因为如果你拷贝一个unique_ptr
,那么拷贝结束后,这两个unique_ptr
都会指向相同的资源,它们都认为自己拥有这块资源,所以都会企图释放。因此unique_ptr
是一个仅能移动 (move_only) 的类型。当指针析构时,它所拥有的资源也被销毁。默认情况下,资源的析构是伴随着调用unique_ptr
内部的原始指针的delete
操作。
Function | Description |
---|---|
operator bool |
Return true if the stored pointer is not null. |
operator= |
Move assignment operator. |
operator* |
Dereference the stored pointer. |
operator-> |
Return the stored pointer. |
get |
Return the stored pointer. |
reset |
The deleter will be invoked if a pointer is already owned. |
release |
Release ownership of any stored pointer. |
swap |
Exchange the pointer and deleter with another object. |
get_deleter |
Return a reference to the stored deleter. |
使用场景:
为动态申请的资源提供异常安全保证,动态申请内存后,保证在抛出异常或提前退出时释放资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15void Func()
{
int *p = new int(5);
// ...(可能会抛出异常)return
delete p;
}
// 替换为下面的写法
void Func()
{
unique_ptr<int> p(new int(5));
// ...(可能会抛出异常)
}返回函数内动态申请资源的所有权
1
2
3
4
5
6
7
8
9
10
11
12
13unique_ptr<int> Func(int p)
{
unique_ptr<int> pInt(new int(p));
return pInt; // 返回 unique_ptr
}
int main()
{
int p = 5;
unique_ptr<int> ret = Func(p);
cout << *ret << endl;
// 函数结束后,自动释放资源
}在容器中保存指针
1
2
3
4
5
6int main()
{
vector<unique_ptr<int>> vec;
unique_ptr<int> p(new int(5));
vec.push_back(std::move(p)); // 使用移动语义
}管理动态数组
1
2
3
4
5int main()
{
unique_ptr<int[]> p(new int[5] {1, 2, 3, 4, 5});
p[0] = 0; // 重载了 operator[]
}作为 auto_ptr 的替代品
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73#include <iostream>
#include <memory>
#include <stdlib.h>
struct Foo
{
Foo() { std::cout << "Foo::Foo\n"; }
~Foo() { std::cout << "Foo::~Foo\n"; }
void bar() { std::cout << "Foo::bar\n"; }
};
void f(const Foo &)
{
std::cout << "f(const Foo&)\n";
}
struct D
{
void operator()(Foo* foo)
{
std::cout << "D operator()" << std::endl;
delete foo;
}
};
void TestAutoDestroy()
{
//1. 普通的 new 对象。
std::cout << "TestDestroy...................." << std::endl;
{
std::unique_ptr<Foo> p1(new Foo);
}
//2. 普通的 new[] 对象。
{
std::unique_ptr<Foo[]> p2(new Foo[4]);
}
//3. 自定义的 deleter.
{
std::unique_ptr<Foo, D> p3(new Foo);
}
}
void TestOwner()
{
std::cout << "TestOwner...................." << std::endl;
//1. new object.
std::unique_ptr<Foo> p1(new Foo); // p1 owns Foo
if (p1) p1->bar();
{
std::unique_ptr<Foo> p2(std::move(p1)); // now p2 owns Foo
f(*p2);
p1 = std::move(p2); // ownership returns to p1
p2->bar();
std::cout << "destroying p2...\n";
}
p1->bar();
}
void TestArrayOwner()
{
std::cout << "TestArrayOwner...................." << std::endl;
//1. new[] object.
std::unique_ptr<Foo[]> p1(new Foo[4]); // p1 owns Foo
if (p1) p1[0].bar();
{
std::unique_ptr<Foo[]> p2(std::move(p1)); // now p2 owns Foo
f(p2[0]);
p1 = std::move(p2); // ownership returns to p1
p2[0].bar();
std::cout << "destroying p2...\n";
}
p1[0].bar();
}
int main()
{
TestAutoDestroy();
TestOwner();
TestArrayOwner();
}输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36TestDestroy....................
Foo::Foo
Foo::~Foo
Foo::Foo
Foo::Foo
Foo::Foo
Foo::Foo
Foo::~Foo
Foo::~Foo
Foo::~Foo
Foo::~Foo
Foo::Foo
D operator()
Foo::~Foo
TestOwner....................
Foo::Foo
Foo::bar
f(const Foo&)
Foo::bar
destroying p2...
Foo::bar
Foo::~Foo
TestArrayOwner....................
Foo::Foo
Foo::Foo
Foo::Foo
Foo::Foo
Foo::bar
f(const Foo&)
Foo::bar
destroying p2...
Foo::bar
Foo::~Foo
Foo::~Foo
Foo::~Foo
Foo::~Foo
std::shared_ptr
空的shared_ptr
指针,其初始引用计数为 0,而不是 1。
1 |
|
如上所示,p3 和 p4 都是 shared_ptr 类型的智能指针,因此可以用 p3 来初始化 p4,由于 p3 是左值,因此会调用拷贝构造函数。需要注意的是,如果 p3 为空智能指针,则 p4 也为空智能指针,其引用计数初始值为 0;反之,则表明 p4 和 p3 指向同一块堆内存,同时该堆空间的引用计数会加 1。
而对于std::move(p4)
来说,该函数会强制将 p4 转换成对应的右值,因此初始化 p5 调用的是移动构造函数。另外和调用拷贝构造函数不同,用std::move(p4)
初始化 p5,会使得 p5 拥有了 p4 的堆内存,而 p4 则变成了空智能指针。
同一普通指针不能同时为多个shared_ptr
对象赋值,否则会导致程序发生异常。
1 |
|
Function | Description |
---|---|
operator bool |
Return true if the stored pointer is not null. |
operator= |
Move assignment operator. |
operator* |
Dereference the stored pointer. |
operator-> |
Return the stored pointer. |
get |
Return the stored pointer. |
swap |
交换 2 个相同类型 shared_ptr 智能指针的内容。 |
reset |
形参为空,当前对象重置为空指针;当传入新申请的堆内存时,则获得该存储空间的所有权,引用计数的重置为 1。 |
use_count |
返回引用计数。 |
unique |
判断指向堆内存的 shared_ptr 对象是否唯一。 |
1 |
|
std::weak_ptr
shared_ptr
是采用引用计数的智能指针,多个shared_ptr
实例可以指向同一个动态对象,并维护了一个共享的引用计数器。对于引用计数法实现的计数,总是避免不了循环引用或环形引用的问题,shared_ptr
也不例外。
weak_ptr
是为了配合shared_ptr
而引入的一种智能指针,它指向一个由shared_ptr
管理的对象而不影响所指对象的生命周期,也就是将一个weak_ptr
绑定到一个shared_ptr
不会改变shared_ptr
的引用计数。不论是否有weak_ptr
指向,一旦最后一个指向对象的shared_ptr
被销毁,对象就会被释放。从这个角度看,weak_ptr
更像是shared_ptr
的一个助手而不是智能指针。
1 |
|
Function | Description |
---|---|
lock |
creates a shared_ptr that manages the referenced object |
- C++11 make_shared
- How do I call ::std::make_shared on a class with only protected or private constructors?
- std::weak_ptr<T>::lock
std::make_shared
C++11 引入
make_shared
与new
的区别:
std::shared_ptr<object> ptr = new object()
由于object
是先创建的,那么在将object
指针传给shared_ptr
时,shared_ptr
只能单独分配控制块,如下图:std::shared_ptr<object> ptr = std::make_shared<object>()
object
和对应的控制块是同时创建的,如下图:
std::make_unique
C++14 引入
3 多线程
1 |
|
std::thread
1 |
|
std::mutex
基本的互斥量。同一线程lock
多次会造成未定义的行为,不同线程lock
会阻塞,我们使用阻塞的特性。
std::timed_mutex
有超时机制的互斥量
std::recursive_mutex
可重入的互斥量
std::recursive_timed_mutex
结合timed_mutex
和recursive_mutex
特点的互斥量
std::shared_timed_mutex
具有超时机制的可共享互斥量
std::shared_mutex
共享的互斥量。底层实现是操作系统提供的读写锁。
lock
和unlock
分别用于获取写锁和解除写锁。lock_shared
和unlock_shared
分别用于获取读锁和解除读锁。
我们一般将写锁模式称为排他锁 (Exclusive Locking),将读锁模式称为共享锁 (Shared Locking)。
std::lock_guard
基于作用域的互斥量管理,构造加锁,析构解锁。
std::unique_lock
独占互斥量管理,C++11 引入。
通常与shared_mutex
配合使用,此对象构造时自动对shared_mutex
加写锁,析构时自动对shared_mutex
解写锁。
std::shared_lock
共享互斥量管理,C++14 引入。
通常与shared_mutex
配合使用,此对象构造时自动对shared_mutex
加读锁,析构时自动对shared_mutex
解读锁。
std::scoped_lock
多互斥量避免死锁的管理
std::condition_variable
使用此对象时,需要绑定一个lock_guard
或unique_lock
对象。
wait
wait_for
wait_until
该操作能够原子性的释放互斥量mutex
上的锁,并阻塞这个线程,当被通知后,解除当前阻塞线程,恢复互斥量mutex
上的锁。notify_one
notify_all
在持有锁的期间去唤醒阻塞线程。
1 |
|
std::async
1 |
|
Since C++11
4 算法
1 |
|
std::find_if
std::any_of
std::transform
1 |
|
std::move
1 |
|
1 |
|
- C++ 标准库使用比如
vector::push_back
等这类函数时,会对参数的对象进行复制,连数据也会复制。这就会造成对象内存的额外创建,本来原意是想把参数push_back
进去就行了,通过std::move
,可以避免不必要的拷贝操作。 std::move
是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝,所以可以提高利用效率,改善性能.。- 对指针类型的标准库对象并不需要这么做。
std::forward
1 |
|
forward
通常是用于完美转发的,它会将输入的参数原封不动地传递到下一个函数中,这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值。
1 |
|
引用折叠:
1 |
|
5 文件流
1 |
|
Flag | Description |
---|---|
std::ios::app |
append |
std::ios::ate |
at end |
std::ios::in |
read |
std::ios::out |
write |
std::ios::trunc |
clear |
std::fstream
该数据类型通常表示文件流,且同时具有ofstream
和ifstream
两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。
std::ifstream
该数据类型表示输入文件流,用于从文件读取信息。
std::ofstream
该数据类型表示输出文件流,用于创建文件并向文件写入信息。
6 其它
std::chrono
1 |
|
可用于统计程序运行的时间。