一、什么是容器?为什么它如此重要? 简单来说,C++ 容器是用来存储和管理一组对象的类模板 。它们就像是工具箱里不同种类的收纳盒:有的适合快速取放(像数组),有的适合保持物品有序(像分类文件柜),还有的适合快速查找(像带索引的卡片盒)。
使用 STL 容器的好处是巨大的:
代码更简洁 :你无需从零开始实现链表、哈希表等复杂数据结构。
性能更优越 :STL 容器经过了高度优化,性能通常比手写的要好。
代码更安全 :它们处理了内存管理的细节,减少了内存泄漏和越界访问的风险。
互操作性强 :所有容器都遵循一套统一的接口(如 begin()
, end()
, size()
),可以与 STL 的迭代器 和算法 无缝协作,威力倍增。
二、容器的分类与概览 C++ 的容器主要分为四大类,分别是序列容器、关联容器、无序关联容器和容器适配器。
类别
容器
特点
序列容器
vector
, deque
, list
, array
元素按线性顺序排列,可通过位置访问。
关联容器
map
, set
, multimap
, multiset
元素根据键值自动排序,查找速度快 (O(log N))。
无序关联容器
unordered_map
, unordered_set
, …
元素通过哈希存储,无序,查找速度极快 (平均 O(1))。
容器适配器
stack
, queue
, priority_queue
提供特定接口(LIFO/FIFO),是对其他容器的封装。
三、序列容器 (Sequence Containers) 序列容器中的元素按严格的线性序列排序,你可以通过元素在序列中的位置来访问它们。
1. std::vector
— 动态数组 vector
是最基本也是最常用的容器,是你的“默认选项”。
头文件 : #include <vector>
本质 : 一块在堆上分配的连续内存空间 。当空间不足时,会自动重新分配一块更大的内存,并将原有元素移动过去。
复杂度 :
随机访问 (通过 []
或 at()
): O(1)
尾部添加/删除 (push_back
, pop_back
): 均摊 O(1)
中间或头部添加/删除 (insert
, erase
): O(N)
适用场景 :
需要快速随机访问元素。
主要在容器末尾进行添加或删除操作。
在大多数情况下,当你需要一个动态数组时,vector
都是最佳选择。
示例代码 :
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <iostream> #include <vector> #include <string> void print_vector (const std::string& title, const std::vector<int >& v) { std::cout << title << ":\t[ " ; for (int i : v) std::cout << i << " " ; std::cout << "], size=" << v.size () << ", capacity=" << v.capacity () << std::endl; } int main () { std::vector<int > vec; std::vector vec2 = {1 , 2 , 3 , 4 , 5 }; std::vector<int > vec3 (5 ) ; std::vector<int > vec4 (5 , 2 ) ; print_vector ("Initial" , vec); print_vector ("Initial vec2" , vec2); print_vector ("Initial vec3" , vec3); print_vector ("Initial vec4" , vec4); vec.push_back (10 ); vec.push_back (20 ); vec.push_back (30 ); print_vector ("After push_back" , vec); std::cout << "Element at index 1: " << vec.at (1 ) << std::endl; vec.insert (vec.begin () + 1 , 15 ); print_vector ("After insert" , vec); vec.erase (vec.begin () + 2 ); print_vector ("After erase" , vec); vec.erase (vec.begin () + 2 , vec.end ()); print_vector ("After erase from index 2 to end" , vec); vec.pop_back (); print_vector ("After pop_back" , vec); }
2. std::deque
— 双端队列 deque
(double-ended queue) 是一个在两端操作都非常高效的序列。
头文件 : #include <deque>
本质 : 由多个独立的小内存块(chunks)和一箇中控的映射结构组成,非连续内存 。
复杂度 :
随机访问: O(1) (但比 vector
慢,需要两次指针解引用)
头部和尾部添加/删除 (push_front
, pop_front
, push_back
, pop_back
): O(1)
中间添加/删除: O(N)
适用场景 :
需要在容器的两端 进行频繁的插入和删除操作。
例如,实现一个任务队列,既可以从队首取任务,也可以在队尾或队首添加新任务。
示例代码 :
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 #include <iostream> #include <deque> #include <string> void print_deque (const std::string& title, const std::deque<int >& d) { std::cout << title << ":\t[ " ; for (int i : d) std::cout << i << " " ; std::cout << "], size=" << d.size () << std::endl; } int main () { std::deque<int > deq = {10 , 20 , 30 }; print_deque ("Initial" , deq); deq.push_back (40 ); print_deque ("After push_back(40)" , deq); deq.push_front (5 ); print_deque ("After push_front(5)" , deq); deq.pop_front (); print_deque ("After pop_front()" , deq); }
3. std::list
— 双向链表 list
专为在任意位置进行快速插入和删除而设计。
头文件 : #include <list>
本质 : 一个双向链表 ,每个节点包含元素值以及指向前一个和后一个节点的指针。
复杂度 :
随机访问: O(N) (不支持 []
或 at()
)
任意位置添加/删除: 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 #include <iostream> #include <list> #include <string> void print_list (const std::string& title, const std::list<int >& l) { std::cout << title << ":\t[ " ; for (int i : l) std::cout << i << " " ; std::cout << "], size=" << l.size () << std::endl; } int main () { std::list<int > myList = {10 , 20 , 40 }; print_list ("Initial" , myList); auto it = myList.begin (); ++it; myList.insert (it, 15 ); print_list ("After insert" , myList); it = myList.begin (); ++it; ++it; myList.erase (it); print_list ("After erase" , myList); }
4. std::array
— 固定大小数组 std::array
是对 C 风格静态数组的轻量级、类型安全的封装。
头文件 : #include <array>
本质 : 一块在栈上或静态存储区 分配的固定大小的连续内存空间 。它的大小在编译时 就必须确定,之后不能改变。它本质上就是对 C 语言数组 T arr[N]
的一个包装。
复杂度 :
随机访问 (通过 []
或 at()
): O(1)
不支持任何改变其大小的操作(如 push_back
, insert
, erase
等)。
适用场景 :
当你需要一个大小在编译时就已知 的固定长度数组时。
相比 C 风格数组,std::array
提供了容器的通用接口(如 size()
, begin()
, end()
, empty()
),可以无缝地与 STL 算法(如 std::sort
)配合使用。
它不会退化为指针,作为函数参数传递时会传递整个数组(或其引用),保留了大小信息,更加安全。
示例代码 :
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> #include <array> #include <string> #include <algorithm> void print_array (const std::array<int , 5 >& arr) { std::cout << "Array contents: [ " ; for (int element : arr) { std::cout << element << " " ; } std::cout << "], size=" << arr.size () << std::endl; } int main () { std::array<int , 5> myArray = {30 , 10 , 50 , 20 , 40 }; std::cout << "Initial state:" << std::endl; print_array (myArray); myArray[0 ] = 35 ; std::cout << "Element at index 2: " << myArray.at (2 ) << std::endl; std::sort (myArray.begin (), myArray.end ()); std::cout << "\nAfter sorting:" << std::endl; print_array (myArray); myArray.fill (0 ); std::cout << "\nAfter filling with 0:" << std::endl; print_array (myArray); }
四、关联容器 (Associative Containers) 关联容器中的元素根据它们的键自动排序,提供了高效的查找能力。
1. std::map
— 有序键值对
头文件 : #include <map>
本质 : 通常是红黑树 (一种自平衡二叉搜索树)。存储 std::pair<const Key, T>
类型的键值对,键是唯一的,并根据键自动排序。
复杂度 :
插入、删除、查找 (insert
, erase
, find
, count
): O(log N)
适用场景 :
需要存储唯一的键值对,并希望它们根据键自动排序。
需要高效地根据键查找、更新或删除对应的值。
例如,存储字典、配置文件、学生ID和姓名的映射等。
示例代码 :
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 #include <iostream> #include <map> #include <string> int main () { std::map<std::string, int > student_ages; student_ages["Alice" ] = 21 ; student_ages.insert (std::make_pair ("Bob" , 22 )); student_ages.emplace ("Charlie" , 20 ); std::cout << "Bob's age is: " << student_ages.at ("Bob" ) << std::endl; std::cout << "All students:\n" ; for (const auto & pair : student_ages) { std::cout << " - " << pair.first << " is " << pair.second << " years old.\n" ; } student_ages.erase ("Alice" ); std::cout << "Number of students after erasing Alice: " << student_ages.size () << std::endl; }
2. std::set
— 有序唯一集合
头文件 : #include <set>
本质 : 同样是红黑树 。只存储键,没有值。可以看作是一种只有键的 map
。元素是唯一的,并自动排序。
复杂度 :
适用场景 :
需要存储一组唯一的元素,并保持它们有序。
需要快速判断一个元素是否存在于集合中。
例如,存储不重复的用户ID、统计文章中出现的不同单词等。
示例代码 :
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 #include <iostream> #include <set> #include <string> int main () { std::set<std::string> unique_words; unique_words.insert ("hello" ); unique_words.insert ("world" ); unique_words.insert ("hello" ); std::cout << "Unique words count: " << unique_words.size () << std::endl; if (unique_words.count ("world" )) { std::cout << "'world' is in the set." << std::endl; } std::cout << "Words in set:\n" ; for (const auto & word : unique_words) { std::cout << " - " << word << std::endl; } }
3. std::multimap
— 有序多重映射 在 std::map
和 std::set
的基础上,STL 提供了它们的多键版本,允许存储重复的键。multimap
是 map
的变体,它允许存储具有相同键的多个键值对。
头文件 : #include <map>
本质 : 与 std::map
一样,通常是红黑树 。它存储的也是 std::pair<const Key, T>
,但允许键 (Key) 重复 。所有元素仍然会根据键自动排序。
复杂度 :
插入 (insert
): O(log N)
删除 (erase
): O(log N + k) ,其中 k 是被删除的元素数量。
查找 (find
, count
, equal_range
): O(log N)
适用场景 :
需要表示**“一对多”**的关系。
例如,一个字典中一个单词可以有多个释义;一个学生可以注册多门课程;一个作者可以有多本著作。在这些场景下,键(单词、学生、作者)是相同的,但值(释义、课程、著作)不同。
示例代码 :
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> #include <map> #include <string> int main () { std::multimap<std::string, std::string> student_courses; student_courses.insert ({"Alice" , "Math101" }); student_courses.insert ({"Bob" , "CS101" }); student_courses.insert ({"Alice" , "Physics201" }); student_courses.insert ({"Charlie" , "History101" }); student_courses.insert ({"Alice" , "Chem101" }); std::cout << "Alice is enrolled in " << student_courses.count ("Alice" ) << " courses." << std::endl; std::cout << "\nCourses for Alice:" << std::endl; auto range = student_courses.equal_range ("Alice" ); for (auto it = range.first; it != range.second; ++it) { std::cout << " - " << it->second << std::endl; } std::cout << "\nAll enrollments:" << std::endl; for (const auto & pair : student_courses) { std::cout << pair.first << " -> " << pair.second << std::endl; } }
4. std::multiset
— 有序多重集合 multiset
是 set
的变体,它允许存储多个相同的元素。
头文件 : #include <set>
本质 : 与 std::set
一样,是红黑树 。它允许存储重复的元素 ,并且所有元素都会自动排序。
复杂度 :
插入 (insert
): O(log N)
删除 (erase
): O(log N + k) ,其中 k 是被删除的元素数量。
查找 (find
, count
): O(log N)
适用场景 :
需要存储一个可以有重复值的有序集合。
例如,存储一场比赛的所有得分(可能会有同分);对一组数字进行排序并保留所有重复项;或者需要快速统计集合中某个特定值的出现次数。
示例代码 :
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 35 36 37 38 39 #include <iostream> #include <set> #include <string> void print_multiset (const std::string& title, const std::multiset<int >& ms) { std::cout << title << ":\t[ " ; for (int score : ms) { std::cout << score << " " ; } std::cout << "]" << std::endl; } int main () { std::multiset<int > scores; scores.insert (88 ); scores.insert (95 ); scores.insert (73 ); scores.insert (88 ); scores.insert (95 ); print_multiset ("All scores (sorted)" , scores); int score_to_find = 88 ; std::cout << "The score " << score_to_find << " appears " << scores.count (score_to_find) << " times." << std::endl; scores.erase (95 ); print_multiset ("After erasing all 95s" , scores); }
五、无序关联容器 (Unordered Associative Containers) C++11 引入,基于哈希表实现,提供平均常数时间的查找性能,但元素无序。
1. std::unordered_map
— 哈希映射
头文件 : #include <unordered_map>
本质 : 哈希表 。存储唯一的键值对,但元素之间没有特定的顺序,其存储位置由键的哈希值决定。
复杂度 :
插入、删除、查找: 平均 O(1) , 最坏 O(N) (哈希冲突严重时)
适用场景 :
当你需要 map
的功能(键值对存储和快速查找),但不关心元素的顺序 时。
在性能至关重要的场景下,它通常是比 map
更快的选择。
示例代码 :
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 #include <iostream> #include <unordered_map> #include <string> int main () { std::unordered_map<std::string, std::string> capital_cities; capital_cities["China" ] = "Beijing" ; capital_cities["France" ] = "Paris" ; capital_cities.emplace ("Japan" , "Tokyo" ); std::cout << "The capital of France is: " << capital_cities["France" ] << std::endl; std::cout << "All entries:\n" ; for (const auto & pair : capital_cities) { std::cout << " - " << pair.first << ": " << pair.second << std::endl; } }
2. std::unordered_set
— 哈希集合
头文件 : #include <unordered_set>
本质 : 哈希表 。存储唯一的元素,无序。
复杂度 :
插入、删除、查找: 平均 O(1) , 最坏 O(N)
适用场景 :
需要快速地判断一个元素是否存在于一个集合中,且完全不关心顺序。
例如,用作访问记录,快速检查某个IP地址今天是否已经访问过。
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> #include <unordered_set> #include <string> int main () { std::unordered_set<int > visited_ids; visited_ids.insert (101 ); visited_ids.insert (205 ); visited_ids.insert (101 ); if (visited_ids.find (205 ) != visited_ids.end ()) { std::cout << "ID 205 has been visited." << std::endl; } std::cout << "Total unique visited IDs: " << visited_ids.size () << std::endl; }
六、容器适配器 (Container Adapters) 它们不是独立的容器,而是对现有序列容器(默认为 std::deque
)的接口进行封装,以提供特定的行为模式。
1. std::stack
— 栈
头文件 : #include <stack>
本质 : 封装了其他容器(默认 std::deque
)的后进先出 (LIFO) 接口。
复杂度 :
push
, pop
, top
: O(1) (依赖于底层容器的 push_back
, pop_back
, back
操作)
适用场景 :
需要后进先出行为的任何地方。
例如,函数调用栈、括号匹配、表达式求值等。
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <stack> int main () { std::stack<int > s; s.push (1 ); s.push (2 ); s.push (3 ); std::cout << "Top element: " << s.top () << std::endl; s.pop (); std::cout << "Top element after pop: " << s.top () << std::endl; std::cout << "Stack is " << (s.empty () ? "empty" : "not empty" ) << std::endl; }
2. std::queue
— 队列
头文件 : #include <queue>
本质 : 封装了其他容器(默认 std::deque
)的先进先出 (FIFO) 接口。
复杂度 :
push
, pop
, front
, back
: O(1) (依赖于底层容器的 push_back
, pop_front
, front
, back
操作)
适用场景 :
需要先进先出行为的任何地方。
例如,任务队列、广度优先搜索 (BFS)、打印机缓冲池等。
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <queue> int main () { std::queue<std::string> q; q.push ("First" ); q.push ("Second" ); q.push ("Third" ); std::cout << "Front of queue: " << q.front () << std::endl; q.pop (); std::cout << "Front after pop: " << q.front () << std::endl; }
3. std::priority_queue
— 优先队列
头文件 : #include <queue>
本质 : 封装了其他容器(默认 std::vector
)并使用堆 (heap) 算法来维护的结构。每次取出的都是当前队列中优先级最高的元素。
复杂度 :
push
: O(log N)
pop
: O(log N)
top
: O(1)
适用场景 :
需要不断处理当前集合中最大(或最小)的元素。
例如,事件调度、Dijkstra算法、A*搜索算法等。
示例代码 (默认最大堆) :
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 #include <iostream> #include <queue> #include <vector> #include <functional> int main () { std::priority_queue<int > max_heap; max_heap.push (30 ); max_heap.push (100 ); max_heap.push (20 ); std::cout << "Max element: " << max_heap.top () << std::endl; std::priority_queue<int , std::vector<int >, std::greater<int >> min_heap; min_heap.push (30 ); min_heap.push (100 ); min_heap.push (20 ); std::cout << "Min element: " << min_heap.top () << std::endl; }