跳转至

Standard Template Library

Introduction

标准模板库(Standard Template Library,STL) 是C++标准库的核心组成部分,提供了一系列通用的数据结构和算法模板,极大地提高了代码复用性和开发效率。STL基于泛型编程思想,主要包含以下四大组件:

  • 容器(Containers):提供了存储和管理数据的模板,主要有序列式,关联式
  • 迭代器(Iterators):提供统一的访问容器元素的接口,类似于指针
  • 函数对象(Functors):包含各类函数对象、适配器
  • 算法(Algorithms):提供了约100+个通用算法,通过迭代器操作容器

Containers

各类容器简介

有序容器

容器名称 数据结构 特点 底层实现
vector 动态数组 随机访问快,尾部插入高效,中间插入慢 连续内存数组
list 双向链表 插入/删除高效,不支持随机访问,内存不连续 双向链表节点
forward_list 单向链表 仅前向遍历,更省内存,插入/删除快 单向链表节点
deque 双端队列 头尾插入高效,随机访问较快,内存分段连续 多个固定大小数组块 + 索引表
array 静态数组 固定大小,栈内存分配,支持随机访问 连续内存数组(编译时确定大小)

关联性容器

使用multiset/map来允许重复键;排序性容器要求元素可比,哈希型容器要求元素可哈希;

容器名称 数据结构 特点 底层实现
set 有序集合 自动排序,唯一元素,查找高效 红黑树(平衡二叉搜索树)
map 有序键值对 键唯一,按键排序,查找高效 红黑树(存储pair对象)
unordered_set 无序集合 哈希表实现,查找最快,元素无序 哈希表(链地址法)
unordered_map 无序键值对 哈希表实现,键唯一,查找高效 哈希表(数组+链表/红黑树)

容器适配器

容器名称 数据结构 特点 底层实现
stack 栈 LIFO 后进先出,限制顶部操作 默认基于 deque 实现
queue 队列 FIFO 先进先出,限制两端操作 默认基于 deque 实现
priority_queue 优先队列 元素按优先级排序,总返回最大值/最小值 vector + 堆算法

容器使用

容器初始化一般使用std::container< T > {value1...}/(object);,通过模版机制,容器支持不同类型的对象

对于遍历,大部分容器,我们迭代器,或者const auto& item : container进行元素的遍历

此外,大部分容器有其调用函数,包括数据访问、增删,底层地址获取等。

  • 初始化可以使用()传参或者=赋值,一般使用{}, {{}}的临时数组,也可以深拷贝已有对象
  • 数据访问一般可以使用[]和at(后者会检查越界),同时使用.empty判断是否为空
  • 数据操作中,插入一般有.insert(), .push(), .push_back(),删除则往往可以使用.erase()
  • 容器的循环和算法调用都可以使用迭代器作为统一接口

基于不同的底层实现,容器自动的进行扩容、数据迁移。

有序容器使用——以Vector为例:

using namespace std;

/*======初始化======*/
// 方式1:默认构造(空vector)
vector<int> v1;
// 方式2:列表初始化
vector<int> v2 = {1, 2, 3, 4, 5};
// 方式5:指定大小n和默认值e
vector<int> v3(n, e); 

/*======数据访问和操作======*/
int first = vec[0];    // 不检查越界
int second = vec.at(1); // 会检查越界,抛出out_of_range异常
int* ptr = vec.data(); //访问底层数据

vec.push_back(4); // 尾部插入 
vec.pop_back(); 

vec.empty;  //是否为空
vec.clear(); //清空向量

/*======遍历======*/
// 方法1:C++11 范围for循环
for (const auto& item : vec) { //使用const auto&,防止复制消耗和修改风险
    cout << item << " ";
}
// 方法2:迭代器
for (vector<string>::iterator it = vec.begin(); it != fruits.end(); ++it) {
    cout << *it << " ";   //迭代器需要"*"来访问其值
}
// 方法3:下标访问
for (size_t i = 0; i < vec.size(); ++i) { // 使用size_t避免符号不匹配
    cout << vec[i] << " ";
}

无序容器使用——以map为例(实际上存储了大量pair<const K, V>

/*======初始化======*/
// 方式1:默认构造空map
map<int, string> m1;
// 方式2:列表初始化(C++11+)
map<int, string> m2 = {{1, "Alice"},{2, "Bob"}};
// 方式3:拷贝构造
map<int, string> m3(m2); 

/*======遍历======*/
// 方法1:迭代器,略
// 方法2:C++11 范围for循环
for (const auto& pair : m2) {
    cout << "[" << pair.first << "]=" << pair.second << " ";
}
// 方法3:C++17 结构化绑定
for (const auto& [key, val] : m2) { 
    cout << key << "->" << val << endl;
}

/*======查找======*/
// 方法0:m.count(k)进行计数,m.contains(k)判断存在性
// 方式1:operator[](注意:不存在时会创建默认值)
cout << "m2[2] = " << m2[2] << endl;      // 存在时访问

// 方式2:at()方法(推荐安全访问,不存在抛出out_of_range异常)
try {
    cout << "m2.at(3) = " << m2.at(3) << endl; 
} catch (const exception& e) {
    cout << "异常:" << e.what() << endl;
}

// 方式3:find查找(安全访问,返回*迭代器*)
auto it = m2.find(2);

//增删操作都可以用迭代器实现,具体细节在迭代器章节讲解
/*======插入======*/
// 方法1:insert直接插入pair
m1.insert(pair<int, string>(10, "Ten"));
m1.insert({k, v})
// 方法2:insert_or_assign(C++17+)
m1.insert_or_assign(20, "New Twenty");
// 方法3:使用operator[]插入
m1[40] = "Forty"; // 直接赋值插入

/*======删除======*/
// 方法1:erase通过键删除
size_t count = m2.erase(4); // 返回删除数量
// 方法2:清空整个map
m5.clear();

Iterators

对于STL的大部分容器,其都有对应的迭代器,用于遍历里面所有的元素

迭代器的使用和数组指针非常相似,可以进行向前,可以取值——同时还有很多支持向后、修改值、随机访问

其中.begin()提供了首个元素的接口,.end()提供比较是否结束的接口

std::vector<int> v {1,2,3,4,5};
auto it = v.begin(); //迭代器的类型为 std::container<T>::iterator, 一般使用auto即可
it += 2;       //向量迭代器具有随机访问能力
++it;        //迭代器一般使用前缀++     
if (it == --v.end()){...}

迭代器具体的集合分类和对应的典型容器如下:

image-20250318160608956

Functors

函数对象

在STL里面,定义了许多函数对象——它们是类,但通过重载,也有类似函数的调用接口,如

template <typename T>
struct std::greater {
    bool operator()(const T& a, const T& b) const {
        return a > b;
    }
};

由于函数类也是一个对象,所以它也可以含有内置的状态!

lambda

当我们需要在局部快速定义匿名函数对象时,可以使用 Lambda 表达式,基本语法:[capture](params) -> ret_type { body },其中返回类型可以省略,捕捉对象可以为空

lambda函数相当于由编译器解析并插入的一段函数结构体——而其捕捉对象就是他的内置状态

int x = 10;
auto func = [x, &y](int z) {  // 值捕获 x,引用捕获 y
    y = x + z; 
    return y * 2;
};

auto counter = [n=0]() mutable { return n++; };  // 如果需要修改捕获变量,使用mutable

常用的捕捉方式如下:

捕获方式 语法示例 作用 注意事项
不捕获 [] 不捕获任何外部变量 只能使用 Lambda 参数和全局变量
混合捕获 [x, &y] 组合值捕获和引用捕获 可精确控制捕获方式
包展开捕获 [args...]``[&x] 捕获参数包(C++14)捕获外部变量 x 的引用 需确保被引对象生命周期长于 Lambda
全部引用捕获 [&] 捕获所有外部变量的引用 容易引发悬垂引用风险
忽略未使用变量 [=, &x] 默认值捕获,但 x 单独引用捕获 显式覆盖默认捕获规则