STL(Standard Template Library)是C++标准库的核心组成部分,提供了一系列高效的通用容器和算法。对于算法竞赛和日常开发而言,掌握STL容器是提高编码效率的关键。本系列教程将从实际应用角度出发,深入解析常用STL容器的特性和使用技巧。
STL容器主要分为三大类:
序列式容器:元素按线性顺序排列
关联式容器:基于键值对的元素存储
容器适配器:基于其他容器实现的特殊结构
vector是C++中最常用的动态数组实现,具有以下特点:
cpp复制#include <vector>
using namespace std;
// 初始化方式示例
vector<int> v1; // 空vector
vector<int> v2(10); // 10个0
vector<int> v3(10, 5); // 10个5
vector<int> v4 = {1,2,3}; // 初始化列表
vector<vector<int>> v5(3, vector<int>(4)); // 3x4二维数组
vector采用动态扩容策略来平衡空间和时间效率:
cpp复制vector<int> vec;
vec.reserve(1000); // 预分配1000个元素空间
for(int i=0; i<1000; i++) {
vec.push_back(i); // 不会触发扩容
}
| 操作 | 语法 | 时间复杂度 | 注意事项 |
|---|---|---|---|
| 随机访问 | vec[i] | O(1) | 不检查越界 |
| 安全访问 | vec.at(i) | O(1) | 越界抛出异常 |
| 尾部插入 | push_back() | 均摊O(1) | 可能触发扩容 |
| 尾部删除 | pop_back() | O(1) | 容器不能为空 |
| 中间插入 | insert(pos) | O(n) | 避免频繁使用 |
| 中间删除 | erase(pos) | O(n) | 返回下一个迭代器 |
| 查找元素 | find() | O(n) | 线性搜索 |
cpp复制vector<int> vec = {2,1,3,2,4,1,5,4};
// 步骤1:排序使相同元素相邻
sort(vec.begin(), vec.end()); // {1,1,2,2,3,4,4,5}
// 步骤2:unique将重复元素移到末尾
auto last = unique(vec.begin(), vec.end());
// 步骤3:删除重复元素
vec.erase(last, vec.end()); // {1,2,3,4,5}
cpp复制// 不规则二维数组
vector<vector<int>> matrix;
matrix.push_back({1,2,3});
matrix.push_back({4,5});
matrix.push_back({6,7,8,9});
// 遍历方式
for(int i=0; i<matrix.size(); i++) {
for(int j=0; j<matrix[i].size(); j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
cpp复制// 错误方式:会产生临时对象拷贝
vector<int> createVector() {
vector<int> v(1000000);
return v; // C++11前会拷贝
}
// 正确方式:利用移动语义(C++11起)
vector<int> v = createVector(); // 不会拷贝
list是双向链表的STL实现,具有以下特点:
cpp复制#include <list>
using namespace std;
list<int> lst = {1,2,3,4,5};
// 高效插入删除
auto it = lst.begin();
advance(it, 2); // 移动到第3个位置
lst.insert(it, 10); // 在第三个位置插入10
lst.erase(it); // 删除原第三个元素
| 操作 | vector | list |
|---|---|---|
| 随机访问 | O(1) | O(n) |
| 头部插入 | O(n) | O(1) |
| 尾部插入 | 均摊O(1) | O(1) |
| 中间插入 | O(n) | O(1) |
| 内存使用 | 紧凑 | 每个元素额外16字节(64位) |
| 缓存友好 | 是 | 否 |
list提供了一些vector没有的特殊操作:
cpp复制list<int> lst = {1,2,3,4,5};
// 1. splice:转移元素到另一个list
list<int> lst2 = {10,20};
auto it = lst.begin();
advance(it, 2);
lst.splice(it, lst2); // lst: 1,2,10,20,3,4,5
// 2. merge:合并两个有序list
lst.sort();
lst2 = {6,8};
lst2.sort();
lst.merge(lst2); // lst: 1,2,3,4,5,6,8
// 3. unique:删除连续重复元素
lst = {1,1,2,3,3,3,4};
lst.unique(); // lst: 1,2,3,4
// 4. remove/remove_if:条件删除
lst.remove(3); // 删除所有3
lst.remove_if([](int x){return x%2==0;}); // 删除所有偶数
stack是后进先出(LIFO)的容器适配器,默认基于deque实现:
cpp复制#include <stack>
using namespace std;
stack<int> stk;
stk.push(1); // 栈顶:1
stk.push(2); // 栈顶:2
stk.push(3); // 栈顶:3
while(!stk.empty()) {
cout << stk.top() << " "; // 3 2 1
stk.pop();
}
cpp复制// 括号匹配示例
bool isValid(string s) {
stack<char> stk;
for(char c : s) {
if(c=='(' || c=='[' || c=='{') {
stk.push(c);
} else {
if(stk.empty()) return false;
char top = stk.top();
if((c==')' && top!='(') ||
(c==']' && top!='[') ||
(c=='}' && top!='{')) {
return false;
}
stk.pop();
}
}
return stk.empty();
}
queue是先进先出(FIFO)的容器适配器,默认基于deque实现:
cpp复制#include <queue>
using namespace std;
queue<int> q;
q.push(1); // 队尾:1
q.push(2); // 队尾:2
q.push(3); // 队尾:3
while(!q.empty()) {
cout << q.front() << " "; // 1 2 3
q.pop();
}
priority_queue是优先级队列,默认是大根堆:
cpp复制// 默认大根堆
priority_queue<int> maxHeap;
// 小根堆定义
priority_queue<int, vector<int>, greater<int>> minHeap;
// 自定义优先级
struct Compare {
bool operator()(int a, int b) {
return a > b; // 小根堆
}
};
priority_queue<int, vector<int>, Compare> customHeap;
cpp复制// Top K问题示例
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> freq;
for(int num : nums) freq[num]++;
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> minHeap;
for(auto& [num, count] : freq) {
minHeap.push({count, num});
if(minHeap.size() > k) {
minHeap.pop();
}
}
vector<int> result;
while(!minHeap.empty()) {
result.push_back(minHeap.top().second);
minHeap.pop();
}
return result;
}
set是基于红黑树实现的有序集合,具有以下特点:
cpp复制#include <set>
using namespace std;
set<int> s = {3,1,4,2};
// 自动排序
for(int num : s) {
cout << num << " "; // 1 2 3 4
}
// 查找操作
auto it = s.find(3);
if(it != s.end()) {
cout << "Found: " << *it;
}
// 范围查询
auto low = s.lower_bound(2); // >=2的第一个元素
auto high = s.upper_bound(3); // >3的第一个元素
for(auto it=low; it!=high; it++) {
cout << *it << " "; // 2 3
}
multiset允许重复元素,其他特性与set相同:
cpp复制multiset<int> ms = {1,2,2,3,3,3};
// 统计特定值出现次数
cout << ms.count(3); // 3
// 删除所有特定值
ms.erase(3); // 删除所有3
// 只删除一个特定值
auto it = ms.find(2);
if(it != ms.end()) {
ms.erase(it); // 只删除一个2
}
map是基于红黑树的键值对容器,键唯一且有序:
cpp复制#include <map>
using namespace std;
map<string, int> scoreMap;
scoreMap["Alice"] = 90;
scoreMap["Bob"] = 85;
scoreMap["Charlie"] = 95;
// 遍历map(C++17结构化绑定)
for(auto& [name, score] : scoreMap) {
cout << name << ": " << score << endl;
}
// 安全访问
if(scoreMap.count("David")) {
cout << scoreMap["David"];
} else {
cout << "David not found";
}
// 避免意外插入
auto it = scoreMap.find("Eve");
if(it != scoreMap.end()) {
cout << it->second;
}
cpp复制// emplace示例
map<string, string> dict;
dict.emplace("hello", "你好"); // 直接构造,不拷贝
// 自定义比较函数
struct CaseInsensitiveCompare {
bool operator()(const string& a, const string& b) const {
return lexicographical_compare(
a.begin(), a.end(),
b.begin(), b.end(),
[](char c1, char c2) {
return tolower(c1) < tolower(c2);
});
}
};
map<string, int, CaseInsensitiveCompare> caseInsensitiveMap;
unordered_set和unordered_map基于哈希表实现:
cpp复制#include <unordered_set>
#include <unordered_map>
unordered_set<int> uset = {3,1,4,2};
unordered_map<string, int> umap;
// 自定义哈希函数
struct MyHash {
size_t operator()(const pair<int,int>& p) const {
return hash<int>()(p.first) ^ hash<int>()(p.second);
}
};
unordered_set<pair<int,int>, MyHash> customSet;
cpp复制unordered_map<string, int> wordCount;
// 1. 预分配桶
wordCount.reserve(10000);
// 2. 设置最大负载因子
wordCount.max_load_factor(0.7);
// 3. 自定义类型需要提供哈希函数
struct Point {
int x, y;
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
struct PointHash {
size_t operator()(const Point& p) const {
return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1);
}
};
unordered_set<Point, PointHash> pointSet;
STL容器在修改操作后可能导致迭代器失效:
| 容器 | 导致迭代器失效的操作 |
|---|---|
| vector | 插入/删除导致重新分配 |
| deque | 首尾插入安全,中间插入/删除失效 |
| list | 只有被删除元素的迭代器失效 |
| map/set | 只有被删除元素的迭代器失效 |
cpp复制// 错误示例:遍历时删除元素
vector<int> vec = {1,2,3,4,5};
for(auto it = vec.begin(); it != vec.end(); it++) {
if(*it % 2 == 0) {
vec.erase(it); // 错误!erase后it失效
}
}
// 正确写法
for(auto it = vec.begin(); it != vec.end(); ) {
if(*it % 2 == 0) {
it = vec.erase(it); // erase返回下一个有效迭代器
} else {
it++;
}
}
C++11引入的移动语义可以显著提升STL容器性能:
cpp复制// 移动构造示例
vector<string> createStrings() {
vector<string> v;
v.push_back("very long string...");
return v; // 触发移动构造而非拷贝
}
vector<string> v = createStrings(); // 高效
// 移动插入示例
vector<string> oldVec = {"a", "b", "c"};
vector<string> newVec;
// 移动而非拷贝
newVec.push_back(std::move(oldVec[0]));
cout << oldVec[0]; // 未定义,已被移动
要使自定义类型能在STL容器中正常工作,通常需要:
cpp复制class Person {
public:
string name;
int age;
// 比较运算符(用于set/map排序)
bool operator<(const Person& other) const {
return tie(name, age) < tie(other.name, other.age);
}
// 相等运算符(用于unordered容器)
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};
// 哈希特化
namespace std {
template<>
struct hash<Person> {
size_t operator()(const Person& p) const {
return hash<string>()(p.name) ^ hash<int>()(p.age);
}
};
}
set<Person> personSet;
unordered_set<Person> personHashSet;
STL提供了丰富的泛型算法,与容器协同工作:
cpp复制#include <algorithm>
#include <numeric>
vector<int> vec = {3,1,4,2,5};
// 排序
sort(vec.begin(), vec.end());
// 查找
auto it = find(vec.begin(), vec.end(), 4);
// 累加
int sum = accumulate(vec.begin(), vec.end(), 0);
// 变换
transform(vec.begin(), vec.end(), vec.begin(),
[](int x){return x*x;});
// 删除重复(需先排序)
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
cpp复制set<int> s = {1,2,3,4,5};
// 错误:线性搜索O(n)
auto it1 = find(s.begin(), s.end(), 3);
// 正确:利用红黑树特性O(log n)
auto it2 = s.find(3);
STL算法常与函数对象配合使用:
cpp复制// 自定义排序规则
vector<Person> people = {{"Alice",25}, {"Bob",30}, {"Charlie",20}};
sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return a.age < b.age;
});
// 自定义函数对象
struct IsEven {
bool operator()(int x) const {
return x % 2 == 0;
}
};
vector<int> nums = {1,2,3,4,5};
int evenCount = count_if(nums.begin(), nums.end(), IsEven());
cpp复制#include <map>
#include <string>
#include <vector>
#include <algorithm>
vector<string> words = {"apple", "banana", "apple", "orange", "banana", "apple"};
map<string, int> wordCount;
for(const auto& word : words) {
wordCount[word]++;
}
// 按频率排序输出
vector<pair<string, int>> sortedCounts(wordCount.begin(), wordCount.end());
sort(sortedCounts.begin(), sortedCounts.end(),
[](const auto& a, const auto& b) {
return a.second > b.second;
});
for(const auto& [word, count] : sortedCounts) {
cout << word << ": " << count << endl;
}
cpp复制vector<int> nums = {3,1,4,2,5,8,6,7};
int k = 3;
// 小根堆维护Top K大元素
priority_queue<int, vector<int>, greater<int>> minHeap;
for(int num : nums) {
minHeap.push(num);
if(minHeap.size() > k) {
minHeap.pop();
}
}
// 输出结果
while(!minHeap.empty()) {
cout << minHeap.top() << " "; // 6 7 8
minHeap.pop();
}
cpp复制set<pair<int, int>> intervals;
// 添加区间
intervals.insert({1, 3});
intervals.insert({6, 9});
intervals.insert({2, 5}); // 自动排序
// 合并重叠区间
auto it = intervals.begin();
while(it != intervals.end()) {
auto next = it; next++;
if(next != intervals.end() && it->second >= next->first) {
// 合并区间
pair<int, int> merged(it->first, max(it->second, next->second));
intervals.erase(it);
intervals.erase(next);
it = intervals.insert(merged).first;
} else {
it++;
}
}
// 输出合并后的区间
for(const auto& interval : intervals) {
cout << "[" << interval.first << "," << interval.second << "] ";
}
cpp复制#include <chrono>
const int N = 1000000;
// vector测试
auto start = chrono::high_resolution_clock::now();
vector<int> vec;
for(int i=0; i<N; i++) {
vec.push_back(i);
}
auto end = chrono::high_resolution_clock::now();
cout << "vector time: " << chrono::duration_cast<chrono::milliseconds>(end-start).count() << "ms\n";
// list测试
start = chrono::high_resolution_clock::now();
list<int> lst;
for(int i=0; i<N; i++) {
lst.push_back(i);
}
end = chrono::high_resolution_clock::now();
cout << "list time: " << chrono::duration_cast<chrono::milliseconds>(end-start).count() << "ms\n";
cpp复制// 优化示例:预先分配+emplace
vector<pair<int, string>> data;
data.reserve(1000); // 预分配空间
for(int i=0; i<1000; i++) {
data.emplace_back(i, "test"); // 直接构造
}
cpp复制#include <array>
#include <forward_list>
array<int, 5> arr = {1,2,3,4,5}; // 固定大小数组
forward_list<int> flist = {1,2,3}; // 单向链表
简化了容器元素的访问:
cpp复制map<string, int> scoreMap = {{"Alice",90}, {"Bob",85}};
// 传统方式
for(const auto& pair : scoreMap) {
cout << pair.first << ": " << pair.second << endl;
}
// C++17结构化绑定
for(const auto& [name, score] : scoreMap) {
cout << name << ": " << score << endl;
}
cpp复制// C++20 ranges示例
#include <ranges>
vector<int> vec = {1,2,3,4,5,6,7,8,9};
// 过滤偶数并平方
auto result = vec | views::filter([](int x){return x%2==0;})
| views::transform([](int x){return x*x;});
for(int x : result) {
cout << x << " "; // 4 16 36 64
}
vector容量不释放:即使clear(),capacity()不变
vector<int>().swap(vec);list节点内存分散:可能导致缓存命中率低
vector频繁扩容:导致多次内存分配和拷贝
map过度使用:简单场景可以用vector+sort替代
STL容器大多不是线程安全的,需要额外同步:
cpp复制#include <mutex>
map<string, int> sharedMap;
mutex mapMutex;
void safeInsert(const string& key, int value) {
lock_guard<mutex> guard(mapMutex);
sharedMap[key] = value;
}
STL容器允许自定义内存分配策略:
cpp复制template<typename T>
class MyAllocator {
public:
using value_type = T;
T* allocate(size_t n) {
cout << "Allocating " << n << " elements\n";
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, size_t n) {
cout << "Deallocating " << n << " elements\n";
::operator delete(p);
}
};
vector<int, MyAllocator<int>> customVec;
通过继承或组合扩展STL容器功能:
cpp复制template<typename T>
class CountingVector : public vector<T> {
public:
size_t push_count = 0;
void push_back(const T& value) {
vector<T>::push_back(value);
push_count++;
}
};
CountingVector<int> cv;
cv.push_back(1);
cv.push_back(2);
cout << "Push count: " << cv.push_count;
cpp复制// 更好的API设计
template<typename InputIt>
void processItems(InputIt begin, InputIt end) {
// 处理任何容器提供的迭代器范围
}
vector<int> items = {1,2,3};
processItems(items.begin(), items.end());
理解不同容器操作的异常安全性:
cpp复制// 游戏实体组件存储优化示例
struct Transform {
float x, y, z;
};
vector<Transform> transforms; // 连续存储提高缓存命中率
vector<RenderComponent> renderComponents;
cpp复制// 预分配所有可能需要的对象
array<Order, MAX_ORDERS> orderPool;
int nextOrderIndex = 0;
Order* createOrder() {
if(nextOrderIndex >= MAX_ORDERS) return nullptr;
return &orderPool[nextOrderIndex++];
}
cpp复制// C++23 flat_map示例(假设)
#include <flat_map>
flat_map<string, int> fm;
fm.insert({"one",1});
fm["two"] = 2; // 内部使用排序vector
书籍:
在线资源:
STL作为C++的核心库,其深度和广度都值得长期学习和探索。希望本教程能够帮助读者建立系统的STL知识体系,并在实际开发中灵活运用这些强大的工具。记住,理解原理比记住API更重要,性能优化应该基于实际测量而非猜测。