C++ STL 六组件介绍。
C++:编程语言
STL(Standard Template Library,标准模板库):C++ 标准库的一部分,提供相关工具(函数模板、类模板和函数等)
“函数模板” 和 “类模板” 是“抽象”概念,“模板函数” 和 “模板类” 是“具体”概念。
六组件:容器,算法,迭代器,适配器,仿函数,分配器
基础(常用) 三组件:容器,算法,迭代器
拓展(少用) 三组件:适配器,仿函数,分配器
顺序容器
关联容器
数组/向量
名称 | 底层原理 | 说明 |
---|---|---|
array | 静态数组 | C++11 标准 |
vector | 动态数组 |
链表
名称 | 底层原理 | 说明 |
---|---|---|
list | 双向链表 | |
forward_list | 单向链表 | C++11 标准 |
队列
名称 | 底层原理 | 说明 |
---|---|---|
deque | 管理数组+多个被管理数组 | |
queue | deque | 容器适配器 |
priority_queue | vector;堆(完全二叉树) | 容器适配器 |
栈
名称 | 底层原理 | 说明 |
---|---|---|
stack | deque | 容器适配器 |
图/映射
名称 | 底层原理 | 说明 |
---|---|---|
map | 红黑树 | |
multimap | 红黑树 | |
unordered_map | 哈希表 | C++11 标准 |
unordered_multimap | 哈希表 | C++11 标准 |
集合
名称 | 底层原理 | 说明 |
---|---|---|
set | 红黑树 | |
multiset | 红黑树 | |
unordered_set | 哈希表 | C++11 标准 |
unordered_multiset | 哈希表 | C++11 标准 |
对组
名称 | 底层原理 | 说明 |
---|---|---|
pair | 结构体struct |
元组
名称 | 底层原理 | 说明 |
---|---|---|
tuple | 递归继承类 | C++11 标准 |
总表
名称 | 底层原理 | 说明 |
---|---|---|
array | 静态数组 | C++11 标准 |
vector | 动态数组 | |
list | 双向链表 | |
forward_list | 单向链表 | C++11 标准 |
deque | 管理数组+多个被管理数组 | |
queue | deque | 容器适配器 |
priority_queue | vector;堆(完全二叉树) | |
stack | deque | 容器适配器 |
map | 红黑树 | |
multimap | 红黑树 | |
unordered_map | 哈希表 | C++11 标准 |
unordered_multimap | 哈希表 | C++11 标准 |
set | 红黑树 | |
multiset | 红黑树 | |
unordered_set | 哈希表 | C++11 标准 |
unordered_multiset | 哈希表 | C++11 标准 |
pair | 结构体 struct | |
tuple | 递归继承类 | C++11 标准 |
#include // vector<>
#include
using std::cout;
using std::endl;
using std::vector;
int main()
{
// 容器
vector<int> vec{0, 1, 2}; // 存储“0、1和2”3个数据
cout << vec.at(0) << endl; // 访问索引“0”的数据“0”
return 0;
};
// 输出:0
这里只列举部分算法,更多参见:算法库 - cppreference.com
不修改序列的操作
修改序列的操作
划分操作
排序操作
二分搜索操作(在已排序范围上)
其他已排序范围上的操作
集合操作(在已排序范围上)
堆操作
最小/最大操作
比较操作
排列操作
数值运算
未初始化内存上的操作
C 库
#include
// #include // max()
using std::cout;
using std::endl;
int main()
{
int num1 = 1;
int num2 = 2;
// 算法
cout << std::max(num1, num2) << endl; // 获取最大值
return 0;
};
// 输出:2
类型
输入迭代器
将“流”看作是一种容器,迭代器可以作用于它。
#include
#include
using std::cin;
using std::cout;
using std::endl;
using std::istream_iterator;
int main()
{
// 输入迭代器
istream_iterator<int> input_it(cin);
int num = *input_it; // 注意:“*”解引用操作就是输入操作
cout << num << endl;
++input_it; // 迭代器需要“前进”偏移,否则会指向同一个“位置”
num = *input_it;
cout << num << endl;
return 0;
}
// 输入:123
// 输出:123
// 输入:456
// 输出:456
输出迭代器
#include
#include
using std::cout;
using std::ostream_iterator;
int main()
{
// 输出迭代器
// ostream_iterator output_it(cout);
ostream_iterator<int> output_it(cout, " "); // 第二个参数:每数据的分隔符
int num = 123;
*output_it = num; // 注意:“*”解引用操作就是输出操作
num = 456;
*output_it = num;
return 0;
}
// 输出:123 456
前向迭代器
“前进”指“增加”即“++/+1”操作,“后退”指“减少”即“–/-1”操作。
#include
// #include
#include
using std::cout;
using std::endl;
using std::forward_list;
int main()
{
forward_list<int> for_list{0, 1, 2};
// 前向迭代器
forward_list<int>::const_iterator it_beg = for_list.begin(); // 数据“0”的“位置”
forward_list<int>::const_iterator it_end = for_list.end(); // 数据“2”的后一个“位置”
for (forward_list<int>::const_iterator it = it_beg; it != it_end; ++it)
{
cout << *it << " ";
}
cout << endl;
return 0;
}
// 输出:0 1 2
双向迭代器
支持逐个前进,支持逐个后退
支持多次遍历
没有特定的类模板, 是 符合双向迭代器特性的 容器 对应的 迭代器的 类型(如 list<> ,其底层原理是双向链表,支持单向前进顺序访问,支持反向后退顺序访问,不支持随机访问,是 符合双向迭代器特性的 容器,其对应的 迭代器的 类型 是双向迭代器)
#include
// #include
#include
using std::cout;
using std::endl;
using std::list;
int main()
{
list<int> li = {0, 1, 2};
// 双向迭代器
list<int>::const_iterator it_beg = li.begin(); // 数据“0”的“位置”
list<int>::const_iterator it_end = li.end(); // 数据“2”的后一个“位置”
for (list<int>::const_iterator it = it_beg; it != it_end; ++it) // 前进遍历
{
cout << *it << " ";
}
cout << endl;
list<int>::const_reverse_iterator it_rbeg = li.rbegin(); // 数据“2”的“位置”
list<int>::const_reverse_iterator it_rend = li.rend(); // 数据“0”的前一个“位置”
for (list<int>::const_reverse_iterator it = it_rbeg; it != it_rend; ++it) // 后退遍历
{
cout << *it << " ";
}
cout << endl;
return 0;
}
// 输出:0 1 2
// 输出:2 1 0
随机访问迭代器
#include
// #include
#include
using std::cout;
using std::endl;
using std::vector;
int main()
{
vector<int> vec = {0, 1, 2};
// 随机访问迭代器
vector<int>::const_iterator it_beg = vec.begin(); // 数据“0”的“位置”
cout << *(it_beg + 2) << endl; // 数据“2”的“位置” 随机访问
return 0;
}
// 输出:2
连续迭代器
#include
// #include
#include
using std::cout;
using std::endl;
using std::vector;
int main()
{
vector<int> vec = {0, 1, 2};
// 连续迭代器
vector<int>::const_iterator it_beg = vec.begin(); // 数据“0”的“位置”
cout << *(it_beg + 2) << endl; // 数据“2”的“位置” 随机访问
return 0;
}
// 输出:2
数组/向量
容器 | 对应的迭代器的类型 |
---|---|
array | 随机访问迭代器 |
vector | 随机访问迭代器 |
链表
容器 | 对应的迭代器的类型 |
---|---|
list | 双向迭代器 |
forward_list | 前向迭代器 |
栈
容器 | 对应的迭代器的类型 |
---|---|
stack | 不支持迭代器 |
队列
容器 | 对应的迭代器的类型 |
---|---|
deque | 随机访问迭代器 |
queue | 不支持迭代器 |
priority_queue | 不支持迭代器 |
图/映射
容器 | 对应的迭代器的类型 |
---|---|
map | 双向迭代器 |
multimap | 双向迭代器 |
unordered_map | 前向迭代器 |
unordered_multimap | 前向迭代器 |
集合
容器 | 对应的迭代器的类型 |
---|---|
set | 双向迭代器 |
multiset | 双向迭代器 |
unordered_set | 前向迭代器 |
unordered_multiset | 前向迭代器 |
对组
容器 | 对应的迭代器的类型 |
---|---|
pair | 不支持迭代器 |
元组
容器 | 对应的迭代器类型 |
---|---|
tuple | 不支持迭代器 |
总表
容器 | 对应的迭代器的类型 |
---|---|
array | 随机访问迭代器 |
vector | 随机访问迭代器 |
list | 双向迭代器 |
forward_list | 前向迭代器 |
stack | 不支持迭代器 |
deque | 随机访问迭代器 |
queue | 不支持迭代器 |
priority_queue | 不支持迭代器 |
map | 双向迭代器 |
multimap | 双向迭代器 |
unordered_map | 前向迭代器 |
unordered_multimap | 前向迭代器 |
set | 双向迭代器 |
multiset | 双向迭代器 |
unordered_set | 前向迭代器 |
unordered_multiset | 前向迭代器 |
pair | 不支持迭代器 |
tuple | 不支持迭代器 |
#include // vector<>
#include // find()
#include
using std::cout;
using std::endl;
using std::vector;
int main()
{
// 容器
vector<int> vec{0, 1, 2}; // 有“0、1和2”3个数据
// 迭代器
vector<int>::const_iterator it_beg = vec.begin(); // 数据“0”的“位置”
vector<int>::const_iterator it_end = vec.end(); // 数据“2”的后一个“位置”
vector<int>::const_iterator it_res = vec.end();
// 算法
it_res = std::find(it_beg, it_end, 1); // 查找数据“1”
if (it_res != it_end)
{
cout << "查找到数据" << endl;
}
else
{
cout << "未查找到数据" << endl;
}
return 0;
};
// 输出:查找到数据
类型
容器适配器
#include // stack<>
#include
using std::cout;
using std::endl;
using std::stack;
int main()
{
// 容器适配器
stack<int> st{};
st.push(0); // 入栈
st.push(1);
cout << st.top() << endl; // 获取栈顶数据
st.pop(); // 出栈
cout << st.top() << endl;
return 0;
}
// 输出:1
// 输出:0
迭代器适配器
注意:“迭代器”和“迭代器适配器”的类型中有重叠的类型。如:stream_iterator<>,从“输入”的角度, 是“迭代器”的“输入迭代器”类型;从“流”的角度,是“迭代器适配器”的“流迭代器”类型。
一般不区分容器和容器适配器,迭代器和迭代器适配器。
代码示例见“迭代器”内容。
函数(普通函数、类方法、 仿函数和 Lambda 表达式等) 适配器
注意:很多函数适配器已不推荐/过时/弃用:bind1st()、std::bind2nd() 和 mem_fun() 等,请注意甄别
#include // bind(),placeholders
#include
using std::cout;
using std::endl;
int add(int num1, int num2) // add()
{
return num1 + num2;
}
int main()
{
// 函数适配器
auto add_bind = std::bind(add, 1, std::placeholders::_1); // 绑定 add() 的第一个参数为数据“1”
int result = add_bind(2); // 调用 add_bind(x),实际上是调用 add(1, x)
cout << result << endl;
return 0;
}
// 输出:3
#include // function<>
#include
using std::cout;
using std::endl;
int add(int num1, int num2) // add()
{
return num1 + num2;
}
int main()
{
// 函数适配器
std::function<int(int, int)> add_func = add; // 封装 add()
int result = add_func(1, 2); // 调用 add_func(x, y),实际上是调用 add(x, y)
cout << result << endl;
return 0;
}
// 输出:3
适配器
一般使用
#include
using std::cout;
using std::endl;
// 仿函数类
class Add_functor // add()
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
int main()
{
// 仿函数(对象)
Add_functor add_functor;
int result = add_functor(1, 2);
cout << result << endl;
return 0;
}
// 输出:3
算法使用
#include
#include
#include
using std::cout;
using std::endl;
using std::vector;
// 仿函数类
class Comp
{
public:
inline bool operator()(const int &num1, const int &num2) const
{
return num1 > num2; // 左数比右数大则返回 true
}
};
int main()
{
vector<int> vec{0, 1, 2};
// 仿函数(对象)
Comp comp;
std::sort(vec.begin(), vec.end(), comp); // std::sort() 默认从小到大排序,使用仿函数(对象)定制从大到小排序
for (const int &v : vec)
{
cout << v << " ";
}
cout << endl;
return 0;
}
// 输出:2 1 0
函数指针:指向函数的指针
参见:为什么要用仿函数?_为什么要把函数写成仿函数-CSDN博客
一般类型(内置类型和自定义类型等)内存分配(在堆上的动态内存分配)不推荐使用分配器(难用),容器分配内存可以使用分配器(少用,有需求才用)
分配器本质: 将 1. 内存分配和对象构造 和 2. 对象析构和内存释放 两个过程 拆分成 1. 内存分配 2. 对象构造 3. 对象析构 4. 内存释放 四个过程,通过定制内存管理策略提高灵活性和效率
一般类型(内置类型和自定义类型等)
#include // allocator<>
#include
using std::allocator;
using std::cout;
using std::endl;
int main()
{
// 分配器
allocator<int> allo;
// 1. 内存分配
int *int_ptr = allo.allocate(1); // 分配1个原始的/未初始化的 int 类型数据的内存
// 2. 对象构造
allo.construct(int_ptr, 3); // 构造对象数据为“3”
cout << *int_ptr << endl;
// 3. 对象析构
allo.destroy(int_ptr); // 析构对象数据
// 4. 内存释放
allo.deallocate(int_ptr, 1); // 释放1个原始的/未初始化的 int 类型数据的内存
return 0;
}
// 输出:3
容器类型
// vector<> 的声明
template <class T, class Allocator = std::allocator<T>>
class vector;
#include
#include
// #include
// (自定义)分配器
template <typename T>
class CustomAllocator
{
public:
// 容器的设计要求:分配器必须提供一个“value_type”的类型别名,以便容器使用分配器时知道数据的类型
using value_type = T;
// 构造
CustomAllocator() noexcept {}
// 内存分配
// 可能失败,不声明为 noexcept
T *allocate(std::size_t n)
{
std::cout << "Allocating memory for " << n << " objects." << std::endl; // 定制化体现
return std::allocator<T>{}.allocate(n); // 使用标准分配器分配内存
}
// 内存释放
void deallocate(T *p, std::size_t n) noexcept
{
std::cout << "Deallocating memory for " << n << " objects." << std::endl;
std::allocator<T>{}.deallocate(p, n); // 使用标准分配器释放内存
}
// 对象构造
// 可能失败,不声明为 noexcept
// typename...:声名可变参数模板
// Args:类型参数包的名称;包含零或多个类型参数;指的是类型
// args:值参数包的名称;包含零或多个值参数;指的是值
// Args:可变参数的类型
// &&:(右值引用)保留传递给函数的 参数的 值的 特性(常量/引用左值/右值)
// ...:折叠运算符(在参数左边)
// args:可变参数的值
// new:new 运算符:1. 内存分配 2. 对象构造
// new:placement new 运算符:对象构造
// placement new 运算符需要一个 void* 参数,以指定在哪个内存地址上构造对象
// forward<>():完美转发:1. 类型(Args) 2. 值的特性(&&) 3. 值(args)
// ...:展开运算符(在参数右边)
// (args...):展开所有参数并作为 forward<>() 的参数 ×
// (args)...:分别展开所有参数并分别作为 forward<>() 的参数 √
// 使用搭配
// malloc() - free()
// 构造 - 析构
// new - delete
// 内存分配 - placement new - 析构 - delete
template <typename U, typename... Args>
void construct(U *p, Args &&...args)
{
std::cout << "Constructing object." << std::endl;
new ((void *)p) U(std::forward<Args>(args)...); // 使用对象的构造函数构造对象
}
// 析构函数,用于在分配的内存上销毁对象
// 没有 placement detele 运算符
template <typename U>
void destroy(U *p)
{
std::cout << "Destroying object." << std::endl;
p->~U(); // 使用对象的析构函数析构对象
}
};
int main()
{
// (自定义)分配器
std::vector<int, CustomAllocator<int>> vec;
// 1. 内存分配
// 2. 对象构造
// 3. 对象析构
// 4. 内存释放
for (int i = 0; i < 2; ++i)
{
vec.push_back(i);
}
return 0;
}
/* 输出:
Allocating memory for 1 objects.
Constructing object.
Allocating memory for 2 objects.
Constructing object.
Constructing object.
Destroying object.
Deallocating memory for 1 objects.
Destroying object.
Destroying object.
Deallocating memory for 2 objects.
*/
/* 逐行分析:
1.1 第一个容器内存分配
1.2 第一个容器第一个对象构造
2.1 第二个容器内存分配(容器扩容)
2.2 第二个容器第一个对象构造
2.2 第二个容器第二个对象构造
1.3 第一个容器第一个对象析构
1.4 第一个容器内存释放
2.3 第二个容器第二个对象析构
2.3 第二个容器第一个对象析构
2.4 第二个容器内存释放
*/
分配器本质: 将 1. 内存分配和对象构造 和 2. 对象析构和内存释放 两个过程 拆分成 1. 内存分配 2. 对象构造 3. 对象析构 4. 内存释放 四个过程,通过定制内存管理策略提高灵活性和效率
参见:C++:allocator 学习整理 - 简书 (jianshu.com)
C++ STL 六组件介绍。
C++ STL基本组成(6大组件+13个头文件) (biancheng.net)
C++的基于对象编程范式、常用STL容器和C++11标准_基于对象的编程范式_夜悊的博客-CSDN博客
cppreference.com
【STL十三】适配器——迭代器适配器_郑同学的笔记的博客-CSDN博客
C++ STL:适配器 - 知乎 (zhihu.com)
C++ STL迭代器适配器完全攻略 (biancheng.net)
为什么要用仿函数?_为什么要把函数写成仿函数-CSDN博客
C++:allocator 学习整理 - 简书 (jianshu.com)
C/C++内存申请和释放(二) - 知乎 (zhihu.com)