C++ STL教程
C++ STL
C++ STL教程
一直以来在竞赛界一直有着一种传言,自己手搓的数组、栈、队列效率比STL高很多,甚至于还有江湖传说,有的人用自己写的就过了,用STL就超时。但其实STL恰恰在C++一众繁复的特性中,是最受人欢迎的之一。使用STL其实并不会让效率降低很多,反而可以让自己的程序更具有可读性,以及获得更低的编程复杂度(笑,现在真的还有人讲究编程复杂度这种玩意么)。
所以特此写下一篇教程。(也是为了让自己以后忘记STL用法的时候能记起来,hhh)
一、概念
C++ STL(标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。
C++ 标准模板库的核心包括以下三个组件:
组件 | 描述 |
---|---|
容器(Containers) | 容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等。 |
算法(Algorithms) | 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。 |
迭代器(iterators) | 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集。 |
二、简介
以下这个篇章将简单介绍STL容器的一些通用规则。(大部分以vector容器为例)
1.引入与声明
首先你需要什么容器就需要引入什么头文件,
例如你需要vector
、stack
、pair
容器,你就要引入以下头文件:
1 |
其次需要在主程序里声明这几个容器,例如:
1 | vector<int> vec; |
也可以在容器里叠加其他容器:
1 | vector<vector<int> > vec1; //二维数组 |
需要注意的是两个> >
之间有空格。
2.访问方式
STL既可以使用传统的数组下标访问(并不是所有的容器都支持数组下标访问),也可以通过迭代器(iterators)访问。需要注意的是使用迭代器访问是更为安全的方法,因为使用数组下标访问时,对于越界并不判定。
传统数组下标访问如下:
1 |
|
声明迭代器的方法,根据你的容器不同而不同:
1 | vector<int>::iterator i; |
使用迭代器访问如下:
1 |
|
PS:在for (int i = vec.begin();i != vec.end();i++)
这行中千万不能写成i <= vec.end()
!千万不能写成i <= vec.end()
!千万不能写成i <= vec.end()
!
笔者在这上面调试了半个小时,硬是没找出问题所在,各位猜怎么着?笔者去问了ChatGPT才知道答案(笑)
以下附上ChatGPT的解释:
The loop should not go beyond
q.end()
as it points to the element after the last element of the deque (similarly to an “end” pointer in C++ STL).意思为如果使用
i<=vec.end()
,会造成迭代器越界问题,从而造成一些未知的错误
迭代器就类似一个指针,访问地址中的内容时需要*i
。
迭代器也可以进行一些逻辑运算,例如上面这段代码中的i!=vec.end()
。
同样支持例如+=
、++
、--
等运算。
当然这个是最常用的正向迭代器,还有反向迭代器、常量正向迭代器、常量反向迭代器。
反向迭代器在使用++
等运算的时候是向前一个目标移动,常量正向、反向迭代器就是指向固定的元素而无法改变的迭代器。
三、常用容器介绍
以下这个篇章将介绍vector
、stack
、queue&priority_queue
、deque
、map
、multimap
、string
容器的用法。
函数仅介绍了一些很常用的函数,例如cbegin
、cend
便不在讲解,如有需要请查阅C++ STL文档。
1.vector
此容器相当于一个可变的数组,用以替代传统的int a[1000];
的数组声明方式,节省空间。
引入头文件:
1 |
常用函数,例如:
1 |
|
以上程序简单介绍了vector的简单用法,输出结果:
1 | 100 1 2 3 4 101 |
2.stack
栈是一种后进先出的数据结构,一般用于深度优先搜索等算法:

stack容器提供了这样的数据结构,可以避免手工模拟。
引入头文件:
1 |
常用函数,例如:
1 |
|
值得注意的是stack
容器并不常用,因为其不能使用迭代器,遍历也较为麻烦,一般还是使用vector
容器。
PS:stack
不支持随机访问,即数组下标访问
2.queue&priority_queue
队列是一种先进先出的数据结构,一般用于广度优先搜索等算法中,使用queue
容器:
优先队列,也被称之为堆,加入的每个元素都拥有不同的优先级,根据不同的优先级决定出队列的顺序。
根据不同的顺序,堆也分为大根堆和小根堆,大根堆就是将大的元素放在堆顶的堆,小根堆就是将小的元素放在堆顶的堆。
C++中priority_queue默认实现的是大根堆。
priority_queue和queue以下我将分别介绍。
两种容器引入的头文件是相同的:
1 |
以下这个例子将介绍queue的用法:
1 |
|
priority_queue的声明较为复杂,除了默认大根堆,也可以声明小根堆。
第一种方式较为巧妙,如果堆中的元素均为正数,那么可将其全部取相反数,这样绝对值较小的元素便会被排序在前面。再取出的时候再次取反,便可以达到小根堆的效果。
第二种方式是声明小根堆(笔者也不明白其中原理,hhhhh):
1 | priority_queue<int,vector<int>,greater<int> >q; |
以下以默认的大根堆为例:
1 |
|
值得注意的是如果使用的是priority_queue
,那么返回栈顶元素是top
函数,而并非front
函数;如果是queue
那么返回队列头元素是front
函数,而并非top
函数。
PS:priority_queue
与queue
均不支持随机访问,即数组下标访问
3.deque
双端队列是队列的进阶版本,队列是队列头弹出元素,队列尾加入元素;双端队列两端都可以加入与弹出元素。
值得注意的是queue
、priority_queue
均为deque
的某种变种,且deque
相较之而言可以使用迭代器进行遍历,也可以使用数组下标进行随机访问,因而实际应用中,笔者以为deque
是更为灵活更为实用的一种容器。
使用
deque
容器时一定要小心,因为大部分vector
容器适用的insert
、erase
等函数在deque
也可以适用,但是这些函数的运用可能会破坏其作为双端队列的性质,要谨慎使用,因此如下的例子中将不再介绍与vector
相同的函数
引入头文件:
1 |
以下这个例子将介绍deque
的应用:
1 |
|
4.map
map
是一个关联容器,用于存储一对键值(key-value)映射,可用于哈希表等数据结构。
引入头文件:
1 |
以下这个例子将介绍map
的应用:
1 |
|
PS:因为在map
里面存储的是一个个pair
容器,所以a.insert(make_pair("e", 5));
也可以起到插入一个键值映射的效果。
5.multimap
map
容器只能一个key对应一个实值,而multimap
则可以一个key对应多个实值。
和
map
容器相比,multimap
未提供at()
成员方法,也没有重载[]
运算符。这意味着,map
容器中通过指定键获取指定指定键值对的方式,将不再适用于multimap
容器。其实这很好理解,因为multimap
容器中指定的键可能对应多个键值对,而不再是 1 个。
引入头文件:
1 |
以下这个例子将介绍multimap
的应用:
1 |
|
6.string
C风格的字符串(char a[1000];
)相当于一个普通的数组,使用比较复杂,许多东西例如插入删除都需要手工模拟,因此C++在STL中引入了一个string
的字符串类。
字符串在日常应用中较为普遍,因此笔者将详细介绍string
的用法
引入头文件:
1 | #include <string> |
声明并初始化的几种方法:
1 | string str1; // 定义一个空字符串str1 |
可使用一些基本操作符:
1 | string str("HelloWorld"); |
拼接、查找、替换、删除、提取子串等操作:
1 | string str("HelloWorld"); |
其余的函数,例如empty()
,size()
,clear()
等函数均可以在string
类中使用
迭代器也可以在string
类中应用。
四、后记
C++的STL也是一个比较复杂的特性,为大程序的编写提供了很多便利。以上笔者仅仅粗浅地介绍了很少一部分。介于笔者水平,难免有理解和描述上有疏漏或者错误的地方,欢迎共同交流。