目录
2.5 forward_list:单向链表的轻量版 (C++11)
3.3 unordered_set/unordered_multiset:哈希集合 (C++11)
3.4 unordered_map/unordered_multimap:哈希字典 (C++11)

class 卑微码农:
def __init__(self):
self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
self.发量 = 100 # 初始发量
self.咖啡因耐受度 = '极限'
def 修Bug(self, bug):
try:
# 试图用玄学解决问题
if bug.严重程度 == '离谱':
print("这一定是环境问题!")
else:
print("让我看看是谁又没写注释...哦,是我自己。")
except Exception as e:
# 如果try块都救不了,那就...
print("重启一下试试?")
self.发量 -= 1 # 每解决一个bug,头发-1
# 实例化一个我
我 = 卑微码农()
引言
为什么写这个?因为容器是C++程序员的“饭碗工具”。你想想,数组太原始,链表自己实现又麻烦,容器直接帮你搞定动态内存、迭代、排序等。学好了它,你的代码效率能翻倍。文章会从基础讲起,覆盖主要容器类型,每个都配示例,性能分析和使用场景。全文会很详尽,别急,慢慢看。
第一部分:C++容器概述
C++的标准模板库(STL)提供了丰富的容器类,这些容器本质上是封装了数据结构的模板类,帮助我们存储和管理元素。STL容器可以分为三大类:序列容器(sequence containers)、关联容器(associative containers)和容器适配器(container adapters)。
- 序列容器:按元素插入顺序存储,像vector、list、deque。这些适合需要按顺序访问的场景。
- 关联容器:基于键值或排序存储,像set、map、unordered_set。这些自动排序或哈希,查找快。
- 容器适配器:基于其他容器实现的简化版,像stack、queue、priority_queue。不支持迭代器,但操作简单。
所有容器都支持模板,所以可以存任何类型的数据,比如int、string或自定义类。但要注意,如果存自定义类,需要实现比较运算符或提供比较函数,尤其是关联容器。
容器头文件都在<algorithm>、 <vector> 等标准头里,使用前要#include。命名空间是std,所以常用using namespace std; 或 std::前缀。
我这些年用容器最多的地方是数据处理。比如在游戏中用vector存玩家列表,在数据库接口用map存键值对。记住,选对容器能让程序跑得飞起,选错就卡顿。
第二部分:序列容器详解
序列容器是STL的入门级选手,我们从最常见的vector开始。
2.1 vector:动态数组的王者
vector是我用得最多的容器,相当于可变长数组。底层是连续内存块,随机访问快,但插入删除中间元素时要移动数据,效率低。
头文件:#include <vector>
基本操作:
- 构造:vector<int> v; 或 vector<int> v(5, 10); // 5个10
- 大小:v.size() 返回元素数,v.capacity() 返回预分配空间。
- 访问:v[0] 或 v.at(0) (后者检查边界)。
- 添加:v.push_back(1); 尾部加,v.emplace_back(1); 就地构造。
- 删除:v.pop_back(); 尾部删,v.erase(it); 指定位置删。
- 迭代:for(auto& elem : v) 或使用begin()/end()。
性能:随机访问O(1),尾部插入O(1)摊销,中间插入O(n)。
适用场景:需要频繁随机访问或尾部操作的列表,比如缓冲区、日志记录。
示例代码:下面是个简单示例,模拟一个成绩管理系统。
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> scores; // 空vector
scores.push_back(90);
scores.push_back(85);
scores.push_back(95);
// 遍历输出
cout << "Scores: ";
for(size_t i = 0; i < scores.size(); ++i) {
cout << scores[i] << " ";
}
cout << endl;
// 插入到中间
auto it = scores.begin() + 1;
scores.insert(it, 88); // 现在是90,88,85,95
// 删除最后一个
scores.pop_back();
// 使用reserve预分配
scores.reserve(10); // 避免多次扩容
cout << "After modifications: ";
for(auto& s : scores) {
cout << s << " ";
}
cout << endl;
return 0;
}
运行这个,输出是Scores: 90 85 95 After modifications: 90 88 85
在实际项目中,我用vector存网络包数据,因为连续内存利于memcpy。注意,如果元素是对象,vector会调用拷贝构造,效率低时用unique_ptr包裹。
vector的容量管理是关键。当size==capacity时,push_back会 realloc,通常翻倍增长。但这可能导致迭代器失效,所以别在循环中随意插入。
常见坑:下标越界用[]会崩溃,用at()安全但慢。resize() vs reserve():前者改变size并初始化,后者只预分配。
扩展讲讲vector<bool>,这是个特化版,用bit存储,节省空间,但不支持引用返回,很多人踩坑。建议用vector<char>代替。
2.2 deque:双端队列的灵活选手
deque(double-ended queue)支持两端高效操作,底层是分块连续内存,像vector和list的混合。
头文件:#include <deque>
基本操作:
- 类似vector,但有push_front/pop_front。
- 访问:d[0] 或 d.front()/back()。
性能:两端插入O(1),随机访问O(1)但常数大,中间插入O(n)。
适用场景:需要前后端操作,如滑动窗口、队列模拟。
示例代码:实现一个最近使用列表(LRU cache简化版)。
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<string> recentFiles;
recentFiles.push_back("file1.txt");
recentFiles.push_back("file2.txt");
recentFiles.push_front("file0.txt"); // 加到前端
cout << "Recent files: ";
for(const auto& f : recentFiles) {
cout << f << " ";
}
cout << endl;
// 移除 oldest
recentFiles.pop_front();
// 添加新,检查大小
if(recentFiles.size() > 5) recentFiles.pop_front();
recentFiles.push_back("file3.txt");
cout << "Updated: ";
for(const auto& f : recentFiles) {
cout << f << " ";
}
cout << endl;
return 0;
}
输出:Recent files: file0.txt file1.txt file2.txt Updated: file1.txt file2.txt file3.txt
我曾在音频处理用deque存样本,因为能高效shift。deque不支持capacity,但有max_size()。
与vector比,deque随机访问慢点,但两端灵活。别用deque当stack/queue,用适配器更好。
2.3 list:双向链表的经典
list是双向链表,插入删除快,但随机访问慢。
头文件:#include <list>
基本操作:
- l.push_back(1); l.push_front(1);
- l.insert(it, 2); l.erase(it);
- 无[]访问,只有迭代器。
性能:插入删除O(1),访问O(n)。
适用场景:频繁插入删除,不需随机访问,如任务队列。
示例代码:模拟播放列表。
#include <iostream>
#include <list>
using namespace std;
int main() {
list<string> playlist = {"Song1", "Song2", "Song3"};
// 插入到指定位置
auto it = playlist.begin();
++it; // 指向Song2
playlist.insert(it, "Song1.5");
// 删除Song3
playlist.pop_back();
// 遍历
cout << "Playlist: ";
for(const auto& song : playlist) {
cout << song << " ";
}
cout << endl;
// splice合并另一个list
list<string> newSongs = {"Song4", "Song5"};
playlist.splice(playlist.end(), newSongs); // newSongs变空
cout << "After splice: ";
for(const auto& song : playlist) {
cout << song << " ";
}
cout << endl;
return 0;
}
输出:Playlist: Song1 Song1.5 Song2 After splice: Song1 Song1.5 Song2 Song4 Song5
list的splice是神器,能O(1)转移元素。我在多线程任务管理用过list,因为删除不失效其他迭代器。
注意,list迭代器是bidirectional,不支持+ n跳转。size()是O(1)在C++11后。
2.4 array:固定大小数组的现代版 (C++11)
array是固定大小的容器,封装了C数组。
头文件:#include <array>
基本操作:
- array<int, 5> a = {1,2,3,4,5};
- a[0], a.at(0), a.front()/back()。
性能:所有O(1),栈上分配。
适用场景:大小固定,如坐标点。
示例代码:
#include <iostream>
#include <array>
using namespace std;
int main() {
array<double, 3> point = {1.0, 2.0, 3.0};
cout << "Point: (" << point[0] << ", " << point[1] << ", " << point[2] << ")" << endl;
// 填充
point.fill(0.0);
cout << "After fill: (" << point.front() << ", " << point.at(1) << ", " << point.back() << ")" << endl;
return 0;
}
输出:Point: (1, 2, 3) After fill: (0, 0, 0)
array比C数组安全,有size()。我在嵌入式用它存配置。
2.5 forward_list:单向链表的轻量版 (C++11)
forward_list是单向链表,节省空间。
头文件:#include <forward_list>
基本操作:类似list,但只push_front, insert_after等。
性能:插入O(1),无size() O(n)。
适用场景:内存紧,单向遍历。
示例代码:
#include <iostream>
#include <forward_list>
using namespace std;
int main() {
forward_list<int> fl = {3,2,1};
fl.push_front(0); // 0,3,2,1
auto it = fl.begin();
fl.insert_after(it, 4); // 0,4,3,2,1
cout << "List: ";
for(int num : fl) {
cout << num << " ";
}
cout << endl;
return 0;
}
输出:List: 0 4 3 2 1
forward_list适合哈希桶实现,我很少用,除非内存优化。
第三部分:关联容器详解
关联容器自动管理顺序或哈希,查找快。
3.1 set/multiset:有序集合
set存储唯一元素,自动排序。multiset允许重复。
头文件:#include <set>
基本操作:
- s.insert(1); 返回pair<iterator, bool>
- s.find(1); 返回iterator或end()
- s.erase(1); 或 s.erase(it)
性能:插入/查找/删除 O(log n),红黑树底层。
适用场景:去重、排序集合,如唯一ID。
示例代码:管理独特访客。
#include <iostream>
#include <set>
using namespace std;
int main() {
set<string> visitors;
visitors.insert("Alice");
visitors.insert("Bob");
auto res = visitors.insert("Alice"); // 失败,res.second=false
cout << "Visitors: ";
for(const auto& v : visitors) {
cout << v << " ";
}
cout << endl;
if(visitors.find("Bob") != visitors.end()) {
cout << "Bob is here." << endl;
}
// multiset示例
multiset<int> scores = {90, 85, 90};
cout << "Score count of 90: " << scores.count(90) << endl;
return 0;
}
输出:Visitors: Alice Bob Bob is here. Score count of 90: 2
set自定义比较:set<int, greater<int>> descending;
我在日志分析用set去重事件。
3.2 map/multimap:键值对的字典
map是有序键值对,键唯一。multimap允许重复键。
头文件:#include <map>
基本操作:
- m[ "key" ] = 1; // 若无,插入
- m.insert(make_pair("key", 1));
- m.find("key")->second
性能:O(log n)
适用场景:配置表、缓存。
示例代码:词频统计。
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<string, int> wordCount;
wordCount["apple"] = 5;
wordCount["banana"]++;
wordCount.emplace("cherry", 3);
cout << "Word counts:" << endl;
for(const auto& pair : wordCount) {
cout << pair.first << ": " << pair.second << endl;
}
// multimap
multimap<string, string> phoneBook;
phoneBook.insert({"John", "123"});
phoneBook.insert({"John", "456"});
auto range = phoneBook.equal_range("John");
cout << "John's numbers: ";
for(auto it = range.first; it != range.second; ++it) {
cout << it->second << " ";
}
cout << endl;
return 0;
}
输出:Word counts: apple: 5 banana: 1 cherry: 3 John's numbers: 123 456
map的[]会默认插入0值,小心。用count()检查存在。
我用map在服务器存session。
3.3 unordered_set/unordered_multiset:哈希集合 (C++11)
unordered_set是无序集合,哈希存储。
头文件:#include <unordered_set>
基本操作:类似set,但无序。
性能:平均O(1),最坏O(n)如果哈希冲突。
适用场景:快速查找,不需排序,如会员检查。
示例代码:
#include <iostream>
#include <unordered_set>
using namespace std;
int main() {
unordered_set<int> uniqueNums = {1,2,3,2}; // 自动去重:1,2,3
uniqueNums.insert(4);
if(uniqueNums.count(3)) {
cout << "3 is present." << endl;
}
cout << "Elements: ";
for(int num : uniqueNums) {
cout << num << " "; // 顺序不定
}
cout << endl;
// unordered_multiset允许重复
unordered_multiset<int> multi = {1,1,2};
cout << "Count of 1: " << multi.count(1) << endl;
return 0;
}
输出:3 is present. Elements: 4 1 2 3 (顺序可能变) Count of 1: 2
自定义哈希:struct Hash { size_t operator()(const T&) const; };
负载因子load_factor(),调整rehash。
我在高频交易用unordered_set查订单。
3.4 unordered_map/unordered_multimap:哈希字典 (C++11)
类似map,但无序。
头文件:#include <unordered_map>
基本操作:类似map。
性能:O(1)平均。
适用场景:快速键值查找,如数据库索引。
示例代码:简单缓存。
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
unordered_map<string, double> prices;
prices["apple"] = 1.5;
prices.emplace("banana", 0.8);
cout << "Apple price: " << prices["apple"] << endl;
// 如果键不存在,[]插入默认0
cout << "Cherry price (default): " << prices["cherry"] << endl; // 0
// 用at()抛异常如果不存在
try {
cout << prices.at("orange") << endl;
} catch(const out_of_range&) {
cout << "Orange not found." << endl;
}
// unordered_multimap
unordered_multimap<string, string> contacts;
contacts.insert({"Alice", "email@"});
contacts.insert({"Alice", "phone"});
auto range = contacts.equal_range("Alice");
cout << "Alice's contacts: ";
for(auto it = range.first; it != range.second; ++it) {
cout << it->second << " ";
}
cout << endl;
return 0;
}
输出:Apple price: 1.5 Cherry price (default): 0 Orange not found. Alice's contacts: email@ phone (顺序可能变)
unordered_map在大数据下高效,但哈希冲突需注意。bucket_count()查看桶数。
第四部分:容器适配器
这些是基于其他容器的简化接口。
4.1 stack:栈
头文件:#include <stack>
基本操作:s.push(1); s.pop(); s.top();
底层默认deque,可指定如stack<int, vector<int>>。
示例:括号匹配。
#include <iostream>
#include <stack>
#include <string>
using namespace std;
bool isBalanced(const string& str) {
stack<char> s;
for(char c : str) {
if(c == '(' || c == '[' || c == '{') {
s.push(c);
} else if(c == ')' || c == ']' || c == '}') {
if(s.empty()) return false;
char top = s.top();
s.pop();
if((c == ')' && top != '(') || (c == ']' && top != '[') || (c == '}' && top != '{')) {
return false;
}
}
}
return s.empty();
}
int main() {
cout << "Balanced? " << boolalpha << isBalanced("{[()]}") << endl; // true
cout << "Balanced? " << boolalpha << isBalanced("{[(]}") << endl; // false
return 0;
}
栈简单,我用它实现undo功能。
4.2 queue:队列
头文件:#include <queue>
基本操作:q.push(1); q.pop(); q.front()/back();
底层deque。
示例:任务队列。
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<string> tasks;
tasks.push("Task1");
tasks.push("Task2");
while(!tasks.empty()) {
cout << "Processing: " << tasks.front() << endl;
tasks.pop();
}
return 0;
}
输出:Processing: Task1 Processing: Task2
4.3 priority_queue:优先队列
默认最大堆。
基本操作:pq.push(1); pq.pop(); pq.top();
示例:Top K问题。
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() {
priority_queue<int> pq; // max heap
vector<int> nums = {3,1,4,1,5,9};
for(int n : nums) pq.push(n);
cout << "Top 3: ";
for(int i=0; i<3; ++i) {
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
// min heap
priority_queue<int, vector<int>, greater<int>> minpq;
for(int n : nums) minpq.push(n);
cout << "Smallest: " << minpq.top() << endl;
return 0;
}
输出:Top 3: 9 5 4 Smallest: 1
优先队列在调度算法中常用。
第五部分:容器比较与选择
- 访问速度:vector/array > deque > list/forward_list
- 插入删除:list/forward_list O(1) > deque两端 > vector尾部 > set/map O(log n) > unordered O(1)
- 内存:vector连续,list节点散布。
- 有序:set/map有序,unordered无序。
选择原则:需随机访问用vector,频繁插删用list,快速查找用unordered_map/set,键值用map。
在项目中,我先用vector原型,优化时换unordered。
第六部分:高级主题
6.1 自定义分配器
容器支持allocator模板参数,自定义内存管理。如用pool allocator优化小对象。
示例:allocator<T> alloc; vector<int, allocator<int>> v(alloc);
6.2 容器与算法交互
STL算法如sort、find用在容器上。
示例:sort(v.begin(), v.end());
注意,关联容器不能sort,因为已排序。
6.3 迭代器失效
vector插入可能失效所有迭代器,list只失效删除的。
用erase返回的新it。
6.4 多线程与容器
STL容器非线程安全,用mutex保护。
C++11后,有concurrent容器但非标准。
6.5 性能优化
- 用emplace代替insert/push,避免拷贝。
- reserve预分配。
- 缩容:vector<int>().swap(v);
第七部分:常见错误与调试
- 越界访问:用at()。
- 迭代器失效:别在循环中erase,用erase返回值。
- 自定义类比较:实现operator< 或 functor。
- 哈希冲突:好哈希函数。
- 内存泄漏:容器存指针,记得delete。
调试用gdb看size/capacity。
结语
C++容器是强大工具,掌握它能写出高效代码。这些年,我从vector入门,到unordered_map优化,受益匪浅。建议多练代码,分析性能。欢迎评论交流!

被折叠的 条评论
为什么被折叠?



