C++ STL 学习笔记
本笔记部分内容来自 c++ 经典教程《C++ Primer Plus》
STL(Standard Template Library) 标准模板库中有 6 大组件。
- 容器(containers):与数组类似的单元,可以储存若干值。
- 算法(algorithms):完成特定任务的方法。
- 迭代器(iterators):与数组的指针类似,可以遍历容器的对象。
- 仿函数(functors):类似函数的对象,类对象或函数指针。
- 适配器(adapters):提供容器不同的接口。
- 空间配置器(allocator):容器分配内存和回收内存。
容器(containers)
容器是与数组类似的单元,可以储存若干值。一般认为,STL中有三类容器。
- 顺序容器
- 关联容器
- 无序关联容器
顺序容器
顺序容器实现能按顺序访问的数据结构。
array 静态的连续数组
定义于头文件 <array>
std::array
是封装固定大小数组的容器,可以看作一个不会退化成指针的数组。它有一个非静态数据成员内建数组T[N]
,因此 array 结合了内建数组的性能、可访问性与容器的优点,比如可获取大小、支持赋值、随机访问迭代器等。
成员函数
隐式定义的成员函数
- 构造函数
- 析构函数
- 复制构造函数
元素访问
at
访问指定的元素,同时进行越界检查operator[]
访问指定的元素front
访问第一个元素back
访问最后一个元素data
返回指向内存中数组第一个元素的指针#include <iostream> #include <array> int main() { std::array<int, 6> data = {1, 2, 4, 5, 5, 6}; auto data2 = data; data.at(1) = 88; // data 中的 2 改为 88 data[0] = 78; // data 中的 1 改为 78 try { data.at(6)++; // 抛出 out_of_range } catch (std::out_of_range const& exc) { std::cout << exc.what() << '\n'; // array::at: __n (which is 6) >= _Nm (which is 6) } data.front() += data.back(); int* ptr = data.data(); return 0; }
容量
empty
检查容器是否为空(在 array 中一般不使用)size
返回容纳的元素数max_size
返回可容纳的最大元素数(在 array 中与 size 作用相同)
操作
fill
以指定值填充容器(覆盖填充)swap
交换内容(交换大小与类型完全相同的数组内容)#include <iostream> #include <array> int main() { std::array<int, 10> data = {1, 2, 3, 4, 5, 6}; std::cout << (data.size() == data.max_size())<< '\n'; // 1 data.fill(7); for (const auto& i : data) { std::cout << i << ' '; // 7 7 7 7 7 7 7 7 7 7 } std::cout << '\n'; std::array<int, 10> data2; if (!data2.empty()) { // 除非在定义时指定数组大小为 0,否则 empty 永远为 false std::cout << data2.size() << '\n'; // 10 } data2.fill(1); data.swap(data2); // 交换两个同类型数组(元素类型和大小) for (const auto& i : data) { std::cout << i << ' '; // 1 1 1 1 1 1 1 1 1 1 } std::cout << '\n'; return 0; }
迭代器
迭代器类似指针。
begin
返回指向起始的迭代器end
返回指向末尾的迭代器rbegin
返回指向起始的逆向迭代器rend
返回指向末尾的逆向迭代器cbegin
cend
crbegin
crend
只读迭代器#include <iostream> #include <array> int main() { std::array<int, 5> data = {1, 2, 3, 4, 5}; for (auto i = data.begin(); i != data.end(); ++i) { std::cout << *i << ' '; // 1 2 3 4 5 } std::cout << '\n'; for (auto i = data.rbegin(); i != data.rend(); ++i) { std::cout << *i << ' '; // 5 4 3 2 1 } std::cout << '\n'; auto i = data.cbegin(); // 推导出 const 类型,元素只读 return 0; }
非成员函数
operator==
按字典序比较字典中的值operator!=
按字典序比较字典中的值operator<
按字典序比较字典中的值operator<=
按字典序比较字典中的值operator>
按字典序比较字典中的值operator>=
按字典序比较字典中的值std::get(std::array)
访问数组中的一个元素std::swap(std::array)
交换数组的元素to_array
(c++20) 将内建数组转为数组对象
vector 变长数组
vector 不像它的名字描述那样,它一点都不像向量,我觉得把它叫做变长数组更贴切一些。
vector 的存储是自动管理的,按需扩张收缩。 vector 通常占用多于静态数组的空间,因为要分配更多内存以管理将来的增长。 vector 所用的方式不在每次插入元素时,而只在额外内存耗尽时重分配。分配的内存总量可用 capacity()
函数查询。额外内存可通过对 shrink_to_fit()
的调用返回给系统。
vector 是非常常用的一类容器,它的相关操作时间复杂度如下:
- 随机访问 O(1)
- 末尾插入或移除元素 O(1)
- 插入或移除元素 O(n) n 为到末尾的距离
特化
std::vector
对类型 bool
有特化,以节省空间。
成员函数(与 array 不同)
容量
reserve
预留储存空间capacity
返回当前储存空间能够容纳的元素数shrink_to_fit
通过释放未使用的内存减少内存的使用
修改器
clear
清除内容insert
插入元素emplace
原位构造函数erase
擦除元素push_back
将元素添加到容器末尾resize
改变容器中可存储元素的个数
修改器中 insert
和 emplace
都是插入元素,但是 emplace
在某些情况不会产生临时对象。
在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。
#include <cmath> const double&& PI = acos(-1.0); // 3.14159265358... using namespace std; int main() { int a = 3; // 可行,定义 a = 1 // int &b = 1; // 报错,1 是右值,b 是左值引用 int &&c = 1; // 可行,1 是右值,c 是右值引用 int &d = a; // 可行,a 是左值,d 是左值引用 // int &&e = a; // 报错,a 是左值,e 是右值引用 int &f = c; // 可行,c 是左值,f 是左值引用 // int &&g = c; // 报错,c 是左值,g 是右值引用 int &h = d; // 可行,d 是左值,h 是左值引用 // int &&i = d; // 报错,d 是左值,i 是右值引用 }
#include <iostream> #include <string> #include <vector> struct A { std::string s; A(std::string str) : s(std::move(str)) { std::cout << " constructed\n"; } A(const A& o) : s(o.s) { std::cout << " copy constructed\n"; } A(A&& o) : s(std::move(o.s)) { std::cout << " move constructed\n"; } A& operator=(const A& other) { s = other.s; std::cout << " copy assigned\n"; return *this; } A& operator=(A&& other) { s = std::move(other.s); std::cout << " move assigned\n"; return *this; } }; int main() { std::vector<A> container; // 预留足够的空间以使 vector 不必重设大小 container.reserve(10); std::cout << "construct 2 times A:\n"; A two { "two" }; A three { "three" }; std::cout << "emplace:\n"; container.emplace(container.end(), "one"); std::cout << "emplace with A&:\n"; container.emplace(container.end(), two); std::cout << "emplace with A&&:\n"; container.emplace(container.end(), std::move(three)); std::cout << "content:\n"; for (const auto& obj : container) std::cout << ' ' << obj.s; std::cout << '\n'; }
assign
将值赋给容器get_allocator
返回相关的分配器
list 双向链表
双向链表是一个常用的数据结构,适合用来进行大量的插入删除操作,但是随机读写性能较差,与 vector
容器互补。
修改器
push_head
向前插入一个元素emplace_head
向前插入一个元素pop_head
删除前面一个函数merge
归并两个已排序双向链表
#include <iostream> #include <list> std::ostream& operator<<(std::ostream& ostr, const std::list<int>& list) { for (auto &i : list) { ostr << " " << i; } return ostr; } int main() { std::list<int> list1 = { 5,9,0,1,3 }; std::list<int> list2 = { 8,7,2,6,4 }; list1.sort(); list2.sort(); std::cout << "list1: " << list1 << "\n"; // list1: 0 1 3 5 9 std::cout << "list2: " << list2 << "\n"; // list2: 2 4 6 7 8 list1.merge(list2); std::cout << "merged: " << list1 << "\n"; // merged: 0 1 2 3 4 5 6 7 8 9 }
链表操作
splice
从另一个list
中移动元素remove
移除满足特定标准的元素remove_if
移除满足特定标准的元素reverse
将该链表的所有元素的顺序反转unique
删除连续的重复元素sort
对元素进行排序
#include <iostream> #include <list> std::ostream& operator<<(std::ostream& ostr, const std::list<int>& list) { for (auto &i : list) { ostr << " " << i; } return ostr; } int main () { std::list<int> list1 = { 1, 2, 3, 4, 5 }; std::list<int> list2 = { 10, 20, 30, 40, 50 }; auto it = list1.begin(); std::advance(it, 2); list1.splice(it, list2); std::cout << "list1: " << list1 << "\n"; // list1: 1 2 10 20 30 40 50 3 4 5 std::cout << "list2: " << list2 << "\n"; // list2: list2.splice(list2.begin(), list1, it, list1.end()); std::cout << "list1: " << list1 << "\n"; // list1: 1 2 10 20 30 40 50 std::cout << "list2: " << list2 << "\n"; // list2: 3 4 5 }
关联容器
map 映射
map
是有序键值对容器,它的元素的键是唯一的。用比较函数 Compare
排序键。搜索、移除和插入操作拥有对数复杂度。 map 通常实现为红黑树。
成员类
value_compare
比较键类型的对象
#include <iostream> #include <map> class key_com { public: bool operator() (const int& a, const int& b) { return a < b; } }; int main() { std::map<int, int, key_com> m{{1, 1}, {2, 4}, {4, 16}}; for(const auto& i : m) { std::cout << i.first << ": " << i.second << std::endl; } // 1: 1 // 2: 4 // 4: 16 m.insert({3, 9}); for(const auto& i : m) { std::cout << i.first << ": " << i.second << std::endl; } // 1: 1 // 2: 4 // 3: 9 // 4: 16 }
查找
count
find
equal_range
lower_bound
upper_bound