C++标准库容器 vector map使用示例(库容.示例.标准.vector.map...)

wufei123 发布于 2025-08-29 阅读(4)
C++标准库中vector和map是核心容器,vector提供连续存储的动态数组,支持高效随机访问和自动扩容,适合频繁遍历和元素数量不确定的场景;map基于红黑树实现,提供自动按键排序的键值对存储,查找、插入、删除操作时间复杂度为O(log n),适用于需要有序数据结构的场景。两者分别在性能和有序性上具有优势,是C++数据管理的基础工具。

c++标准库容器 vector map使用示例

C++标准库中的

vector
map
是两种基石级的容器,它们分别提供了动态数组和键值对存储的功能,几乎是每个C++开发者日常工作中不可或缺的工具。简单来说,
vector
就像一个可以自动扩容的数组,适合存储同类型元素的序列;而
map
则是一个基于键(key)来查找值(value)的字典,并且它会自动保持键的有序性。理解并熟练运用它们,是编写高效、可维护C++代码的关键一步。

std::vector
std::map
使用示例

在我看来,C++标准库的容器设计得非常精妙,尤其是

vector
map
,它们几乎覆盖了我们日常编程中大部分数据组织的需求。
vector
提供了一种连续存储的方式,这对于性能敏感的场景非常有利;而
map
则以其键值对的直观性,让数据管理变得异常清晰。

std::vector
:动态数组的瑞士军刀

std::vector
本质上就是一个动态数组,它最吸引人的地方在于可以根据需要自动调整大小。当你需要一个元素序列,并且不确定最终会有多少个元素时,
vector
几乎是第一选择。
#include <vector>
#include <string>
#include <iostream>
#include <map> // 即使是vector的例子,也可能需要其他头文件

int main() {
    // 声明一个存储整数的vector
    std::vector<int> numbers;

    // 添加元素到vector的末尾
    numbers.push_back(10);
    numbers.push_back(20);
    numbers.push_back(30);
    numbers.push_back(40);

    // 访问元素
    std::cout << "第一个元素: " << numbers[0] << std::endl; // 可能会越界,不检查边界
    std::cout << "第二个元素 (安全访问): " << numbers.at(1) << std::endl; // at()会进行边界检查,越界抛出异常

    // 遍历vector
    std::cout << "所有元素 (范围for循环): ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 使用迭代器遍历
    std::cout << "所有元素 (迭代器): ";
    for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 获取vector大小和容量
    std::cout << "Vector大小: " << numbers.size() << std::endl;
    std::cout << "Vector容量: " << numbers.capacity() << std::endl; // 容量通常 >= 大小

    // 插入元素到指定位置 (效率较低,因为可能需要移动后续所有元素)
    numbers.insert(numbers.begin() + 2, 25); // 在索引2处插入25
    std::cout << "插入25后的Vector: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 删除元素 (同样效率较低)
    numbers.erase(numbers.begin() + 0); // 删除第一个元素
    std::cout << "删除第一个元素后的Vector: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    // 清空vector
    numbers.clear();
    std::cout << "清空后Vector大小: " << numbers.size() << std::endl;

    // 预留空间,避免频繁重新分配
    std::vector<std::string> names;
    names.reserve(100); // 预留100个string的空间
    for (int i = 0; i < 50; ++i) {
        names.push_back("Name_" + std::to_string(i));
    }
    std::cout << "预留空间后Vector容量: " << names.capacity() << std::endl;

    return 0;
}

std::map
:有序键值对的典范

std::map
提供了一种将键(Key)映射到值(Value)的机制,而且它最大的特点就是所有元素都会根据键自动排序。这意味着当你遍历
map
时,你会得到一个按键升序排列的序列。这在需要快速查找、并且保持数据有序的场景下非常有用。
#include <map>
#include <string>
#include <iostream>
// vector的例子已经包含了iostream和string

// main函数可以拆分或合并,这里为了演示方便,放在同一个main函数里
// 实际使用中,通常会封装在不同的函数或类中

// int main() { // 如果是单独的map例子,可以这样开始
    // 声明一个存储string键和int值的map
    std::map<std::string, int> ages;

    // 插入元素
    ages["Alice"] = 30; // 如果键不存在,则插入;如果存在,则更新值
    ages["Bob"] = 25;
    ages.insert({"Charlie", 35}); // 另一种插入方式
    ages.insert(std::make_pair("David", 28)); // 也可以用std::make_pair

    // 访问元素
    std::cout << "Alice的年龄: " << ages["Alice"] << std::endl;
    // 注意:使用[]访问一个不存在的键会默认插入一个新元素,其值会被默认初始化
    // std::cout << "Eve的年龄: " << ages["Eve"] << std::endl; // 此时map中会多一个键为"Eve",值为0的元素

    // 安全访问元素,检查键是否存在
    auto it_bob = ages.find("Bob");
    if (it_bob != ages.end()) {
        std::cout << "Bob的年龄 (find): " << it_bob->second << std::endl;
    } else {
        std::cout << "Bob不存在于map中。" << std::endl;
    }

    auto it_frank = ages.find("Frank");
    if (it_frank != ages.end()) {
        std::cout << "Frank的年龄 (find): " << it_frank->second << std::endl;
    } else {
        std::cout << "Frank不存在于map中。" << std::endl;
    }

    // 遍历map (按键的升序)
    std::cout << "Map中的所有元素: " << std::endl;
    for (const auto& pair : ages) { // 推荐使用const auto&
        std::cout << "  键: " << pair.first << ", 值: " << pair.second << std::endl;
    }

    // 删除元素
    ages.erase("Bob");
    std::cout << "删除Bob后Map中的所有元素: " << std::endl;
    for (const auto& pair : ages) {
        std::cout << "  键: " << pair.first << ", 值: " << pair.second << std::endl;
    }

    // 获取map大小
    std::cout << "Map大小: " << ages.size() << std::endl;

    return 0;
// } // 如果是单独的map例子,这里结束
为什么
std::vector
是C++中动态数组的首选?

在我多年的C++开发经验里,

std::vector
几乎是我每次需要一个动态大小的同类型元素集合时的首选。它不仅仅是“能用”,更是因为它的设计哲学和底层实现,使其在大多数场景下都表现出色。

首先,

vector
最核心的优势在于它的连续内存存储。这意味着
vector
中的所有元素在内存中是紧挨着存放的。这种布局对现代CPU的缓存机制极其友好。当你访问
vector
中的一个元素时,CPU很可能会把这个元素以及它周围的几个元素一起加载到缓存中,这样你接下来访问相邻元素时,就能以极快的速度从缓存中获取,而不是从慢速的主内存中读取。这对于遍历操作或者需要频繁访问相邻元素的算法来说,性能提升是巨大的。相比之下,像
std::list
这种链表结构,元素在内存中是分散的,每次访问都可能导致缓存失效,从而性能下降。

其次,

vector
提供了O(1)的随机访问能力。因为是连续存储,给定一个索引,
vector
可以直接通过简单的指针算术计算出元素的内存地址,这和原生数组的访问速度是一样的。无论是通过
[]
操作符还是
at()
方法,都能实现常数时间访问。

再者,

vector
的自动内存管理极大地简化了开发。你不再需要手动
new
delete
内存,也不用担心内存泄漏。当你
push_back
一个元素时,如果当前容量不足,
vector
会自动重新分配一块更大的内存,并将现有元素拷贝(或移动)过去,然后释放旧内存。虽然重新分配是一个相对昂贵的操作,但
vector
通常会以指数级增长容量(比如每次翻倍),这使得均摊下来,
push_back
操作的复杂度依然是O(1)。

当然,

vector
并非没有它的“脾气”。比如,频繁在中间插入或删除元素效率会比较低,因为这需要移动大量后续元素。当存储的元素是复杂对象时,拷贝或移动的成本也需要考虑。但总的来说,对于大多数需要动态数组的场景,
vector
的优点远超其缺点,是名副其实的“瑞士军刀”。
// 更多vector操作示例
#include <vector>
#include <iostream>
#include <algorithm> // 用于std::sort

struct MyObject {
    int id;
    std::string name;

    MyObject(int i, const std::string& n) : id(i), name(n) {
        // std::cout << "MyObject(" << id << ") 构造" << std::endl;
    }
    MyObject(const MyObject& other) : id(other.id), name(other.name) {
        // std::cout << "MyObject(" << id << ") 拷贝构造" << std::endl;
    }
    MyObject(MyObject&& other) noexcept : id(other.id), name(std::move(other.name)) {
        // std::cout << "MyObject(" << id << ") 移动构造" << std::endl;
    }
    MyObject& operator=(const MyObject& other) {
        if (this != &other) {
            id = other.id;
            name = other.name;
            // std::cout << "MyObject(" << id << ") 拷贝赋值" << std::endl;
        }
        return *this;
    }
    MyObject& operator=(MyObject&& other) noexcept {
        if (this != &other) {
            id = other.id;
            name = std::move(other.name);
            // std::cout << "MyObject(" << id << ") 移动赋值" << std::endl;
        }
        return *this;
    }
    ~MyObject() {
        // std::cout << "MyObject(" << id << ") 析构" << std::endl;
    }

    void print() const {
        std::cout << "{ID: " << id << ", Name: " << name << "} ";
    }
};

void print_vector_objects(const std::vector<MyObject>& vec) {
    for (const auto& obj : vec) {
        obj.print();
    }
    std::cout << std::endl;
}

// int main() { // 假设在之前的main函数之后继续
    std::vector<MyObject> objects;
    objects.reserve(5); // 预留空间,避免频繁重新分配

    std::cout << "--- Emplace_back vs Push_back ---" << std::endl;
    // emplace_back 直接在vector内部构造对象,避免一次拷贝或移动
    objects.emplace_back(1, "Alpha"); // 直接构造
    objects.push_back(MyObject(2, "Beta")); // 构造临时对象,然后移动到vector中 (C++11后通常是移动)
    objects.emplace_back(3, "Gamma");
    print_vector_objects(objects);

    std::cout << "\n--- 调整大小 (resize) ---" << std::endl;
    objects.resize(5, MyObject(0, "Default")); // 调整大小到5,新元素用默认值填充
    print_vector_objects(objects);
    objects.resize(2); // 缩小大小,多余的元素被析构
    print_vector_objects(objects);

    std::cout << "\n--- 排序 ---" << std::endl;
    objects.emplace_back(5, "Epsilon");
    objects.emplace_back(4, "Delta");
    std::sort(objects.begin(), objects.end(), [](const MyObject& a, const MyObject& b) {
        return a.id < b.id;
    });
    print_vector_objects(objects);

    std::cout << "\n--- 迭代器失效示例 ---" << std::endl;
    std::vector<int> nums = {1, 2, 3, 4, 5};
    auto it = nums.begin() + 2; // 指向3
    std::cout << "原始Vector: ";
    for(int n : nums) std::cout << n << " ";
    std::cout << std::endl;

    // 插入操作可能导致迭代器失效
    nums.insert(nums.begin(), 0); // 在开头插入,所有迭代器都可能失效
    // std::cout << "失效的迭代器指向: " << *it << std::endl; // 此时it可能指向错误位置或野指针

    // 正确的做法是重新获取迭代器
    it = nums.begin() + 3; // 重新指向新的3 (原先的2)
    std::cout << "插入后Vector: ";
    for(int n : nums) std::cout << n << " ";
    std::cout << std::endl;
    std::cout << "重新获取的迭代器指向: " << *it << std::endl;

    // 清空并释放内存
    std::cout << "\n--- 清空并释放内存 (shrink_to_fit) ---" << std::endl;
    std::vector<int> big_vec;
    big_vec.reserve(100);
    for(int i = 0; i < 10; ++i) big_vec.push_back(i);
    std::cout << "初始容量: " << big_vec.capacity() << std::endl;
    big_vec.clear();
    std::cout << "清空后容量: " << big_vec.capacity() << std::endl; // 容量通常不变
    big_vec.shrink_to_fit(); // 请求将容量缩小到与大小匹配
    std::cout << "shrink_to_fit后容量: " << big_vec.capacity() << std::endl; // 容量现在可能为0

    // return 0; // 如果是单独的main函数
// }
std::map
std::unordered_map
:如何选择合适的键值对容器?

这是个很经典的问题,也是我在设计系统时经常需要权衡的。

std::map
std::unordered_map
都提供了键值对的存储功能,但它们底层的实现机制截然不同,这直接导致了它们在性能特性和适用场景上的巨大差异。

std::map
:有序的魅力与对数时间复杂度

std::map
的底层通常是一个红黑树(一种自平衡二叉搜索树)。这种数据结构保证了
map
中的元素总是按照键的严格弱序进行排列。
  • 优点:

    • 自动排序: 这是
      map
      最显著的特点。当你需要按键顺序遍历元素,或者需要获取某个键范围内的元素时,
      map
      是理想选择。
    • 稳定的性能: 插入、查找和删除操作的时间复杂度都是O(log N),其中N是
      map
      中元素的数量。这个性能是稳定的,不会出现像哈希表那样最坏情况下的性能退化。
    • 无需哈希函数: 键类型只需要支持比较操作(
      operator<
      ),不需要提供哈希函数。
  • 缺点:

    • 相对较慢: 相比于
      unordered_map
      平均O(1)的性能,O(log N)在N很大时会显得慢一些。
    • 内存开销: 每个节点都需要存储键、值以及指向父节点和子节点的指针,这会比
      unordered_map
      有更大的内存开销。
    • 非连续内存: 节点在内存中是分散的,可能导致缓存效率不高。

std::unordered_map
:速度的追求与平均常数时间复杂度

std::unordered_map
的底层实现是哈希表。它通过一个哈希函数将键映射到表中的一个桶(bucket),然后在这个桶中查找值。
  • 优点:

    • 极快的平均性能: 插入、查找和删除操作的平均时间复杂度是O(1)。在数据量大、对查找速度要求极高的场景下,
      unordered_map
      通常表现更优。
    • 更低的内存开销(相对而言): 虽然也有一些内部结构,但通常比
      map
      的每个节点开销小。
  • 缺点:

    • 无序性: 元素在
      unordered_map
      中没有固定的顺序,遍历结果是不确定的。
    • 最坏情况: 如果哈希函数设计不当,或者遇到大量哈希冲突,所有元素都映射到同一个桶,那么操作的复杂度可能退化到O(N),性能会急剧下降。
    • 需要哈希函数: 键类型必须提供一个可用的哈希函数(
      std::hash
      特化或自定义)。对于自定义类型,你需要自己提供一个哈希函数。

如何选择?

我的经验是,选择哪个容器,主要看你的核心需求:

  1. 是否需要键的有序性? 如果你需要按键排序遍历,或者需要查找某个范围内的键,那么
    std::map
    是你的不二之选。比如,你需要按日期顺序存储事件,或者按字母顺序存储用户名字典。
  2. 是否对查找/插入速度有极致要求,且不关心顺序? 如果你的主要操作是快速查找、插入和删除,并且顺序无关紧要,那么
    std::unordered_map
    通常会提供更好的平均性能。比如,统计词频、缓存数据等。
  3. 键的类型是否复杂? 对于自定义类型作为键,
    std::unordered_map
    需要你提供一个合适的哈希函数,这可能需要额外的工作。而
    std::map
    只需要重载
    operator<

通常情况下,如果对性能没有特别严苛的要求,并且数据量不是天文数字,

std::map
的O(log N)性能已经足够好,而且其有序性常常能带来便利。但如果你真的在追求极致的查找速度,并且能够很好地管理哈希冲突,
std::unordered_map
无疑是更强大的工具。
// std::map 和 std::unordered_map 示例
#include <map>
#include <unordered_map>
#include <string>
#include <iostream>

struct Person {
    int id;
    std::string name;

    // 对于std::map,需要定义 operator<
    bool operator<(const Person& other) const {
        return id < other.id;
    }

    // 对于std::unordered_map,需要定义 operator==
    bool operator==(const Person& other) const {
        return id == other.id;
    }
};

// 为std::unordered_map 定义 Person 的哈希函数
namespace std {
    template <>
    struct

以上就是C++标准库容器 vector map使用示例的详细内容,更多请关注知识资源分享宝库其它相关文章!

标签:  库容 示例 标准 

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。