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类型,避免整数溢出

六、容器使用的最佳实践

  1. 选择合适的容器

    • 需要快速随机访问时选择vector
    • 需要频繁插入删除时选择list
    • 需要键值对存储和快速查找时选择unordered_map
  2. 注意容量与大小的区别

    • size()返回实际元素个数
    • capacity()返回当前分配的存储空间能容纳的元素个数
    • 合理使用reserve()减少内存重分配
  3. 使用引用(&)提高效率

    • 遍历容器时使用引用避免不必要的拷贝
    • 函数参数使用引用传递大型容器
  4. 利用标准算法

    • 熟悉头文件中的常用算法(如sort、find、for_each)
    • 算法与容器结合使用能大幅提高代码效率和可读性
  5. 注意迭代器失效问题

    • 对vector进行插入删除操作可能导致迭代器失效
    • 失效的迭代器使用时会导致未定义行为

七、总结

C++容器是编写高效、简洁代码的基础,本文重点介绍了vector和unordered_map的使用方法和实战技巧。掌握这些容器的特性和适用场景,能够帮助你编写更优雅、更高效的C++代码。

容器库是C++标准库中最有价值的部分之一,除了本文介绍的vector和unordered_map,还有许多其他有用的容器(如map、set、deque等)值得深入学习。在实际开发中,选择合适的容器往往是解决问题的关键一步。

希望本文能帮助你更好地理解和使用C++容器,为你的C++编程之路打下坚实的基础。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐