C++STL 学习笔记

c++的常用STL容器

一、基础概念

1.什么是容器

容器是存放数据的模板类,相当于现成的数据结构,用来存放各种数据(int,string,自定义对象等)

分为三大类

1.序列式容器:元素有序排列,位置和插入顺序有关,不自动排序

vector,deque,list,forward_list,array

2.关联式容器:元素按键自动排序,底层红黑树,查找快

set,multiset,map,multimap

3.无序容器(哈希容器)c++11新增的:底层哈希表,不排序,查找平均O(1)

unordered_set,unordered_multiset,unordered_map,unordered_multimap

2.迭代器iterator

迭代器:容器的“指针”,用来遍历容器元素。每种容器都有自己的迭代器类型。
容器类型::iterator it;
  • .begin():返回第一个元素迭代器

  • .cbegin():const只读迭代器,不能修改元素

  • .end():返回末尾的下一个位置(不是最后一个元素!不能解引用)

  • .cend():const只读末尾迭代器

  • .rbegin()/.rend():反向迭代器,从后往前遍历

遍历模板写法:

vector<int> v = {1,2,3};
for(vector<int>::iterator it = v.begin();it != v.end();it++)
{
    cout<<*it<<endl;
}
//C++11范围for,底层本质上还是迭代器
for(auto x:v)
{
    cout<<x;
}

迭代器失效重点

迭代器失效:容器增删元素,部分迭代器变成野指针,继续使用直接崩溃。不同容器失效规则不一样,后面逐个记。

3.通用常用成员函数(大部分容器都有)

  • .size():返回元素个数

  • .empty():判断是否为空,true空,false不为空

  • .clear():清空所有元素

  • .swap(另一个容器):交换两个容器,效率极高,交换内部指针,不拷贝数据

  • == !=可以直接比较两个容器

二、序列式容器

1.vector动态数组 (最常用)

头文件:

#include <vector>

底层原理

连续内存数组。内部有三个指针:start、finish、end_of_storage

  • size:实际元素数量

  • capacity:容量(已经分配的内存大小),capacity >= size

当size == capacity,再push_back,会重新开辟一块更大连续内存,拷贝全部旧元素,释放旧元素。扩容会拷贝,性能损耗。
vector扩容一般是1.5倍或者2倍,编译器实现决定。MSVC为1.5倍,GCC为2倍。

特点

1.内存连续,支持随机访问 v[下标] / at(下标),访问速度O(1)

2.尾部插入删除很快O(1);中间/头部插入删除需要移动大量元素O(n),很慢

3.扩容会导致全部迭代器失效!

4.删除元素:只会减少size,不会自动减小capacity;想要释放多余内存:v.shrink_to_fit();注意:shrink_to_fit只是请求,标准允许编译器忽略该请求,不保证一定缩减容量。

常用API

//构造
vector<int> v1; //空
vector<int> v2(10)//10个默认0
vector<int> v3(10,6)//10个6
vector<int> v4(v3.begin(),v3.end());//迭代器区间构造
vector<int> v5 = {1,2,3,4};//列表初始化c++11
//增
v.push_back(10); //尾部插入
v.emplace_back(10);//尾部构造元素,直接在内存构造,比push_back少拷贝,优先用emplace_back,传入的是对象构造参数
v.insert(pos,val);//迭代器pos位置插入元素,返回新元素迭代器,插入后pos以及之后迭代器全部失效
//删
v.pop_back();//尾部删除,无返回值
v.erase(it);//删除it指向元素,返回下一个有效迭代器;erase之后it迭代器失效!
v.erase(begin,end);//删除区间元素
//查改
v[2]; //下标访问,越界不会报错!危险
v.at(2);//下标访问,越界抛异常,安全
v.front();//第一个元素
v.back();//最后一个元素
v.reserve(100);//预分配容量capacity,只开内存,不创建元素,不改变size。避免频繁扩容!reserve只能扩大容量,不会缩小
[优化重点]
v.shrink_to_fit();//缩减容量释放更多内存

vector迭代器失效总结

1.insert / push_back:如果发生扩容,全部迭代器失效;没扩容,插入点之后迭代器失效

2.erase:被删除元素以及后面全部迭代器失效。erase返回新迭代器,接受返回值可以继续使用

❗错误示例:erase后直接++it会崩溃

//错误
for(auto it=v.begin();it!=v.end();++it){ if(*it==3) v.erase(it); }
//正确
for(auto it=v.begin();it!=v.end();){ if(*it==3) it = v.erase(it); else ++it; }

2.deque双端队列

头文件:

#include <deque>

底层原理

分段连续内存,不是整块连续数组。多个小块内存,中控数组管理每一块地址。逻辑上看起来连续,物理不连续

特点

1.头部、尾部插入删除都很快O(1)

2.支持随机访问[ ] at(),但是比vector慢一点,需要计算块地址

3.中间插入删除依然很慢O(n)

4.不会像vector那样整体拷贝扩容;push_front/push_back/insert会让全部迭代器失效;erase仅被删除元素迭代器失效

5.没有capacity概念,没有reserve

常用API

大部分和vector一样,多了头部操作

deque<int> d;
d.push_front(2);//头插
d.emplace_front(2);
d.pop_front();//头删
适用场景:需要头尾频繁增删,需要随机访问。比如栈队列底层容器。
注意:不要拿deque做大量中间插入

3. list 双向链表

头文件:

#include <list>

底层原理

双向循环链表,每个节点存:数据、前驱指针、后继指针。内存不连续。

特点

1.任意位置插入删除 O (1),前提迭代器已经定位到该位置;如果遍历找位置时间为O(n)。
2.不支持随机访问!不能 [] 下标访问,只能迭代器遍历。访问第 n 个元素必须一步步走 O (n)。
3.插入元素:不会导致迭代器失效!(只有删除那个被删节点的迭代器失效,其他全部有效)

重点区别 vector!list insert 不会废掉别的迭代器。

特有 API

list<int> L;
L.push_front(1);
L.push_back(2);
L.pop_front();
L.pop_back();
L.remove(5); //删除所有等于5的元素,list特有
L.sort();    //list自带排序,不要用std::sort!std::sort需要随机访问迭代器,list不支持
L.reverse(); //反转链表
L.splice(pos,other); //把另一个list全部挪到pos位置,转移,不拷贝,极高效率
//splice重载:L.splice(pos, other, it);移动单个节点;L.splice(pos, other, b, e);移动区间
适用:频繁任意位置插入删除,很少随机读取。
如果需要大量按下标拿元素,不要选 list。

4. forward_list 单向链表 C++11

头文件:

<forward_list>

底层原理

单向链表,只支持向前迭代,没有 back,没有 push_back,只有头插。用的很少。简单了解即可。

5. array 静态数组 C++11

头文件

#include <array>

底层:编译期确定大小的数组,内存一般在栈上,大小是模板参数,运行时不能改变。
特点:支持随机访问;不能增删元素,容量固定。

array<int,5> arr; //第二个模板参数为数组大小

三、关联式容器(底层:红黑树,自动排序,key 有序)

红黑树:平衡二叉查找树。插入删除查找都是 O (logn)。容器内部自动按照 key 从小到大排序。
分为 set 系列(key 就是值) map 系列(key‑value 键值对)

1. set 集合/multiset

头文件

#include <set>

底层原理

红黑树,节点存放元素本身,key=value。

特点

1.set:key 不允许重复;multiset 允许 key 重复。
2.容器内部自动按照 key 升序排序,和插入顺序无关。
3.查找、插入、删除:稳定 O(logn)。
4.迭代器是双向迭代器:支持++ --,不支持 it+n、it‑n、[] 随机访问。不能按下标取元素。
5.迭代器失效规则

  • 插入 insert:所有原有迭代器全部不会失效,只是新增节点。

  • erase 删除:只有被删除节点的迭代器失效,其他迭代器依然有效。

  • set内元素为const T,不可以直接修改容器里面的元素值,修改会打乱红黑树有序规则,属于未定义行为。如果要改:先删除旧值,再插入新值。

⚠️multiset大坑:s.erase(值)会删除全部等于该值的元素;s.erase(迭代器)仅删除单个元素。

set<int> s;
s.insert(3);
s.insert(1);
s.insert(2);
//遍历输出:1 2 3,自动排好序,不管插入顺序

常用API

s.insert(val); //插入;如果元素已经存在,插入失败,不报错
s.erase(val);  //删除值,也可以传迭代器
s.clear();
s.size();
s.find(val); //查找val,返回迭代器;找不到返回s.end()
s.count(val); //统计该元素个数。set只能0或者1;multiset可以大于1
s.lower_bound(x); //第一个≥x的元素迭代器
s.upper_bound(x); //第一个>x的元素迭代器
multiset:#include <set>,允许重复元素,其余 API 和 set 一模一样。

2.map/multimap

头文件:

#include <map>

底层

红黑树,节点存pair<const Key,T>。key 用来排序,value 是附属数据。

特点

1.map:key 唯一不重复;multimap 允许 key 重复。
2.根据key 自动升序排序,value 不参与排序。
3.查找、插入、删除稳定 O(logn)。
4.迭代器是双向迭代器,支持 ++、--,不支持随机访问;map的[]不是下标运算符,是重载的键访问运算符,multimap没有 [] 运算符
5.迭代器失效规则和 set 完全一样:

  • insert 插入:原有迭代器全部有效,不失效。

  • erase 删除:仅被删除节点迭代器失效,其他迭代器正常。

6.大坑:[]运算符

  • map 支持mp[key]=value;multimap没有 [] 运算符,key 重复,不知道操作哪一个 pair。

  • 如果[]传入不存在的 key,会自动插入这个 key,value 默认构造。只读查询绝对不要用 [],要用 find ()

注意:pair中key是const,不能修改key;value可以正常修改。

常用API

map<int,string> mp;

//插入三种方式
mp.insert( make_pair(1,"张三") );
mp.insert( {2,"李四"} );
mp[3]="王五"; //[]运算符,如果key不存在:自动创建key,默认构造value;存在则修改value!

//查找
mp.find(1); //返回迭代器,找不到end()
mp.count(1); //返回1存在,0不存在

//删除
mp.erase(1); //按key删除
mp.erase(it);

//取值
cout<<mp[1];

四、无序容器(哈希表 C++11 unordered_xxx)

头文件:

#include <unordered_set>
#include <unordered_map>

容器:

unordered_set unordered_multiset unordered_map unordered_multimap

底层

哈希表(链地址法)

  • 时间复杂度:平均查找插入删除 O (1);最坏退化 O (n) 哈希冲突。

  • 不会排序!元素顺序乱七八糟,和插入顺序无关

set/map:有序 O (logn);unordered:无序平均 O (1)。
如果不需要有序优先选 unordered 速度更快。

迭代器失效规则

  • rehash哈希表扩容:全部迭代器失效

  • 插入不触发rehash:迭代器不失效

  • erase:仅被删除节点迭代器失效

自定义类型作为key需要两个条件:提供哈希函数,重载==;内置int、string无需手动处理。

常用API

API 几乎和 set/map 完全一模一样。
区别:没有 lower_bound upper_bound,哈希表没有有序概念。

unordered_map/unordered_set,自定义类型需要自己提供哈希函数;内置 int string 不用管。

1.需要元素自动排序 → set /map
2.不需要排序,追求速度 → unordered_set /unordered_map

五、四大容器对比表

1.vector:连续数组,随机访问快;头尾快中间慢;扩容迭代器失效。适合随机访问。
2.deque:分段内存,头尾都快;可随机访问;中间慢。
3.list:双向链表,任意位置插入删除快;不能随机访问。
4.map/set:红黑树,自动排序 O (logn);
5.unordered_map/unordered_set:哈希表无序,平均 O (1)。

六、容器适配器(不是容器!包装已有容器)

适配器:对已有容器封装一层,改变接口,不实现底层数据结构。

1.stack 栈

头文件

<stack>

后进先出 LIFO

默认底层容器 deque,可以手动指定底层容器。

stack<int> st;
//示例指定底层vector: stack<int,vector<int>> st;
st.push(10); //入栈
st.pop();    //出栈,无返回值
st.top();    //栈顶元素
st.empty();
st.size();

stack没有迭代器,不能遍历!

2.queue 队列

头文件:

<queue>

先进先出 FIFO

默认底层 deque,可以手动指定底层容器。

queue<int> q;
//示例指定底层list:queue<int,list<int>> q;
q.push(1);
q.pop();
q.front(); //队头
q.back();  //队尾

queue 也没有迭代器,不能遍历。

3.priority_queue 优先队列(大顶堆)

头文件:

<queue>

底层默认 vector 实现堆,底层依靠堆算法make_heap/push_heap/pop_heap。

默认:最大值在堆顶 top ()。模板参数:元素类型,底层容器,比较仿函数。

⚠️注意:priority_queue仿函数逻辑与std::sort相反。

priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.push(5);
pq.top(); //得到5,最大元素
pq.pop();

小顶堆写法:

priority_queue<int,vector<int>,greater<int>> min_heap;

priority_queue 底层堆结构,内部自动维护大小顺序。不支持完整遍历。

底层容器必须支持随机访问迭代器,不能使用list。

七、迭代器分类

1.输入迭代器:只读,单向。istream_iterator
2.输出迭代器:只写,单向。ostream_iterator
3.前向迭代器:只能 ++;forward_list。
4.双向迭代器:支持 ++ --;list set map。不支持随机加减 it+n
5.随机访问迭代器:支持 it+n、it‑n、[]、大小比较> <;vector deque array。

std::sort 算法要求随机访问迭代器,所以 list 不能用 std::sort,list 自带 sort 成员函数。

八、STL 常用算法

头文件:

<algorithm>

算法不操作容器,操作迭代器!算法和容器解耦。

sort(begin,end) //排序,[begin,end) 左闭右开区间;随机访问迭代器容器 (vector/deque) 才能用。默认升序,内省排序,不稳定。
stable_sort(begin,end) //稳定排序
find(begin,end,val) //查找元素,返回迭代器。找不到 end ()。
find_if(begin,end,条件) //按条件查找
reverse(begin,end) //反转区间
copy(b1,e1,b2) //复制区间 [b1,e1) 到 b2 开始位置
for_each(begin,end,函数) //遍历每一个元素执行函数
remove(begin,end,val) //把指定值移到末尾,**不会真正删除元素,不改变size,返回新的尾后迭代器**
remove_if(begin,end,条件) //把满足条件元素移到末尾

✨erase‑remove惯用法,vector删除条件元素必考

//删除所有等于3的元素
v.erase(remove(v.begin(),v.end(),3), v.end());

list不要使用std::remove,直接调用成员remove()。

set/map 不要用 std::find!自带成员 find 效率 O (logn);std::find 是遍历 O (n)。优先用容器自带.find ()。

九、坑点速记

1..end()是末尾后一位,不能解引用 *v.end()程序直接崩溃。
2.vector 的[]越界不报错,at () 越界抛异常。
3.vector insert/push_back 扩容会全部迭代器失效。erase 之后迭代器失效,接收 erase 返回值。
4.map 的[]读取不存在 key 会自动插入数据,只读用 find。
5.multiset/multimap 允许重复 key,multimap 没有 []。
6.multiset调用erase(值)删除全部匹配元素;erase(迭代器)只删除单个元素。
7.list 不要用 std::sort,使用自带.sort ()。
8.stack queue priority_queue 没有迭代器,不能 for 遍历。
9.set/map 内部 key 是 const,不能修改 key 的值。
10.unordered 容器无序,如果业务需要有序,不要用哈希容器;发生rehash时全部迭代器失效。
11.emplace_back /emplace 原地构造,减少对象拷贝,优先使用代替 push_back。
12.std::remove / remove_if仅挪动元素,不会改变容器size,要配合erase才能真正删除。
13.priority_queue比较仿函数逻辑和sort相反。

十、容器选型速记

1.需要大量随机访问,尾部增删 → vector,记得 reserve 预分配优化
2.头尾频繁插入删除,要随机访问 → deque
3.任意位置频繁插入删除,几乎不按下标访问 → list
4.需要自动排序,查找,key‑value → map;存单纯值 set
5.不需要排序,追求最快查找速度 → unordered_map /unordered_set
6.后进先出栈 stack;先进先出队列 queue;取最大最小优先队列 priority_queue

posted @ 2026-08-31 10:51  烟雨楼10  阅读(7)  评论(0)    收藏  举报