C++容器详解:从基础到实战的全面指南
C++容器详解:从基础到实战的全面指南
容器是C++标准库中最强大的特性之一,它们为数据存储和操作提供了灵活高效的方式。本文将从基础概念出发,深入探讨C++中常用容器的特性、用法及实战技巧,特别聚焦于vector和哈希表的应用。
一、容器概述
C++标准模板库(STL)提供了多种容器类型,这些容器可以分为三大类:
- 序列容器(如vector、list、deque)
- 关联容器(如map、set、unordered_map)
- 容器适配器(如stack、queue、priority_queue)
容器的核心价值在于:
- 封装了数据结构的实现细节
- 提供统一的操作接口
- 自动管理内存
- 优化了性能
二、vector容器:动态数组的艺术
2.1 vector基础
std::vector是最常用的序列容器,功能上相当于动态数组,能够根据需要自动调整大小。
#include <vector> // 使用vector必须包含的头文件
// 定义和初始化
vector<int> nums; // 定义一个存储int类型的vector
vector<string> names = {"Alice", "Bob", "Charlie"}; // 初始化并赋值
vector<double> values(10, 3.14); // 定义包含10个3.14的vector
2.2 vector的基本操作
元素访问:
vector<int> nums = {10, 20, 30, 40};
// 两种访问方式
cout << nums[2] << endl; // 30,不做边界检查
cout << nums.at(2) << endl; // 30,会做边界检查,越界抛出异常
// 获取首尾元素
cout << nums.front() << endl; // 10
cout << nums.back() << endl; // 40
元素修改:
// 添加元素
nums.push_back(50); // 在末尾添加元素
// 插入元素
nums.insert(nums.begin() + 2, 25); // 在索引2处插入25
// 删除元素
nums.pop_back(); // 删除最后一个元素
nums.erase(nums.begin() + 3); // 删除索引3处的元素
// 清空容器
nums.clear(); // 清空所有元素,但可能保留容量
容量管理:
vector<int> nums = {1, 2, 3};
cout << nums.size() << endl; // 3,当前元素个数
cout << nums.capacity() << endl; // 可能是3或更大,容器分配的存储空间
nums.resize(5); // 改变元素个数为5,新增元素为默认值0
nums.reserve(10); // 预留至少能存储10个元素的空间,不改变元素个数
2.3 遍历vector的方法
1. 传统for循环:
for (int i = 0; i < nums.size(); i++) {
cout << nums[i] << " ";
}
2. 范围for循环(C++11及以上):
// 只读遍历
for (auto num : nums) {
cout << num << " ";
}
// 修改元素(注意&符号,取引用)
for (auto& num : nums) {
num *= 2; // 所有元素翻倍
}
3. 使用迭代器:
for (auto it = nums.begin(); it != nums.end(); ++it) {
cout << *it << " "; // 迭代器需要解引用
}
2.4 vector与排序
使用标准库的sort函数可以轻松对vector进行排序:
#include <algorithm> // 包含sort函数
vector<int> nums = {3, 1, 4, 1, 5, 9};
sort(nums.begin(), nums.end()); // 升序排序
// 降序排序
sort(nums.begin(), nums.end(), greater<int>());
三、迭代器:容器的"指针"
迭代器是连接容器和算法的桥梁,它提供了访问容器元素的统一方式。可以将迭代器理解为容器专用的"指针"。
vector<string> fruits = {"apple", "banana", "cherry"};
// 获取迭代器
auto it = fruits.begin(); // 指向第一个元素的迭代器
// 使用迭代器
cout << *it << endl; // 解引用,输出"apple"
++it; // 移动到下一个元素
cout << *it << endl; // 输出"banana"
// 遍历所有元素
for (it = fruits.begin(); it != fruits.end(); ++it) {
cout << *it << " ";
}
不同容器支持的迭代器类型不同,vector支持随机访问迭代器,这意味着我们可以对其进行算术运算:
vector<int> nums = {10, 20, 30, 40, 50};
auto it = nums.begin();
it += 2; // 移动两个位置
cout << *it; // 输出30
it -= 1; // 向后移动一个位置
cout << *it; // 输出20
四、哈希表:unordered_map的妙用
unordered_map是C++11引入的哈希表容器,提供了键值对的存储和快速查找功能,平均时间复杂度为O(1)。
4.1 unordered_map基础
#include <unordered_map> // 包含头文件
// 定义:键为string类型,值为int类型
unordered_map<string, int> studentScores;
// 插入元素
studentScores["Alice"] = 95;
studentScores.insert({"Bob", 88});
// 访问元素
cout << "Alice's score: " << studentScores["Alice"] << endl;
// 检查元素是否存在
if (studentScores.find("Charlie") != studentScores.end()) {
cout << "Charlie's score: " << studentScores["Charlie"] << endl;
} else {
cout << "Charlie not found" << endl;
}
4.2 哈希表的实战案例
案例1:两数之和
哈希表非常适合解决"两数之和"这类问题,可以将时间复杂度从O(n²)降至O(n):
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> valToIndex; // 维护值到索引的映射
for (int i = 0; i < nums.size(); ++i) {
int complement = target - nums[i];
// 查找是否存在互补的数
if (valToIndex.find(complement) != valToIndex.end()) {
return {valToIndex[complement], i};
}
valToIndex[nums[i]] = i;
}
return {}; // 未找到(题目保证有解时可省略)
}
案例2:字母异位词分组
字母异位词是指由相同字母重排列形成的单词,使用哈希表可以高效分组:
#include <ranges> // 用于ranges::sort
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> groups;
for (string& s : strs) {
string sorted_s = s;
ranges::sort(sorted_s); // 排序字符串作为键
groups[sorted_s].push_back(s); // 相同排序结果的字符串分到同一组
}
vector<vector<string>> result;
result.reserve(groups.size()); // 预分配空间,提高效率
// 将哈希表中的值收集到结果中
for (auto& [_, group] : groups) {
result.push_back(group);
}
return result;
}
五、auto关键字:简化代码的利器
auto关键字用于自动类型推导,在处理容器时特别有用,可以简化代码并提高可读性:
// 代替复杂的迭代器类型
for (auto it = nums.begin(); it != nums.end(); ++it) { ... }
// 代替复杂的容器元素类型
for (auto& pair : studentScores) { // pair是unordered_map中键值对的类型
cout << pair.first << ": " << pair.second << endl;
}
// 自动推导变量类型
auto numbers = vector<int>{1, 2, 3}; // numbers被推导为vector<int>类型
auto sum = 0LL; // 自动推导为long long类型,避免整数溢出
六、容器使用的最佳实践
-
选择合适的容器:
- 需要快速随机访问时选择vector
- 需要频繁插入删除时选择list
- 需要键值对存储和快速查找时选择unordered_map
-
注意容量与大小的区别:
- size()返回实际元素个数
- capacity()返回当前分配的存储空间能容纳的元素个数
- 合理使用reserve()减少内存重分配
-
使用引用(&)提高效率:
- 遍历容器时使用引用避免不必要的拷贝
- 函数参数使用引用传递大型容器
-
利用标准算法:
- 熟悉头文件中的常用算法(如sort、find、for_each)
- 算法与容器结合使用能大幅提高代码效率和可读性
-
注意迭代器失效问题:
- 对vector进行插入删除操作可能导致迭代器失效
- 失效的迭代器使用时会导致未定义行为
七、总结
C++容器是编写高效、简洁代码的基础,本文重点介绍了vector和unordered_map的使用方法和实战技巧。掌握这些容器的特性和适用场景,能够帮助你编写更优雅、更高效的C++代码。
容器库是C++标准库中最有价值的部分之一,除了本文介绍的vector和unordered_map,还有许多其他有用的容器(如map、set、deque等)值得深入学习。在实际开发中,选择合适的容器往往是解决问题的关键一步。
希望本文能帮助你更好地理解和使用C++容器,为你的C++编程之路打下坚实的基础。
更多推荐
所有评论(0)