0

0

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

P粉602998670

P粉602998670

发布时间:2025-08-29 08:54:02

|

594人浏览过

|

来源于php中文网

原创

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
:动态数组的瑞士军刀

立即学习C++免费学习笔记(深入)”;

std::vector
本质上就是一个动态数组,它最吸引人的地方在于可以根据需要自动调整大小。当你需要一个元素序列,并且不确定最终会有多少个元素时,
vector
几乎是第一选择。

#include 
#include 
#include 
#include  // 即使是vector的例子,也可能需要其他头文件

int main() {
    // 声明一个存储整数的vector
    std::vector 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::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 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 
#include 
#include 
// vector的例子已经包含了iostream和string

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

// int main() { // 如果是单独的map例子,可以这样开始
    // 声明一个存储string键和int值的map
    std::map 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
的优点远超其缺点,是名副其实的“瑞士军刀”。

Calliper 文档对比神器
Calliper 文档对比神器

文档内容对比神器

下载
// 更多vector操作示例
#include 
#include 
#include  // 用于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& vec) {
    for (const auto& obj : vec) {
        obj.print();
    }
    std::cout << std::endl;
}

// int main() { // 假设在之前的main函数之后继续
    std::vector 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 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 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 
#include 
#include 
#include 

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

539

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

21

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

31

2026.01.06

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

75

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

36

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

61

2025.11.17

java判断map相关教程
java判断map相关教程

本专题整合了java判断map相关教程,阅读专题下面的文章了解更多详细内容。

42

2025.11.27

数据库Delete用法
数据库Delete用法

数据库Delete用法:1、删除单条记录;2、删除多条记录;3、删除所有记录;4、删除特定条件的记录。更多关于数据库Delete的内容,大家可以访问下面的文章。

279

2023.11.13

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

54

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
SQL 教程
SQL 教程

共61课时 | 3.7万人学习

C 教程
C 教程

共75课时 | 4.3万人学习

PHP面向对象基础课程(更新中)
PHP面向对象基础课程(更新中)

共12课时 | 0.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号