前几天谈论了许多关于数论和数据结构的东西,这些内容可能对初学者而言比较晦涩难懂(毕竟是属于初高等算法/数据结构的范畴了)。今天打算来讲一些简单的内容 - STL 标准模板库。

STL 标准模板库

C++ 标准模板库 (Standard Template Library, STL),是 C++ 语言非常重要的一个构成部分。它提供了一组通用的类和函数,用于实现一些数据结构和算法。STL 主要包含以下五个部分:

  1. 容器 (Containers):STL 提供了不同的容器来供开发者根据所需要维护数据来选择存储。
  2. 算法 (Algorithms):STL 提供了许多通用算法,用于排序、搜索、复制、修改等操作。
  3. 迭代器 (Iterators):STL 迭代器的作用是遍历容器元素的对象。
  4. 函数对象 (Function Objects):这是一种行为类似于函数的对象(由于不太常见,因此本篇文章将不会详细讲解 STL 中的函数对象)。
  5. 适配器 (Adapters):STL 的适配器用于修改容器、迭代器或函数对象的接口。为方便起见,容器适配器将会与容器部分一同讲解。

综上所述,本文将会主要围绕 容器、算法和迭代器这三者来展开叙述。本文只会阐述一些相对常见的模板/方法,部分生僻的内容还请各位自行上网搜索。

容器 Containers

STL 容器还可以被细分成三个类别,分别是 序列式容器 (Sequence Containers)关联式容器 (Associative Containers)无序关联式容器 (Unordered Associative Containers)

序列式容器 (Sequence Containers) 与常见容器适配器。

  1. vector 向量容器:向量容器可以被理解为我们常说的动态数组。与普通数组不同的是,动态数组的大小可以在运行过程中更改,并且用户可以任意地在此数组的末尾添加数据。它的优点是支持快速随机访问和在末尾插入删除元素。向量容器的基本使用方法如下:
#include <iostream>
#include <vector> // 引入头文件
using namespace std; int main(){
// 创建一个整数类型,名为 vec 的向量容器,初始存放了数字1-5。
vector<int> vec = {1, 2, 3, 4, 5}; vec.push_back(6); // 向容器末尾添加一个元素6。
vec.size(); // 获取容器的大小,即容器内元素的个数。
vec.resize(10); // 动态更改容器的大小,将容器的大小设置为10。 // 通过 for 循环对容器进行遍历:
for (int i=0; i<vec.size(); i++){}
for (int i : vec) {}
return 0;
}
  1. queue 队列容器适配器:队列是一种基本的数据结构,其讲究 先进先出 (FIFO),即最先添加进适配器的元素会被第一个弹出,以此类推。在队列中,所有元素都会从队尾入队,并从对首出队。队列适配器的优点是支持随机访问队首和队尾元素。队列容器严格意义上并非序列式容器,而是一种容器适配器。队列容器适配器的基本使用方法如下:
#include <iostream>
#include <queue> // 引入头文件
using namespace std; int main(){
// 创建一个存放整数类型,名为 que 的队列容器适配器,一开始默认为空。
queue<int> que; que.push(10); // 向队尾添加一个新元素。
que.front(); // 获取队首元素,此时队首元素为数字10。
que.pop(); // 将队首元素出队,此时的队列容器为空。
que.size(); // 获取队列的长度,即对内元素的个数。
que.empty(); // 判断队列是否为空,为空返回真,否则返回假。 // 队列元素不支持直接遍历,只能从头一个一个弹出。
// 在弹出元素时需要保证队内不为空。
while (!que.empty()){
int t = que.front();
que.pop();
cout << t << endl;
}
return 0;
}
  1. stack 栈容器适配器:栈也是一种基本的数据结构,栈实现的是 后进先出 (LIFO),即最后添加进该适配器的元素会最先弹出。在栈中,所有元素都会从栈顶入栈,并从栈顶出栈。栈容器严格意义上并非序列式容器,而是一种容器适配器。栈容器适配器的基本使用方法如下:
#include <iostream>
#include <stack> // 引入头文件
using namespace std; int main(){
// 创建一个存放整数类型,名为 s 的栈容器适配器,一开始默认为空。
stack<int> s; s.push(10); // 将元素压入栈顶。
s.top(); // 获取栈顶元素,此时的栈顶元素为数字10。
s.pop(); // 将栈顶元素出栈,此时栈内没有元素。
s.size(); // 获取栈内元素的个数。
s.empty(); // 判断栈是否为空,为空返回真,否则返回假。 // 与队列相同,栈内元素不支持直接遍历,只能从栈顶一个一个弹出。
// 在弹出元素时需要保证栈内不为空。
while (!s.empty()){
int t = s.top();
s.pop();
cout << t << endl;
}
return 0;
}
  1. deque 双端队列容器:与队列类似,双端对类容器支持快速随机访问和在两端插入删除。上面提及到的 queue 容器适配器就是基于双端队列所实现的。双端队列容器的基本使用方法如下:
#include <iostream>
#include <deque> int main() {
// 创建一个整数类型,名为 deq 的双端队列容器,初始存放了数字1-5。
deque<int> deq = {1, 2, 3, 4, 5}; deq.push_front(0); // 在前端添加元素。
deq.push_back(6); // 在末尾添加元素。 // 通过 for 循环对容器进行遍历:
for (int i : deq) cout << i << endl;
return 0;
}
  1. list 双向链表容器:链表数据结构支持快速插入删除操作,但是通过索引访问元素会比较慢。双向链表的语法与双端队列相似。在一般算法实现上,双向链表的应用并不算广泛,因此不过多阐述。双向链表的基本使用方法如下:
#include <iostream>
#include <list> // 引入头文件 int main() {
// 创建一个整数类型,名为 lst 的双向队列容器,初始存放了数字1-5。
list<int> lst = {1, 2, 3, 4, 5}; lst.push_front(0); // 在前端添加元素。
lst.push_back(6); // 在末尾添加元素。 // 通过 for 循环对容器进行遍历:
for (int i : lst) cout << i << endl;
return 0;
}

关联式容器 (Associative Containers)

  1. set 集合容器:集合容器的定义与数学中的集合完全相同。集合中相同的元素只会被存入一次。集合可以进行高效的查找操作。集合容器的基本使用方法如下:
#include <iostream>
#include <set> // 引入头文件 int main() {
// 创建一个整数类型,名为 s 的集合容器,初始存放了许多数字。
set<int> s = {3, 1, 4, 1, 5, 9}; s.insert(2); // 向集合内插入元素2。
s.erase(2); // 向集合中移除元素2。 s.count(2); // 检查元素是否存在于集合,如果返回真即存在,否则即不存在。
s.size(); // 获取集合内元素的个数。
s.clear(); // 清空集合。 // 通过 for 循环对容器进行遍历:
// 由于集合自带“去重”功能,最输出的结果为:1 3 4 5 9。
for (auto i : s) cout << i << " ";
return 0;
}
  1. map 映射容器:除了集合容器之外,该容器也经常在算法中被应用。映射容器用于存储键值对。其中键在该容器中是唯一,同时它还支持高效的查找操作(对于某些数据如果懒得离散化一定要试一下这个容器,我觉得真的很好用)。以下是映射容器的基本用法:
#include <iostream>
#include <map> // 引入头文件 int main() {
// 创建一个键为整数类型、值为字符串类型,名为 m 的映射容器。
map<int, string> m;
m[1] = "Macw07"; // 插入键值对。 s.count(2); // 检查元素是否存在于集合,如果返回真即存在,否则即不存在。
s.size(); // 获取集合内元素的个数。
return 0;
}

无序关联式容器 (Unordered Associative Containers)

一般情况下,所有的关联式容器默认都会按照存放数组(值/键)从小到大进行排序。但如果将这些关联式容器变成无序关联式容器,那么 STL 内部就不会默认对其进行排序。一般情况下,如果没有特殊需求推荐使用无序关联式容器,因为它们的运行效率通常都比有序的关联式容器要高很多。

以下是常见的无序关联式容器和有序关联式容器的声明代码:

mapunordered_map

#include <map>  // 有序的。
#include <unordered_map> // 无序的。 map<int, int> mp1; // 有序映射表的声明。
unordered_map<int, int> mp2; // 无序映射表的声明。

setunordered_set

#include <set>  // 有序的。
#include <unordered_set> // 无序的。 set<int> mp1; // 有序集合的声明。
unordered_set<int> mp2; // 无序集合的声明。

算法 Algorithms

终于讲完了容器部分,接下来来到了算法的部分。C++ 的 STL 供了许多算法,用于对容器中的元素进行操作。这些算法主要包括:排序、搜索、修改、集合操作、数值操作等。STL 的算法通常以函数模板的形式实现,可以与任意容器类型配合使用。

以下所有有关区间的内容均默认为 闭区间操作

  1. 排序算法 sortstable_sort。两种算法的目的都是对一个容器/数组进行排序。两个算法均传入三个参数,分别是排序区间的头尾地址和排序方式。其中排序方式不强制传参,对于整形类型的容器而言,默认按照从小到大的顺序进行排序。两种算法的作用基本相同,但 stable_sort 保证了排序的稳定性,普通的 sort 无法保证排序的稳定性。基本使用代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std; int main() {
vector<int> vec = {5, 3, 8, 6, 2};
int arr[] = {5, 4, 3, 2, 1};
// 前两个参数传入的是地址。
// xxx.begin()/ end() 表示获取某容器的首地址或尾地址。
sort(vec.begin(), vec.end()); // 对动态数组进行排序。
sort(arr, arr+5); // 对数组进行排序。 // 输出排序后的动态数组。
for (int i : vec) cout << n << " ";
return 0;
}

如果想要使用自定义排序,那么可以仿照以下方法来写:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std; // 创建一个自定义比较函数。
// 传入两个参数(类型与所需要排序的容器类型一致)。
// 说直白点,想要哪个前一个参数考前就返回真,否则返回假。
bool cmp(int a, int b){
return a > b; // 按照从大到小的顺序排。
// return a < b; // 按照从小到大的顺序排。
} int main() {
vector<int> vec = {5, 3, 8, 6, 2}; sort(vec.begin(), vec.end(), cmp); // 对动态数组按照cmp规则进行排序。 // 输出排序后的动态数组。
for (int i : vec) cout << n << " ";
return 0;
}
  1. 搜索算法 find:该算法帮助我们在指定范围内查找等于指定值的第一个元素。该算法也传入三个参数,也分别是查找区间的左地址和右地址,以及需要查找的对象。该算法的返回值也是一个地址。具体的使用方法如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std; int main() {
vector<int> vec = {5, 3, 8, 6, 2};
// 如需要获取所查找对象的索引,因在最后减去首地址。
auto index = std::find(vec.begin(), vec.end(), 8) - vec.begin();
return 0;
}
  1. 集合操作算法 set_unionset_intersection:之前已经提到了集合的容器,这两个算法均应用在集合容器(当然其他容器也可以)当中。其中 set_union 算法会将两个集合合并。set_intersection 算法可以求出两个集合的交集。这两个算法均传入五个参数,分别是第一个集合的左右地址、第二个集合的左右地址和存放最终计算结果的容器。两个函数的使用方法如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std; int main() {
vector<int> vec1 = {1, 2, 3};
vector<int> vec2 = {3, 4, 5};
vector<int> unionVec, intersectionVec; // 计算 vec1 和 vec2 的并集,并将结果存储到 unionVec 数组中。
set_union(vec1.begin(), vec1.end(), vec2.begin(), vec2.end(), back_inserter(unionVec)); // 计算 vec1 和 vec2 的交集,并将结果存储到 intersectionVec 数组中。
set_union(vec1.begin(), vec1.end(), vec2.begin(), vec2.end(), back_inserter(intersectionVec));
return 0;
}
  1. 反转算法 reverse:反转一个容器(数组/字符串也可以)指定范围内的元素顺序。该算法传入两个参数,分别表示区间的左右地址。该算法具体的使用方法如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std; int main() {
vector<int> vec = {1, 2, 3, 4, 5};
// 反转整一个 vec 数组
reverse(vec.begin(), vec.end()); // 此时 vec = {5, 4, 3, 2, 1} string str = "Macw07";
reverse(str.begin(), str.end()); // 此时 str = "70wcaM"。
return 0;
}
  1. 去重算法 uniqueeraseunique 算法用于移除指定范围内相邻的重复元素(需要先排序)。该算法只能在保证容器内有序地情况下使用,否则算法将无法返回正确的结果。该算法的会将重复的元素存放到区间的末尾,并返回一个指针,表示从该指针开始到区间结束是重复元素所在的区间。而 erase 的用处是删除容器指定区间内的所有元素。相同地,两个算法都需传入两个参数,分别表示区间的左右地址。该算法具体的使用方法如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std; int main() {
vector<int> vec = {1, 2, 2, 3, 4, 4, 5}; // 去重。
auto last = unique(vec.begin(), vec.end());
// 删除重复元素。
vec.erase(last, vec.end());
return 0;
}
  1. 查找算法 lower_boundupper_boundlower_boundupper_bound 是两种常用的搜索算法,主要用于有序范围内查找元素。它们分别返回 第一个不小于(大于或等于)指定值 和 第一个大于指定值的位置 。请注意,这两个算法需要在给定区间内的元素从小到大升序的情况下使用,否则将会返回错误的结果。这两个算法的底层原理是通过二分查找来实现的。假设存在一个数列 \([1, 2, 3, 5, 6, 6, 7]\),那么这个数列对 \(4\) 求 \(lower\_bound\) 就是 \(3\)(索引),而这个数列对 \(6\) 求 \(upper\_bound\) 就是 \(6\)(索引)两个算法的代码示例如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std; int main() {
vector<int> vec = {1, 2, 3, 4, 5, 6}; // ---------- lower_bound 算法 ----------
auto it1 = lower_bound(vec.begin(), vec.end(), 4);
// 如果要获取索引,那么再减去首地址即可。
int index1 = lower_bound(vec.begin(), vec.end(), 4) - vec.begin(); if (it1 != vec.end())
cout << "Lower bound of 4 is: " << *it1 << endl;
else
cout << "4 is not in the vector" << endl; // ---------- upper_bound 算法 ----------
auto it2 = upper_bound(vec.begin(), vec.end(), 4);
// 如果要获取索引,那么再减去首地址即可。
int index2 = upper_bound(vec.begin(), vec.end(), 4) - vec.begin(); if (it2 != vec.end())
cout << "Upper bound of 4 is: " << *it2 << endl;
else
cout << "All elements are <= 4" << endl; return 0;
}

迭代器 Iterators

接下来来到了迭代器部分,迭代器是一种用于遍历容器元素的抽象概念。它提供了一种统一的访问容器内元素的方式,使得算法可以独立于容器类型工作,增强了代码的通用性和可重用性。

迭代器的基本概念

迭代器 是一种指针-like 对象(表示与指针类似),它允许你遍历容器中的元素。你可以使用迭代器访问容器的每个元素,而不需要了解容器的内部实现细节。

迭代器范围 由两个迭代器表示,通常是一个指向范围起始位置的迭代器和一个指向范围末尾位置的迭代器。这两个迭代器标识了一个半开区间,即左闭右开。

虽然迭代器与指针近乎相同,但也有细微的差别(在本文前问中,由于未详细讲解迭代器,因此将迭代器和指针统称为指针)。以下是迭代器的常见方法:

  1. .begin():获取指向容器的第一个元素的迭代器。
  2. .end():获取指向容器的最后一个元素的下一个位置的迭代器。
  3. 迭代器++:将迭代器指向容器的下一个位置。
  4. *迭代器:获取迭代器指向的元素(类似于通过地址取值)。

一般情况下,我们通过使用 auto 类型来实现迭代器(因为这个类型比较方面,可以在任何时候用)。auto 类型是 C++11 引入的一个关键字,用于在编译时自动推断变量的类型。使用 auto 关键字声明的变量会根据其初始化表达式的类型自动推导出变量的类型。这样做可以简化代码、提高可读性,并且使代码更具灵活性

通过使用 auto 类型来对 STL 容器进行遍历操作:

#include <iostream>
#include <vector>
using namespace std; int main() {
vector<int> vec = {1, 2, 3, 4, 5}; // 使用迭代器遍历容器并输出元素。
for (auto it = vec.begin(); it != vec.end(); it++)
cout << *it << " ";
return 0;
}

小结

至此,对于 STL 的基本讲解就到此为止了。STL 标准模板库 本身是非常庞大的,因此本文并不涵盖所有的知识点。与此同时,我希望各位在使用不同的算法/容器的时候自行搜索有关该模板的时间复杂度,以更好的在做题过程中预估算法的整体耗时。

本文后续可能会持续更新。

【知识点】深入浅出STL标准模板库的更多相关文章

  1. STL标准模板库(简介)

    标准模板库(STL,Standard Template Library)是C++标准库的重要组成部分,包含了诸多在计算机科学领域里所常见的基本数据结构和基本算法,为广大C++程序员提供了一个可扩展的应 ...

  2. STL标准模板库介绍

    1. STL介绍 标准模板库STL是当今每个从事C++编程的人需要掌握的技术,所有很有必要总结下 本文将介绍STL并探讨它的三个主要概念:容器.迭代器.算法. STL的最大特点就是: 数据结构和算法的 ...

  3. STL学习系列一:STL(标准模板库)理论基础

    STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称.现然主要出现在C++中,但在被引入C++之前该技术就已经存在了很长的一段时间. STL的从广 ...

  4. STL(标准模板库)理论基础,容器,迭代器,算法

    基本概念 STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称.现然主要出现在C++中,但在被引入C++之前该技术就已经存在了很长的一段时间.   ...

  5. STL(标准模板库)基本概念

    一.什么是STL STL(Standard Template Library,标准模板库)的从广义上讲分为三类:algorithm(算法).container(容器)和iterator(迭代器),容器 ...

  6. C++ Templates STL标准模板库的基本概念

    STL标准库包括几个重要的组件:容器.迭代器和算法.迭代器iterator,用来在一个对象群集的元素上进行遍历操作.这个对象群集或许是一个容器,或许是容器的一部分.迭代器的主要好处是,为所有的容器提供 ...

  7. STL标准模板库 向量容器(vector)

    向量容器使用动态数组存储.管理对象.因为数组是一个随机访问数据结构,所以可以随机访问向量中的元素.在数组中间或是开始处插入一个元素是费时的,特别是在数组非常大的时候更是如此.然而在数组末端插入元素却很 ...

  8. STL标准模板库之vector

    目录 vector容器 1)什么是vector 2)如何定义 3)常用的Vector函数 1.容量函数 2.增加函数 3.删除函数 4.迭代器 5.访问函数 6.其他函数及操作 7.算法 STL提供了 ...

  9. STL(标准模板库) 中栈(stack)的使用方法

    STL 中栈的使用方法(stack) 基本操作: stack.push(x)  将x加入栈stack中,即入栈操作 stack.pop()  出栈操作(删除栈顶),只是出栈,没有返回值 stack.t ...

  10. STL 标准模板库

    <vector> 可变长的数组 Vector<int>v int是一个模板参数,这样传进来的都会是int V.push_back(a)将a传进v,且放在最后一个 V.clear ...

随机推荐

  1. 利用PyTorch训练模型识别数字+英文图片验证码

    利用PyTorch训练模型识别数字+英文图片验证码 摘要:使用深度学习框架PyTorch来训练模型去识别4-6位数字+字母混合图片验证码(我们可以使用第三方库captcha生成这种图片验证码或者自己收 ...

  2. 自己diy一个smi(可以监控gpu,cpu,memory)

    diy-smi 1.0(DIY System management interface) 在我们训练自己深度学习模型时,我们想看GPU信息,也想看CPU和内存信息.我常用的是nvidia-smi和ht ...

  3. i-MES生产制造管理系统-老化时间管控

    在生产过程中,产品的可靠性是影响其性能和寿命的关键因素,因此提高产品的可靠性是十分必要的,而老化测试是提高产品可靠性的重要手段之一,老化的时间随着产品不同而变化,因此老化时间管控变得尤为重要! 在ME ...

  4. 一站式指南:ClkLog部署环境配置指南

    在今天的数字化世界中,数据管理和分析工具的选择对于企业的成功至关重要.ClkLog作为一款强大的日志分析工具,能够帮助企业从海量数据中洞察业务.提升效率.然而,如何才能顺利部署ClkLog系统,使其最 ...

  5. HarmonyOS NEXT应用开发案例——阻塞事件冒泡

    介绍 本示例主要介绍在点击事件中,子组件enabled属性设置为false的时候,如何解决点击子组件模块区域会触发父组件的点击事件问题:以及触摸事件中当子组件触发触摸事件的时候,父组件如果设置触摸事件 ...

  6. 【ASPLOS 2022】机器学习访存密集计算编译优化框架AStitch,大幅提升任务执行效率

    简介: 近日,关于机器学习访存密集计算编译优化框架的论文<AStitch: Enabling A New Multi-Dimensional Optimization Space for Mem ...

  7. 从图森未来的数据处理平台,看Serverless工作流应用场景

    4月,阿里云Serverless工作流正式商业化,这是一款用于协调多个分布式任务执行的全托管 Serverless 云服务.产品致力于简化开发和运行业务流程所需要的任务协调.状态管理以及错误处理等繁琐 ...

  8. WPF 多线程下跨线程处理 ObservableCollection 数据

    本文告诉大家几个不同的方法在 WPF 里,使用多线程修改或创建 ObservableCollection 列表的数据 需要明确的是 WPF 框架下,非 UI 线程直接或间接访问 UI 是不合法的,设计 ...

  9. dotnet ConditionalWeakTable 的底层原理

    在 dotnet 中有一个特殊的类,这个类能够做到附加属性一样的功能.也就是给某个对象附加一个属性,当这个对象被回收的时候,自然解除附加的属性的对象的引用.本文就来聊聊这个类的底层原理 小伙伴都知道弱 ...

  10. dotnet Microsoft.Recognizers.Text 超强大的自然语言关键词提取库

    本文和大家介绍一个使用超级简单,但是功能特别强大的自然语言关键词提取库,可以根据输入的自然语言提取出里面的信息.例如我在一句话里面说了哪些数值变量或者说了手机号码等 先看看下图的一个效果,下图是尝试识 ...