【学习笔记03】C++STL标准模板库核心技术详解
📝 说明:本文为学习笔记,AI辅助整理。目的是系统梳理C++STL标准模板库核心知识,方便复习和查阅。
课时03:掌握C++标准库的强大武器,提升编程效率和代码质量
日期:2025年10月6日
难度:⭐⭐⭐⭐☆ | 时长:2小时 | 类型:实战必修
🎯 学习目标
通过本课时的学习,你将掌握:
- STL三大组件:容器、迭代器、算法的核心原理
- 常用容器的特性、时间复杂度和使用场景
- 迭代器的分类和使用技巧
- 常用算法的实际应用和性能考量
- 面试中STL的高频考点和最佳实践
📚 STL核心架构
🎯 STL三大组件关系图
🔍 1. 容器详解
📦 序列容器(Sequence Containers)
vector - 动态数组
#include <vector>
#include <iostream>
using namespace std;void demonstrateVector() {// 1. 初始化方式vector<int> v1; // 空vectorvector<int> v2(5); // 5个元素,默认值0vector<int> v3(5, 10); // 5个元素,值为10vector<int> v4{1, 2, 3, 4, 5}; // 初始化列表vector<int> v5(v4.begin(), v4.end()); // 迭代器范围初始化// 2. 容量管理cout << "大小:" << v4.size() << endl; // 当前元素数量cout << "容量:" << v4.capacity() << endl; // 已分配空间cout << "最大容量:" << v4.max_size() << endl; // 理论最大容量v4.reserve(100); // 预分配空间,避免频繁重新分配v4.shrink_to_fit(); // 释放多余空间// 3. 元素访问cout << "第一个元素:" << v4[0] << endl; // 操作符[]cout << "第一个元素:" << v4.at(0) << endl; // at()有边界检查cout << "第一个元素:" << v4.front() << endl; // 第一个元素cout << "最后元素:" << v4.back() << endl; // 最后一个元素// 4. 修改操作v4.push_back(6); // 尾部添加 O(1)摊销v4.pop_back(); // 尾部删除 O(1)v4.insert(v4.begin() + 2, 99); // 指定位置插入 O(n)v4.erase(v4.begin() + 2); // 删除指定位置 O(n)// 5. 遍历方式// 传统for循环for (size_t i = 0; i < v4.size(); ++i) {cout << v4[i] << " ";}// 迭代器for (auto it = v4.begin(); it != v4.end(); ++it) {cout << *it << " ";}// 范围for(推荐)for (const auto& element : v4) {cout << element << " ";}
}
deque - 双端队列
#include <deque>void demonstrateDeque() {deque<int> dq{1, 2, 3, 4, 5};// 双端操作 - deque的优势dq.push_front(0); // 头部插入 O(1)dq.push_back(6); // 尾部插入 O(1)dq.pop_front(); // 头部删除 O(1)dq.pop_back(); // 尾部删除 O(1)// 随机访问(类似vector)cout << "中间元素:" << dq[dq.size()/2] << endl;// 使用场景:需要频繁头尾操作的场景// 例如:滑动窗口、广度优先搜索等
}
list - 双向链表
#include <list>void demonstrateList() {list<int> lst{3, 1, 4, 1, 5};// 任意位置高效插入删除auto it = find(lst.begin(), lst.end(), 4);lst.insert(it, 99); // 在4之前插入99 O(1)lst.erase(it); // 删除4 O(1)// 链表特有操作lst.sort(); // 原地排序lst.unique(); // 去除连续重复元素lst.reverse(); // 反转// 合并两个有序链表list<int> lst2{2, 6, 8};lst2.sort();lst.merge(lst2); // 合并后lst2为空// 注意:list不支持随机访问,没有[]和at()// cout << lst[0]; // 错误!
}
🗺️ 关联容器(Associative Containers)
map - 有序键值对
#include <map>void demonstrateMap() {// 1. 初始化map<string, int> scores;map<string, int> scores2{{"Alice", 95}, {"Bob", 87}, {"Charlie", 92}};// 2. 插入操作scores["Alice"] = 95; // 操作符[]scores.insert({"Bob", 87}); // insert方法scores.insert(make_pair("Charlie", 92)); // make_pairscores.emplace("David", 89); // 原地构造(效率更高)// 3. 查找操作auto it = scores.find("Alice");if (it != scores.end()) {cout << "Alice的分数:" << it->second << endl;}// 检查键是否存在if (scores.count("Eve") > 0) {cout << "Eve存在" << endl;}// C++20: contains方法// if (scores.contains("Eve")) { ... }// 4. 有序遍历(map自动按键排序)for (const auto& [name, score] : scores) { // 结构化绑定(C++17)cout << name << ": " << score << endl;}// 5. 范围操作auto lower = scores.lower_bound("Bob"); // 第一个 >= "Bob" 的元素auto upper = scores.upper_bound("Charlie"); // 第一个 > "Charlie" 的元素for (auto it = lower; it != upper; ++it) {cout << it->first << ": " << it->second << endl;}
}
set - 有序集合
#include <set>void demonstrateSet() {set<int> numbers{3, 1, 4, 1, 5, 9, 2, 6}; // 自动去重和排序// 插入和删除numbers.insert(7);numbers.erase(1);// 查找if (numbers.find(5) != numbers.end()) {cout << "找到5" << endl;}// 集合操作set<int> set1{1, 2, 3, 4};set<int> set2{3, 4, 5, 6};set<int> result;// 交集set_intersection(set1.begin(), set1.end(),set2.begin(), set2.end(),inserter(result, result.begin()));// 并集 set_union(set1.begin(), set1.end(),set2.begin(), set2.end(),inserter(result, result.begin()));
}
🔀 无序容器(Unordered Containers)
unordered_map - 哈希表
#include <unordered_map>void demonstrateUnorderedMap() {unordered_map<string, int> wordCount;// 统计单词频率的经典应用vector<string> words{"apple", "banana", "apple", "cherry", "banana", "apple"};for (const string& word : words) {wordCount[word]++; // 自动初始化为0再递增}// 查看哈希表信息cout << "桶数量:" << wordCount.bucket_count() << endl;cout << "负载因子:" << wordCount.load_factor() << endl;cout << "最大负载因子:" << wordCount.max_load_factor() << endl;// 自定义哈希函数(用于自定义类型)struct Person {string name;int age;bool operator==(const Person& other) const {return name == other.name && age == other.age;}};struct PersonHash {size_t operator()(const Person& p) const {return hash<string>()(p.name) ^ (hash<int>()(p.age) << 1);}};unordered_map<Person, string, PersonHash> personMap;personMap[{"Alice", 25}] = "Engineer";
}
🔄 2. 迭代器详解
🎯 迭代器类型和特性
#include <iterator>void demonstrateIterators() {vector<int> vec{1, 2, 3, 4, 5};list<int> lst{1, 2, 3, 4, 5};// 1. 随机访问迭代器(vector, deque)auto vec_it = vec.begin();vec_it += 3; // 支持算术运算vec_it = vec_it - 1; // 支持减法int diff = vec.end() - vec.begin(); // 支持距离计算// 2. 双向迭代器(list, map, set)auto lst_it = lst.begin();++lst_it; // 支持前进--lst_it; // 支持后退// lst_it += 2; // 不支持算术运算// 3. 迭代器适配器// 反向迭代器for (auto rit = vec.rbegin(); rit != vec.rend(); ++rit) {cout << *rit << " "; // 逆序输出}// 插入迭代器vector<int> dest;copy(vec.begin(), vec.end(), back_inserter(dest)); // 尾部插入list<int> dest2;copy(vec.begin(), vec.end(), front_inserter(dest2)); // 头部插入// 4. 迭代器失效问题vector<int> v{1, 2, 3, 4, 5};auto it = v.begin() + 2; // 指向元素3v.push_back(6); // 可能导致重新分配,it失效// cout << *it; // 危险!可能崩溃// 安全做法:记录索引而非迭代器size_t index = 2;v.push_back(6);cout << v[index] << endl; // 安全
}
⚙️ 3. 算法详解
🔍 查找算法
#include <algorithm>void demonstrateSearchAlgorithms() {vector<int> vec{3, 1, 4, 1, 5, 9, 2, 6, 5, 3};// 1. 线性查找auto it = find(vec.begin(), vec.end(), 5);if (it != vec.end()) {cout << "找到5,位置:" << distance(vec.begin(), it) << endl;}// 查找满足条件的元素auto it2 = find_if(vec.begin(), vec.end(), [](int x) { return x > 5; });if (it2 != vec.end()) {cout << "第一个大于5的元素:" << *it2 << endl;}// 2. 二分查找(需要先排序)vector<int> sorted_vec = vec;sort(sorted_vec.begin(), sorted_vec.end());bool found = binary_search(sorted_vec.begin(), sorted_vec.end(), 5);cout << "二分查找5:" << (found ? "找到" : "未找到") << endl;// 查找插入位置auto pos = lower_bound(sorted_vec.begin(), sorted_vec.end(), 5);cout << "5应该插入的位置:" << distance(sorted_vec.begin(), pos) << endl;// 3. 计数int count = count(vec.begin(), vec.end(), 1);cout << "1出现的次数:" << count << endl;int count_if = count_if(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; });cout << "偶数的个数:" << count_if << endl;
}
🔄 修改算法
void demonstrateModifyingAlgorithms() {vector<int> vec{1, 2, 3, 4, 5};vector<int> dest(vec.size());// 1. 复制copy(vec.begin(), vec.end(), dest.begin());// 条件复制vector<int> even_nums;copy_if(vec.begin(), vec.end(), back_inserter(even_nums), [](int x) { return x % 2 == 0; });// 2. 变换vector<int> squares(vec.size());transform(vec.begin(), vec.end(), squares.begin(), [](int x) { return x * x; });// 两个序列的变换vector<int> vec2{6, 7, 8, 9, 10};vector<int> sums(vec.size());transform(vec.begin(), vec.end(), vec2.begin(), sums.begin(),[](int a, int b) { return a + b; });// 3. 填充vector<int> filled(10);fill(filled.begin(), filled.end(), 42);// 生成vector<int> generated(10);int counter = 0;generate(generated.begin(), generated.end(), [&counter]() { return ++counter; });// 4. 替换vector<int> replaced = vec;replace(replaced.begin(), replaced.end(), 3, 99); // 把3替换为99replace_if(replaced.begin(), replaced.end(), [](int x) { return x % 2 == 0; }, 0); // 偶数替换为0// 5. 删除vector<int> removed{1, 2, 3, 2, 4, 2, 5};auto new_end = remove(removed.begin(), removed.end(), 2); // 移除所有2removed.erase(new_end, removed.end()); // 真正删除// 或者使用erase-remove idiomremoved.erase(remove(removed.begin(), removed.end(), 4), removed.end());
}
📊 排序和相关算法
void demonstrateSortingAlgorithms() {vector<int> vec{3, 1, 4, 1, 5, 9, 2, 6, 5, 3};// 1. 完全排序sort(vec.begin(), vec.end()); // 升序sort(vec.begin(), vec.end(), greater<int>()); // 降序// 自定义比较函数vector<string> names{"Alice", "Bob", "Charlie", "David"};sort(names.begin(), names.end(), [](const string& a, const string& b) {return a.length() < b.length(); // 按长度排序});// 2. 部分排序vector<int> partial{3, 1, 4, 1, 5, 9, 2, 6, 5, 3};partial_sort(partial.begin(), partial.begin() + 3, partial.end()); // 只排序前3个最小的// 3. nth_element(快速选择)vector<int> nth_vec{3, 1, 4, 1, 5, 9, 2, 6, 5, 3};nth_element(nth_vec.begin(), nth_vec.begin() + 5, nth_vec.end());cout << "第5小的元素:" << nth_vec[5] << endl;// 4. 堆操作vector<int> heap{3, 1, 4, 1, 5, 9, 2, 6};make_heap(heap.begin(), heap.end()); // 建立最大堆heap.push_back(10);push_heap(heap.begin(), heap.end()); // 添加元素到堆pop_heap(heap.begin(), heap.end()); // 移除堆顶heap.pop_back();sort_heap(heap.begin(), heap.end()); // 堆排序// 5. 集合操作(需要有序序列)vector<int> set1{1, 2, 3, 4, 5};vector<int> set2{3, 4, 5, 6, 7};vector<int> result;// 并集set_union(set1.begin(), set1.end(), set2.begin(), set2.end(),back_inserter(result));result.clear();// 交集set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(),back_inserter(result));result.clear();// 差集set_difference(set1.begin(), set1.end(), set2.begin(), set2.end(),back_inserter(result));
}
🎯 容器选择指南
📊 性能对比表
操作 | vector | deque | list | map | unordered_map |
---|---|---|---|---|---|
随机访问 | O(1) | O(1) | O(n) | O(log n) | O(1)平均 |
头部插入 | O(n) | O(1) | O(1) | O(log n) | O(1)平均 |
尾部插入 | O(1)摊销 | O(1) | O(1) | O(log n) | O(1)平均 |
中间插入 | O(n) | O(n) | O(1) | O(log n) | N/A |
查找 | O(n) | O(n) | O(n) | O(log n) | O(1)平均 |
内存局部性 | 最好 | 好 | 差 | 中等 | 中等 |
🎯 选择建议
// 1. 需要频繁随机访问 → vector
vector<int> data;
cout << data[100] << endl; // O(1)访问// 2. 需要频繁头尾操作 → deque
deque<int> queue;
queue.push_front(1);
queue.push_back(2);// 3. 需要频繁中间插入删除 → list
list<int> timeline;
auto it = timeline.begin();
advance(it, 10);
timeline.insert(it, new_event); // O(1)插入// 4. 需要有序存储和查找 → map/set
map<string, int> scores;
auto range = scores.equal_range("Alice"); // 范围查询// 5. 需要快速查找,不关心顺序 → unordered_map/unordered_set
unordered_map<string, int> cache;
if (cache.find("key") != cache.end()) { // O(1)查找// 处理缓存命中
}
🎯 面试高频考点
🔥 经典面试题
1. vector的扩容机制?
void demonstrateVectorGrowth() {vector<int> v;cout << "初始容量:" << v.capacity() << endl;for (int i = 0; i < 10; ++i) {v.push_back(i);cout << "大小:" << v.size() << ", 容量:" << v.capacity() << endl;}// 通常按1.5或2的倍数增长// 扩容时:分配新空间 → 复制元素 → 释放旧空间
}
2. map和unordered_map的区别?
"map基于红黑树实现,有序,O(log n)操作;
unordered_map基于哈希表,无序,O(1)平均操作。
选择依据:需要有序或范围查询用map,
只需要快速查找用unordered_map。"
3. 迭代器失效的情况?
void demonstrateIteratorInvalidation() {vector<int> v{1, 2, 3, 4, 5};// 1. vector扩容导致失效auto it1 = v.begin();v.push_back(6); // 可能扩容,it1失效// 2. 删除元素导致失效auto it2 = v.begin() + 2;v.erase(it2); // it2及其后面的迭代器失效// 3. list删除不影响其他迭代器list<int> lst{1, 2, 3, 4, 5};auto lst_it1 = lst.begin();auto lst_it2 = next(lst_it1);lst.erase(lst_it1); // 只有lst_it1失效,lst_it2仍有效
}
4. STL算法的时间复杂度?
"sort: O(n log n)
find: O(n)
binary_search: O(log n) (需要有序)
lower_bound/upper_bound: O(log n)
make_heap: O(n)
push_heap/pop_heap: O(log n)"
🚀 实际应用案例
📝 LRU缓存实现
#include <unordered_map>
#include <list>class LRUCache {
private:int capacity;list<pair<int, int>> cache; // 双向链表存储键值对unordered_map<int, list<pair<int, int>>::iterator> map; // 哈希表存储迭代器public:LRUCache(int cap) : capacity(cap) {}int get(int key) {auto it = map.find(key);if (it == map.end()) {return -1; // 未找到}// 移动到头部cache.splice(cache.begin(), cache, it->second);return it->second->second;}void put(int key, int value) {auto it = map.find(key);if (it != map.end()) {// 更新值并移动到头部it->second->second = value;cache.splice(cache.begin(), cache, it->second);return;}if (cache.size() >= capacity) {// 删除尾部元素int last_key = cache.back().first;cache.pop_back();map.erase(last_key);}// 在头部插入新元素cache.emplace_front(key, value);map[key] = cache.begin();}
};
🎯 单词频率统计
#include <sstream>
#include <algorithm>class WordCounter {
public:map<string, int> countWords(const string& text) {map<string, int> wordCount;istringstream iss(text);string word;while (iss >> word) {// 转换为小写transform(word.begin(), word.end(), word.begin(), ::tolower);// 移除标点符号word.erase(remove_if(word.begin(), word.end(), ::ispunct), word.end());if (!word.empty()) {wordCount[word]++;}}return wordCount;}vector<pair<string, int>> getTopWords(const map<string, int>& wordCount, int n) {vector<pair<string, int>> words(wordCount.begin(), wordCount.end());// 按频率降序排序sort(words.begin(), words.end(), [](const auto& a, const auto& b) {return a.second > b.second;});if (words.size() > n) {words.resize(n);}return words;}
};
🎉 课时总结
通过本课时的学习,我们全面掌握了STL的核心内容:
- ✅ 容器掌握:理解了各种容器的特性和适用场景
- ✅ 迭代器应用:学会了迭代器的使用技巧和注意事项
- ✅ 算法运用:掌握了常用算法的应用和性能特点
- ✅ 性能分析:了解了时间复杂度和内存使用特点
- ✅ 实战应用:通过案例学会了STL在实际项目中的应用
STL是C++编程的利器,熟练掌握STL不仅能提高编程效率,还能写出更加安全、高效的代码。在面试和实际开发中,STL的重要性不言而喻。
下一课时,我们将学习C++的内存管理和智能指针,了解如何写出更加安全的C++代码。
💡 最佳实践:
- 优先使用STL算法而非手写循环
- 选择合适的容器,避免性能瓶颈
- 注意迭代器失效问题
- 善用lambda表达式简化代码
- 利用auto关键字提高代码可读性