c++ STL容器及常用函数
前置知识
迭代器
介绍
定义:迭代器是一种检查容器内元素并遍历元素的数据类型。迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围。
迭代器和指针的区别
- 迭代器不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能,通过重载了指针的一些操作符,->,*,++ --等封装了指针,是一个“可遍历STL容器内全部或部分元素”的对象, 本质是封装了原生指针,指针的++只是简单的去增加快大小的地址,这在支持随机迭代器的容器中是可行的,但是当去遍历list这种内存不连续的来说,就无法只通过简单的++来实现了,而迭代器是可以++访问任何容器的。
- 迭代器返回的是对象引用而不是对象的值,所以cout只能输出迭代器使用*取值后的值而不能直接输出其自身。
- 指针能指向函数而迭代器不行,迭代器只能指向容器;指针是迭代器的一种。指针只能用于某些特定的容器;迭代器是指针的抽象和泛化。所以,指针满足迭代器的一切要求。
- 迭代器在使用后就会被释放,不能再次使用,而指针可以
迭代器的运算符
*iter // 返回迭代器iter所指元素的引用
iter->mem // ->左边为指针,.左边为实体 == (*iter).mem
iter + n // 返回的迭代器是原迭代器向前移动若干个元素。如果超出容器的范围,则返回迭代器的第一个元素的位置,或者尾元素的下一个位置。
iter - b // 返回的迭代器是原迭代器向后移动若干个元素。如果超出容器的范围,则返回迭代器的第一个元素的位置,或者尾元素的下一个位置。
iter += n // 迭代器加法赋值语句,将iter + n 赋值给iter (!!!不能超范围!!!)
iter -= n // 迭代器减法赋值语句,将iter - n 赋值给iter (!!!不能超范围!!!)
iter1 - iter2 // 返回两个迭代器之间的距离。返回的类型为difference_type,带符号的整数。建议使用auto
>、>= 、<、<=、 ==、 != // 判断两个迭代器的位置。必须是同一个容器的迭代器
迭代器的类型:iterator 和 const_iterator
begin和end返回的具体类型由对象是否是常量决定,如果是常量,begin和end返回的const_iterator
;如果不是常量,返回iterator
。
vector<int>::iterator it; // it可以读写vector<int>的元素
vector<int>::const_iterator it3 // it3只能读元素,不能写元素
迭代器分类
容器 | 迭代器功能 | 迭代器属性 |
---|---|---|
vector | 随机访问 | 一种随机访问的数组类型,提供了对数组元素进行快速随机访问以及在序列尾部进行快速的插入和删除,可以在需要的时候修改其自身的大小 |
deque(双向队列) | 随机访问 | 一种随机访问的数组类型,提供了对序列两端进行快速的插入和删除,可以在需要的时候修改其自身的大小 |
list | 双向 | 不支持随机访问,插入和删除所花费的时间是固定的,可以进行双向迭代 |
set/multiset | 双向 | 随机存储,关键字和数据元素是同一个值,可以进行双向迭代 |
map/multimap | 双向 | 成对数值,关键字与元素进行关联,可以进行双向迭代 |
stack | 无 | 不允许遍历,所以不支持迭代器,只支持插入和取出头部元素 |
queue | 无 | 不允许遍历,所以不支持迭代器,只支持低端插入和取出头部元素 |
priority_queue | 无 | 不允许遍历,所以不支持迭代器,只支持插入和取出头部元素 |
迭代器失效
- 容器的插入insert和erase操作可能导致迭代器失效
- 对于erase操作不要使用操作之前的迭代器,因为erase的那个迭代器一定失效了,正确的做法是返回删除操作时候的那个迭代器。
通用函数
函数 | 说明 | 适用范围 |
---|---|---|
.begin() .end() | 返回第一个元素迭代器和最后一个元素迭代器 | 支持迭代器的 |
sort(first,last,cmp) | 排序,默认是升序 | vector |
reverse(first,last) | 翻转列表,可以将vector行翻转 | vector,string |
reseize(int),reserve(int) | 重新定义容器大小和元素多少 | 全 |
empty() | 如果为空返回真 | 全 |
front(),back() | 返回第一个/最后一个元素 | queue,vector |
push_back(),pop_back() | 在末尾插入和删除一个元素(emplace_back) | vector,string,list |
pop(),push() | 删除元素/加入元素 | queue、stack |
top() | 返回顶部元素 | stack、优先队列 |
v.insert(v.begin(),v1.first,v1.last) | 向任意迭代器it处插入一个元素/插入某个值 | 支持迭代器 |
swap() | 交换两个容器的内容(可以交换二维数组行) | 全 |
lower_bound(first,last, target) | 查找第一个大于等于target目标值的位置 | vector,set,map |
upper_bound(first,last, target) | 查找第一个大于target目标值的位置**(基于二分查找实现,所以可以用作有序的容器)** | vector,set,map |
binary_search(first,last, target) | 查找target是否存在于数组或vector中,找到返回true | vector |
find(first,last, target) | 返回一个指向被查找到元素的迭代器/位置 | set,string,map |
count(first,last, target) | 计数 | set,map |
erase(迭代器) | 删除某个元素 ,并返回下一个元素的迭代器 | set,vector,map |
clear() | 清空容器 | 全 |
push()\push_back()\emplace_back()\insert
- push(val):在栈顶增加元素(stack),将元素接到队列的末端(queue)
- push_back(val):该函数将一个新的元素加到vector的最后面,位置为当前最后一个元素的下一个元素,新的元素的值是val的拷贝(或者是移动拷贝)
- emplace_back():push_back 和 emplace_back 的差异
- insert():在指定位置插入/在关联式容器中插入某个值。
set.insert(xx).second
用来确认某个值是否插入成功,成功就返回true。
sort排序(添加lambda表达式)
//从大到小排序
sort(temp.begin(),temp.end(),[](int a ,int b){return a>b;});
//按照第一列升序排序
sort(v.begin(),v.end(),[](vector<int> &a,vector<int>&b){return a[0]<b[0];});
//定义按照第一列升序排列,如果相等,按照第二列升序排列
bool cmp(vector<int> &a,vector<int>&b>{
if(a[0]!=b[0] return a[0]<b[0];
else return a[1]<b[1];
}
使用场合
vector | deque | list | set | map | unordered_set | unordered_map | |
---|---|---|---|---|---|---|---|
内存结构 | 单端数组 | 双端数组 | 双向链表 | 二叉树 | 二叉树 | 哈希表 | 哈希表 |
可随机存取 | 是 | 是 | 否 | 否 | 对key而言:不是 | 否 | 否 |
元素搜寻速度 | 慢 | 慢 | 非常慢 | 快 | 对key而言:快 | 快 | 快 |
元素安插移除 | 尾端 | 头尾两端 | 任何位置 | - | - | - | - |
vector
概述
vector
是长度可以根据需要改变的数组,vector的数据安排方式和操作方式与array十分相似。两者的唯一区别是array是静态空间,空间一旦配置就无法改变,如果一定需要改变,需要用户自己重新开辟一个新的大小空间,然后原有空间上的数据一 一拷贝到新的空间,然后将原有空间释放掉。而vector是一个动态空间,在元素的不断加入的过程中,如果空间满了,它会自行扩充空间。
实现原理:
图来源于STL之Vector实现原理_316llp的博客-CSDN博客_vector实现原理
resize(n)
:调整容器的长度大小,使其能容纳n个元素,resize(n,t)
:将所有新添加的元素初始化为t
。reserver(n)
:预分配n个元素的存储空间。
resize()
和reserver()
的区别:
size
指容器当前拥有的元素个数;而capacity
则指容器在必须分配新存储空间之前可以存储的元素总数。也可以说是预分配存储空间的大小。
调用resize(n)
后,容器的size
即为n。至于是否影响capacity
,取决于调整后的容器的size
是否大于capacity
,如果大于,那么capacity
更新为n。调用reserve(n)
后,若容器的capacity<n
,则重新分配内存空间,从而使得capacity
等于n。如果capacity>=n
呢?capacity
无变化。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a;
a.reserve(100);
a.resize(50);
cout<<a.size()<<" "<<a.capacity()<<endl;// 50 ,100
a.reserve(150);
//此时改变了capacity但是没有改变size
cout<<a.size()<<" "<<a.capacity()<<endl;// 50 ,150
a.resize(200);
//此时capacity和size都被改变了
cout<<a.size()<<" "<<a.capacity()<<endl;// 200 ,200
return 0;
}
reserve是容器预留空间,但在空间内不真正创建元素对象,所以在没有添加新的对象之前,不能引用容器内的元素。 加入新的元素时,要调用push_back()/insert()函数。
resize是改变容器的大小,且在创建对象,因此,调用这个函数之后,就可以引用容器内的对象了, 因此当加入新的元素时,用operator[]操作符,或者用迭代器来引用元素对象。此时再调用push_back()函数,是加在这个新的空间后面的。
方法
定义和初始化
-
头文件:
#include <vector>
-
定义:
vector<typename> name;
-
初始化(常用的):
//初始化一个size为0的vector vector<int> abc; vector<int> abc(10); //初始化了10个默认值为0的元素 vector<int> cde(10,1); //初始化了10个值为1的元素 vector<int> list2(list); vector<int> ilist2 = ilist; //拷贝初始化 vector<int> list {1,2,3,4,5,6,7};//列表中元素的拷贝 vector<int> list3(list.begin()+2, list.end()-1); //切片list vector<vector<int>> vec(row, vector<int> (col,1)); //初始化为1 vector<int> list {{1,2},{3,4},{6,7}};//列表中元素的拷贝
访问方式
- 通过下标进行访问
vector[index]
- 通过迭代器进行访问
vector<typename>::iterator it;
,*it
就是要取的值。
常用函数
图来源于博客:【C/C++】STL详解_沉晓的博客-CSDN博客_stl
函数 | 说明 | 时间复杂度 |
---|---|---|
operator[] | *回索引idx所指的数据,越界时,运行直接报错 | O(1) |
accumulate(v.begin(), v.end(), 0) | 累加,包含头文件#include<numeric> | O(n) |
v.insert(v.end(),v1.begin(),v1.end()) | 将v1中的元素从开始到末尾插入到v的末尾 | |
vector a[10] | 留10个位置的空间,插入时a[1].push_back(val) | |
A.assign(const_iterator first,const_iterator last); | 将区间[first,last)的元素赋值到当前的vector容器中 | |
max_element(first,last),min_element | 找到数组中的最大值和最小值,返回迭代器位置 | O ( n ) O(n) O(n) |
deque
概述
Vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间。
所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,无法被接受。
Deque容器和vector容器最大的差异,一在于deque允许使用常数项时间对头端进行元素的插入和删除操作。二在于deque没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,换句话说,像vector那样旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque身上是不会发生的。也因此,deque没有必须要提供所谓的空间保留(reserve)功能.
虽然deque容器也提供了Random Access Iterator,但是它的迭代器并不是普通的指针,其复杂度和vector不是一个量级,这当然影响各个运算的层面。因此,除非有必要,我们应该尽可能的使用vector,而不是deque。对deque进行的排序操作,为了最高效率,可将deque先完整的复制到一个vector中,对vector容器进行排序,再复制回deque.
实现原理
Deque容器是连续的空间,至少逻辑上看来如此,连续现行空间总是令我们联想到array和vector,array无法成长,vector虽可成长,却只能向尾端成长,而且其成长其实是一个假象,事实上(1) 申请更大空间 (2)原数据复制新空间 (3)释放原空间 三步骤,如果不是vector每次配置新的空间时都留有余裕,其成长假象所带来的代价是非常昂贵的。
Deque是由一段一段的定量的连续空间构成。一旦有必要在deque前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在deque的头端或者尾端。Deque最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。
既然deque是分段连续内存空间,那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。Deque代码的实现远比vector或list都多得多。
Deque采取一块所谓的map(注意,不是STL的map容器)作为主控,这里所谓的map是一小块连续的内存空间,其中每一个元素(此处成为一个结点)都是一个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是deque的存储空间的主体。
方法
定义和初始化
- 头文件:
#include <deque>
- 定义:
deque<typename> name;
- 初始化(常用的):
deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将n个elem拷贝给本身。
deque(const deque &deq);//拷贝构造函数。
访问方式
- 通过下标进行访问
deque[index]
- 通过迭代器进行访问
deque<typename>::iterator it;
,*it
就是要取的值。
常用函数
函数 | 说明 | 时间复杂度 |
---|---|---|
push_front(elem) | 在容器头部插入一个数据 | |
pop_front() | 删除容器第一个数据 |
string
概述
string和c风格字符串对比:
- Char* 是一个指针,String是一个类,string封装了char* ,管理这个字符串,是一个char* 型的容器。
- String封装了很多实用的成员方法,查找find,拷贝copy,删除delete 替换replace,插入insert
- 不用考虑内存释放和越界,string管理char*所分配的内存。每一次string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。
实现原理
类字符串数组
方法
定义和初始化
-
头文件:
#include <string>
-
定义:
string name;
-
初始化(常用的):
string();//创建一个空的字符串 例如: string str; string(const string& str);//使用一个string对象初始化另一个string对象 string(const char* s);//使用字符串s初始化 string(int n, char c);//使用n个字符c初始化 string = " ";
访问方式
- 通过下标进行访问
string[index]
常用函数
函数 | 说明 | 时间复杂度 |
---|---|---|
stoi(string) | 将字符串s转化成整形,s为string 类型,即string --> int | |
to_string(int) | 将整型转换为字符串 | |
stringstream | 可以将数字和字符串任意转换 | |
string a=s.substr(0,3); | 返回从pos号开始、长度为len的子串。 | |
str.replace(it1, it2, str2) | 把str的迭代器[it1, it2)范围的子串换成str2 | |
str.replace(pos, len, str2) | 把str从pos位开始、长度为len的字符串换成str2 | |
str.append(string s, int pos, int n) | 把字符串s中从pos开始的n个字符连接到当前字符串结尾 |
queue
概述
Queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口,queue容器允许从一端新增元素,从另一端移除元素。
queue不支持迭代器,不提供遍历功能,只有顶端元素才会被外界取用
方法
定义和初始化
- 头文件:
#include <queue>
- 定义:
queue<Typename> name;
stack
概述
stack是一种先进后出的数据结构,它只有一个出口,形式如图所示。stack容器允许新增元素,移除元素,取得栈顶元素,但是除了最顶端外,没有任何其他方法可以存取stack的其他元素。换言之,stack不允许有遍历行为。
有元素推入栈的操作称为:push,将元素推出stack的操作称为pop.
stack没有迭代器,只有顶端的元素才能被外界取用,不提供遍历功能,也不提供迭代器
方法
定义和初始化
- 头文件:
#include <stack>
- 定义:
stack<Typename> name;
list
概述
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
相较于vector的连续线性空间,list就显得负责许多,它的好处是每次插入或者删除一个元素,就是配置或者释放一个元素的空间。因此,list对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的元素插入或元素的移除,list永远是常数时间。
list容器是一个双向链表
- 采用动态存储分配,不会造成内存浪费和溢出
- 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
- 链表灵活,但是空间和时间额外耗费较大
方法
定义和初始化
- 头文件
#include<list>
- 定义:
list<T> l;
访问方式
常用函数
函数 | 说明 | 时间复杂度 |
---|---|---|
remove(elem) | 删除容器中所有与elem值匹配的元素。 | O(n) |
pair
概述
对组(pair)将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别用pair的两个公有属性first和second访问。
//第一种方法创建一个对组
pair<string, int> pair1(string("name"), 20);
cout << pair1.first << endl; //访问pair第一个值
cout << pair1.second << endl;//访问pair第二个值
//第二种
pair<string, int> pair2 = make_pair("name", 30);
cout << pair2.first << endl;
cout << pair2.second << endl;
//pair=赋值
pair<string, int> pair3 = pair2;
cout << pair3.first << endl;
cout << pair3.second << endl;
set/map/multiset/multimap
概述
Set的特性是。所有元素都会根据元素的键值自动被排序。Set的元素不像map那样可以同时拥有实值和键值,set的元素即是键值又是实值。Set不允许两个元素有相同的键值。
我们不可以通过set的迭代器改变set元素的值,因为set元素值就是其键值,关系到set元素的排序规则。如果任意改变set元素值,会严重破坏set组织。换句话说,set的iterator是一种const_iterator(只可读不可写).
set拥有和list某些相同的性质,当对容器中的元素进行插入操作或者删除操作的时候,操作之前所有的迭代器,在操作完成之后依然有效,被删除的那个元素的迭代器必然是一个例外。
map元素为pair,其余特性与set一致。
set和unordered_set只能通过insert()函数插入数据,如果set中已经存在该键值,则insert()函数插入失败,不改变set中的值,直接返回
实现原理
multiset特性及用法和set完全相同,唯一的差别在于它允许键值重复。set和multiset的底层实现是红黑树。
方法
定义和初始化
- 包含相应的头文件
set<T> st;//set默认构造函数:
mulitset<T> mst; //multiset默认构造函数:
set(const set &st);//拷贝构造函数
map<T1, T2> mapTT;//map默认构造函数:
map(const map &mp);//拷贝构造函数
访问方式
- map可以用[key]的方式来访问键值
set和map使用迭代器进行遍历。
常用函数
set的交集,并集,差集
- set具有有序性,即对插入的元素会进行自动排序,而如果需要对vector进行求并集、交集、差集操作,请提前使用
sort函数
进行排序。
//需要头文件
#include <algorithm>
#include <set>
#include <iterator>//inserter函数需要该头文件
set_union(all(s1), all(s2), ins(sR))//把s1与s2的并集以插入的形式给sR
set_intersection(all(s1), all(s2), ins(sR))//把s1与s2的交集以插入的形式给sR
//其中
#define all(s0) s0.begin(),s0.end()
#define ins(s0) inserter(s0,s0.begin())
//如果不以插入的形式也可以直接传sR.begin()这个迭代器,但你需要保证sR有足够的空间
//以接送收元素,而使用inserter函数返回的迭代器则没有这个问题。
//需要注意的是:inserter函数返回的迭代器在内部使用insert方法插入元素,你需要保证容器
//有insert方法,还需要确保sR被提前清空,否者会出现任意集合交空集结果不是空集等错误。
相关练习
【c++】【leetcode128】最长连续序列(含set用法)_wenningker的博客-CSDN博客
unordered_set/unordered_map
概述
unordered_set和unordered_map用法详解_谢白羽的博客-CSDN博客_unordered_map和unordered_set
关联容器,内部采用的是hash表结构,拥有快速检索的功能。
性质
- 关联性:一个将key和value关联起来的容器,它可以高效的根据单个key值查找对应的value。通过key去检索value,而不是通过绝对地址(和顺序容器不同)
- 无序性:使用hash表存储,内部无序,可以使用[]操作符来访问key值对应的value值。
- 键唯一性:不存在两个元素的键一样,Map : 每个值对应一个键值
- 动态内存管理:使用内存管理模型来动态管理所需要的内存空间
- Hashtable和bucket
由于unordered_map内部采用的hashtable的数据结构存储,所以,每个特定的key会通过一些特定的哈希运算映射到一个特定的位置,我们知道,hashtable是可能存在冲突的(多个key通过计算映射到同一个位置),在同一个位置的元素会按顺序链在后面。所以把这个位置称为一个bucket是十分形象的(像桶子一样,可以装多个元素)。
对于unordered_set,保持的元素就是键值。键值不可改变。
相关练习
【c++】【leetcode349】求两个数组交集,用set实现_wenningker的博客-CSDN博客
priority_queue
常用函数和队列一样
- top 访问队头元素
- empty 队列是否为空
- size 返回队列内元素个数
- push 插入元素到队尾 (并排序)
- emplace 原地构造一个元素并插入队列
- pop 弹出队头元素
- swap 交换内容
概述
定义:priority_queue<Type, Container, Functional>
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式,当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆
//升序队列
priority_queue <int,vector<int>,greater<int>> q;
//降序队列
priority_queue <int,vector<int>,less<int>> q;
//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)
自定义比较类型
//方法1
struct tmp1 //运算符重载<
{
int x;
tmp1(int a) {x = a;}
bool operator<(const tmp1& a) const
{
return x < a.x; //大顶堆
}
};
//方法2
struct tmp2 //重写仿函数
{
bool operator() (tmp1 a, tmp1 b)
{
return a.x < b.x; //大顶堆
}
};
priority_queue<type,vector<type>,tmp2> q;