STL 容器与原生数组的 API 速查表,按“查得到、抄得来”组织,覆盖构造、访问、容量、修改、查找。原理(内存布局、扩容机制、迭代器失效的根因、选型权衡)见 C++ STL 容器 ,内存分配细节见 C++ STL Allocator 。本文只列怎么用。
容器总览 容器 头文件 底层 迭代器类别 随机访问 T[N] 原生数组- 连续 指针(随机) ✓ array<array>固定数组 随机访问 ✓ vector<vector>动态数组 随机访问 ✓ deque<deque>分段连续 随机访问 ✓ list<list>双向链表 双向 ✗ forward_list<forward_list>单向链表 前向 ✗ set / multiset<set>红黑树 双向 ✗ map / multimap<map>红黑树 双向 ✗ unordered_set/map<unordered_*>哈希表 前向 ✗ stack<stack>适配器 — — queue<queue>适配器 — — priority_queue<queue>堆 — —
array/vector/deque/string/原生数组是连续内存 ,cache 友好;链表与节点容器不连续。适配器不暴露迭代器。
全容器通用接口 除适配器外,标准容器共享这套接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 C c; C c2 (c) ; C c3 (std::move(c2)) ; C c4 (other.begin(), other.end()) ; C c5{1 , 2 , 3 }; c = c4; c = std::move (c4); c = {1 , 2 , 3 }; c.empty (); c.size (); c.max_size (); c.clear (); c.swap (other); std::swap (c, other); c.begin (); c.end (); c.cbegin (); c.cend (); c.rbegin (); c.rend ();
forward_list 特立独行:没有 size()(为省一个指针),用 insert_after 系列代替 insert。
原生数组(C 风格) std::array 的老底:自身不动态分配(存放位置随变量而定)、零开销,但类型系统弱、易退化、边界不查。能用 std::array 就别用原生数组;以下补它仍常见时的用法。
声明与初始化 1 2 3 4 5 6 int a[4 ]; int b[4 ]{}; int c[4 ]{1 , 2 , 3 , 4 }; int d[4 ]{1 , 2 }; int e[] = {1 , 2 , 3 }; char s[] = "hi" ;
大小必须是编译期常量;int a[4]; 不加 {} 则局部内容未定义。C99 变长数组(VLA)不是标准 C++。
访问与大小 1 2 3 4 5 6 7 8 9 #include <iterator> a[0 ]; a[2 ] = 9 ;sizeof (a); sizeof (a) / sizeof (a[0 ]); std::size (a); std::ssize (a); std::begin (a); std::end (a);
sizeof/std::size 仅在数组未退化 时正确;传入函数后退化成指针,三者全部失灵。
退化:头号陷阱 数组名在多数语境退化为首元素指针,长度信息丢失:
1 2 3 4 5 6 7 8 int a[4 ]{1 , 2 , 3 , 4 };int * p = a; auto x = a; auto & r = a; void f (int arr[]) ; void f (int arr[4 ]) ; f (a);
遍历 1 2 3 4 for (size_t i = 0 ; i < std::size (a); ++i) a[i];for (int x : a) { }for (int & x : a) { x *= 2 ; }
多维数组 行优先、连续存储;除第一维外大小必须固定。
1 2 3 4 int m[2 ][3 ]{{1 , 2 , 3 }, {4 , 5 , 6 }}; m[0 ][1 ]; int (*row)[3 ] = m; void g (int arr[][3 ], int n) ;
int m[][] 非法。
安全传参 退化让“数组参数”形同虚设。三种姿势,长度保障从弱到强:
1 2 3 4 5 6 7 8 9 10 11 #include <span> void f0 (int * a, size_t n) ; void f1 (int (&a)[4 ]) ; void f2 (std::span<const int > a) ; int a[4 ]{1 , 2 , 3 , 4 };f0 (a, 4 );f1 (a);f2 (a); std::span<int > s{a, 4 };
std::span 是“连续内存 + 长度”的零拷贝视图,可接受原生数组、std::array、std::vector、std::string。
自由函数:统一访问接口 容器有成员函数(c.begin()),原生数组没有。<iterator> 的自由函数 对二者一视同仁,是泛型代码的基础;范围 for 正是靠它展开。
begin / end 家族 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iterator> int a[4 ]{1 , 2 , 3 , 4 }; std::vector<int > v{1 , 2 , 3 }; std::begin (a); std::end (a); std::begin (v); std::cbegin (a); std::cend (a); std::rbegin (a); std::rend (a); std::crbegin (a); std::crend (a);
对容器等价于成员函数;对数组返回首/尾后指针。写泛型代码统一用自由函数。
size / ssize / empty / data 1 2 3 4 5 6 7 std::size (a); std::size (v); std::ssize (a); std::empty (a); std::empty (v); std::data (a); std::data (v);
std::ssize 专治无符号下溢:
1 2 3 4 5 for (size_t i = 0 ; i < v.size () - 1 ; ++i) { }for (ptrdiff_t i = 0 ; i < std::ssize (v) - 1 ; ++i) { }
next / prev / advance / distance 迭代器算术,代价随迭代器类别变化:
1 2 3 4 5 std::advance (it, n); auto it2 = std::next (it); auto it3 = std::next (it, 3 );auto it4 = std::prev (it); auto d = std::distance (b, e);
随机访问 O(1),其余按步数 O(n)。
泛型示例 1 2 3 4 5 6 7 8 9 10 11 12 template <class R >void print (const R& r) { for (auto it = std::begin (r); it != std::end (r); ++it) std::cout << *it << ' ' ; }int a[4 ]{1 , 2 , 3 , 4 }; std::vector<int > v{5 , 6 , 7 }; std::list<int > l{8 , 9 };print (a); print (v);print (l);
范围 for(for (auto x : r))即用 std::begin/std::end 展开,故对数组同样有效。
序列容器 std::array:固定大小数组 自身不动态分配,存放位置随变量而定:局部在栈、static/全局在静态区、new 出来的在堆。零开销,大小是类型的一部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <array> std::array<int , 4> a{1 , 2 , 3 , 4 }; std::array b{1.0 , 2.0 }; a[0 ]; a.at (2 ); a.front (); a.back (); a.data (); a.size (); a.empty (); a.fill (0 ); std::get <0 >(a); a.swap (b);
没有 push_back/insert/erase/resize——大小编译期固定。
std::vector:默认选择 连续内存,尾端均摊 O(1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <vector> std::vector<int > v;std::vector<int > v1 (n) ; std::vector<int > v2 (n, 7 ) ; std::vector<int > v3{1 , 2 , 3 };std::vector<int > v4 (v3. begin(), v3. end()) ; v[0 ]; v.at (2 ); v.front (); v.back (); v.data (); v.size (); v.capacity (); v.reserve (1000 ); v.shrink_to_fit (); v.resize (10 ); v.resize (10 , -1 ); v.push_back (9 ); v.emplace_back (9 ); v.pop_back (); v.insert (v.begin (), 0 ); v.insert (v.begin (), 3 , 0 ); v.insert (v.begin (), v3. begin (), v3. end ()); v.emplace (v.begin (), 0 ); v.erase (v.begin ()); v.erase (v.begin (), v.begin () + 2 ); v.assign ({1 , 2 }); v.assign (n, 7 ); v.swap (v2); v.clear ();
C++23 范围插入:append_range / assign_range / insert_range(pos, rng)。 C++20 统一删除:std::erase(v, 7) 删所有等于 7;std::erase_if(v, pred)。
std::vector<bool>:特化版本 std::vector<bool> 是标准库的显式特化 ,非普通 vector:为省空间把每个 bool 压成 1 bit 紧凑存储,代价是行为与 vector<T> 不一致。
1 2 3 4 5 6 7 8 std::vector<bool > b{true , false , true }; b[0 ]; bool x = b[0 ]; auto y = b[0 ]; auto & r = b[0 ]; bool * p = &b[0 ]; b.flip ();
关键差异与陷阱:
operator[]/at()/front()/back() 返回代理对象 reference,非 bool&:不可取地址、不可绑到 bool&。无连续 bool 数组,不满足 ContiguousContainer,不能传给 C 风格 bool* 接口。 数据竞争 :同一字节内不同位非独立,多线程并发写相邻元素即 race(下标不同也算)。flip() 翻转全部位;static swap(r1, r2) 交换两个位引用。需要“真”布尔数组时改用:
1 2 3 4 std::vector<char > v (n, 1 ) ; std::array<bool , N> a; std::bitset<N> bits; boost::dynamic_bitset<> db;
经验:除非明确要省内存,否则别用 vector<bool>;要位操作选 bitset,要当数组用 vector<char>。
std::deque:双端队列 两端 O(1) 插删,随机访问 O(1) 但慢于 vector。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <deque> std::deque<int > dq{1 , 2 , 3 }; dq[0 ]; dq.at (1 ); dq.front (); dq.back (); dq.push_back (4 ); dq.push_front (0 ); dq.emplace_back (4 ); dq.emplace_front (0 ); dq.pop_back (); dq.pop_front (); dq.insert (dq.begin (), 9 ); dq.erase (dq.begin ()); dq.resize (5 ); dq.assign ({7 , 8 });
中间插删 O(n)。
std::list / forward_list:链表 list 双向,forward_list 单向(更省内存)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <list> #include <forward_list> std::list<int > l{1 , 2 , 3 }; std::forward_list<int > fl{1 , 2 , 3 }; l.front (); l.back (); fl.front (); l.push_back (4 ); l.push_front (0 ); fl.push_front (0 ); l.pop_back (); l.pop_front (); fl.pop_front (); l.insert (it, 9 ); l.insert (it, 3 , 9 ); l.insert (it, l2. begin (), l2. end ()); l.emplace (it, 9 ); l.erase (it); l.erase (first, last);
链表特有 O(1) 整表操作(forward_list 对应有 _after 版本):
1 2 3 4 5 6 7 8 9 l.splice (it, l2); l.splice (it, l2, it2); l.splice (it, l2, first, last); l.remove (9 ); l.remove_if (pred); l.unique (); l.merge (l2); l.sort (); l.reverse ();
forward_list 用 insert_after/emplace_after/erase_after/splice_after,配合 before_begin() 取得"首前"位置。
关联容器 有序,红黑树,查找/插删 O(log n)。
set / multiset 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <set> std::set<int > s{3 , 1 , 2 }; std::multiset<int > ms{1 , 1 , 2 }; s.find (2 ); s.contains (2 ); s.count (2 ); s.lower_bound (2 ); s.upper_bound (2 ); s.equal_range (2 ); s.insert (4 ); s.emplace (4 ); s.erase (2 ); s.erase (it); s.extract (2 ); s.merge (s2);
multiset::erase(key) 删除全部 匹配;只删一个用 erase(it)。自定义序:std::set<int, std::greater<>>。
map / multimap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <map> std::map<std::string, int > m{{"a" , 1 }, {"b" , 2 }}; std::multimap<std::string, int > mm; m["a" ] = 1 ; m.at ("a" ); m.find ("a" ); m.contains ("a" ); m.count ("a" ); m.lower_bound ("a" ); m.upper_bound ("a" ); m.equal_range ("a" ); m.insert ({"c" , 3 }); m.emplace ("c" , 3 ); m.insert_or_assign ("c" , 3 ); m.try_emplace ("c" , 3 ); m.erase ("a" ); m.extract ("a" );for (auto & [k, v] : m) { }
multimap 一对多,无 operator[] 与 at();按 key 取区间用 equal_range。
无序关联容器 哈希表,平均 O(1),最坏 O(n)(冲突/rehash)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <unordered_set> #include <unordered_map> std::unordered_set<int > us{1 , 2 , 3 }; std::unordered_map<std::string, int > um; um["a" ] = 1 ; us.find (2 ); us.contains (2 ); us.count (2 ); us.insert (4 ); us.emplace (4 ); us.erase (2 ); us.bucket_count (); us.load_factor (); us.max_load_factor (); us.rehash (100 ); us.reserve (100 );
接口与有序容器基本一致,但没有 lower_bound/upper_bound。unordered_map 同样有 []/at/try_emplace/insert_or_assign。
自定义 key 必须提供哈希与相等判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct Point { int x, y; };struct PHash { size_t operator () (const Point& p) const noexcept { return size_t (p.x) * 31 + p.y; } };struct PEq { bool operator () (const Point& a, const Point& b) const noexcept { return a.x == b.x && a.y == b.y; } }; std::unordered_map<Point, int , PHash, PEq> grid;
容器适配器 包装底层容器(默认 deque),只暴露受限接口,不可遍历。
stack:LIFO 1 2 3 4 5 6 7 8 9 10 11 #include <stack> std::stack<int > st; std::stack<int , std::vector<int >> sv; st.push (1 ); st.emplace (1 ); st.pop (); st.top (); st.empty (); st.size ();
queue:FIFO 1 2 3 4 5 6 7 8 9 10 11 #include <queue> std::queue<int > q; q.push (1 ); q.emplace (1 ); q.pop (); q.front (); q.back (); q.empty (); q.size ();
priority_queue:堆 默认大根堆,top() 是最大元素。
1 2 3 4 5 6 7 8 9 10 11 12 #include <queue> std::priority_queue<int > pq; std::priority_queue<int , std::vector<int >, std::greater<>> min_pq; pq.push (3 ); pq.emplace (3 ); pq.pop (); pq.top (); pq.empty (); pq.size ();
无 front/back,无遍历。自定义类型需提供 operator<(或传入比较器)。
迭代器失效速查 对容器的修改可能让已有迭代器/引用/指针失效,再用即 UB。
容器 插入 删除 vector / string扩容则全部失效;否则插入点之后失效 删除点及之后失效(含 end) deque两端插:仅 end 失效;中间插:全部失效 两端删:被删与 end 失效;中间删:全部失效 list / forward_list不失效其他 仅被删元素失效 set / map 等不失效 仅被删元素失效 unordered_*rehash 则全部失效;否则不失效 仅被删元素失效(rehash 期间除外)
边遍历边删的正确写法:
1 2 3 4 5 6 7 for (auto it = v.begin (); it != v.end ();) if (*it % 2 == 0 ) it = v.erase (it); else ++it; std::erase_if (v, [](int x) { return x % 2 == 0 ; });
复杂度速查 操作 vector deque list set/map unordered_* 随机访问 [] O(1) O(1) — — — 头部插入 O(n) O(1) O(1) — — 尾部插入 均摊 O(1) O(1) O(1) — — 中间插入 O(n) O(n) O(1)* O(log n) 均摊 O(1) 按 key 查找 O(n) O(n) O(n) O(log n) 均摊 O(1) 排序 O(n log n) — O(n log n) 已序 —
* list 中间插入 O(1) 仅指"已知位置插入",找位置仍 O(n)。
选型决策 1 2 3 4 5 6 7 8 9 10 11 12 13 要随机访问 / 连续内存? ├─ 大小编译期已知 - > array ├─ 默认 - > vector └─ 两端都要进出 - > deque 要按键查找? ├─ 需要有序 / 范围查询 - > map / set └─ 只要平均最快 - > unordered_map / unordered_set 要在已知位置频繁增删, 且不能失效其他迭代器? - > list / forward_list 要栈/ 队列/ 优先队列? - > stack / queue / priority_queue
经验法则:默认 vector;按 key 查表默认 unordered_map;大小固定用 array;除非真需要"增删不失效其他迭代器",否则避免 list。批量操作优先 assign/insert(range)/append_range 而非循环 push_back,一次 reserve 避免多次扩容。