C++笔记——STL map

map 是 C++ 标准模板库(STL)中一个非常重要的关联式容器,它提供了一种"键-值"对的存储和访问机制。

1. 核心概念与特性

  • 键值对 :map 存储的元素都是 std::pair<const Key, T> 类型的对象。简单理解,就是一个唯一的 Key(关键字)映射到一个 Value(值)。

  • 有序性 :map 中的元素会根据 键(Key) 自动进行排序。默认情况下,它使用 std::less<Key>(即 < 操作符)对键进行升序排序。你也可以通过自定义比较函数来指定排序规则。

  • 唯一性 :map 容器保证所有元素的键都是唯一的。如果你尝试插入一个已经存在的键,插入操作会失败。

  • 底层实现 :map 通常使用红黑树 实现。红黑树是一种自平衡的二叉搜索树,这使得 map 在插入、删除和查找操作上的时间复杂度都能保持在 O(log n),效率非常稳定。

  • 不可修改的键 :一旦一个元素被插入到 map 中,它的键就是常量,不能被修改。这是为了不破坏红黑树的结构。但你可以修改该键对应的值。

2. 基本用法

头文件与声明

cpp

复制代码
#include <iostream>
#include <map> // 必须包含的头文件
#include <string>

int main() {
    // 声明一个 map,键为 string,值为 int
    std::map<std::string, int> studentScores;

    // 也可以声明时指定自定义比较规则(例如降序)
    // std::map<std::string, int, std::greater<std::string>> studentScoresDesc;

    return 0;
}
常用操作
插入元素

有几种常见的方法:

  1. 使用 insert 成员函数:

    cpp

    复制代码
    std::map<std::string, int> studentScores;
    
    // 方法1:使用 make_pair
    studentScores.insert(std::make_pair("Alice", 95));
    // 方法2:使用花括号 {} (C++11)
    studentScores.insert({"Bob", 88});
    // 方法3:使用 pair 的构造函数
    studentScores.insert(std::pair<const std::string, int>("Charlie", 70));
    
    // insert 会返回一个 pair<iterator, bool>
    auto ret = studentScores.insert({"Alice", 100}); // 键 "Alice" 已存在,插入失败
    if (!ret.second) {
        std::cout << "Insertion failed. Key 'Alice' already exists.\n";
    }
  2. 使用 operator[](最常用、最直观):

    cpp

    复制代码
    std::map<std::string, int> studentScores;
    
    studentScores["Alice"] = 95; // 如果 "Alice" 不存在,会先创建一个并赋值
    studentScores["Bob"] = 88;
    studentScores["Alice"] = 100; // 因为 "Alice" 已存在,这会修改其对应的值
    
    std::cout << studentScores["Alice"]; // 输出 100

    注意 :operator[] 如果找不到键,会自动插入 一个具有该键的新元素,并用值类型的默认构造函数初始化其值(对于 int 是 0)。这有时会导致非预期的插入。

访问元素
  1. 使用 operator[]:

    cpp

    复制代码
    int score = studentScores["Alice"]; // 如果 "Alice" 不存在,会插入一个新元素!
  2. 使用 at 成员函数 (C++11):

    cpp

    复制代码
    int score = studentScores.at("Alice"); // 如果 "Alice" 不存在,会抛出 std::out_of_range 异常

    推荐在确定键存在时使用 at,因为它更安全,不会意外插入新元素。

  3. 使用迭代器:

    cpp

    复制代码
    auto it = studentScores.find("Alice");
    if (it != studentScores.end()) {
        // it->first 是键 ("Alice")
        // it->second 是值 (100)
        std::cout << it->first << ": " << it->second << std::endl;
    } else {
        std::cout << "Key not found.\n";
    }
查找元素

使用 find 成员函数,它返回一个迭代器。

cpp

复制代码
std::map<std::string, int>::iterator it = studentScores.find("Bob");
// 或者用 auto
auto it = studentScores.find("Bob");

if (it != studentScores.end()) {
    std::cout << "Found: " << it->first << " -> " << it->second << std::endl;
} else {
    std::cout << "Key 'Bob' not found." << std::endl;
}

注意 :不要使用 std::find 算法来查找 map 的元素,因为它是线性搜索,效率远低于 map 自己的 find 方法(O(n) vs O(log n))。

删除元素

使用 erase 成员函数。

cpp

复制代码
std::map<std::string, int> studentScores = {{"Alice", 95}, {"Bob", 88}, {"Charlie", 70}};

// 方法1:通过迭代器删除
auto it = studentScores.find("Bob");
if (it != studentScores.end()) {
    studentScores.erase(it);
}

// 方法2:通过键删除
size_t numRemoved = studentScores.erase("Charlie"); // 返回被删除元素的个数(对于map是0或1)

// 方法3:删除一个范围
studentScores.erase(studentScores.begin(), studentScores.find("Alice")); // 删除 "Alice" 之前的所有元素
遍历元素

由于 map 是有序的,遍历时会按照键的顺序进行。

cpp

复制代码
std::map<std::string, int> studentScores = {{"Alice", 95}, {"Bob", 88}, {"Charlie", 70}};

// 方法1:使用迭代器
for (std::map<std::string, int>::iterator it = studentScores.begin(); it != studentScores.end(); ++it) {
    std::cout << it->first << ": " << it->second << std::endl;
}

// 方法2:使用基于范围的for循环 (C++11) - 推荐!
for (const auto& pair : studentScores) {
    std::cout << pair.first << ": " << pair.second << std::endl;
}

// 输出:
// Alice: 95
// Bob: 88
// Charlie: 70

3. 复杂度分析

  • 插入 :O(log n)

  • 查找 :O(log n)

  • 删除 :O(log n)

  • 遍历 :O(n)

4. 与其他容器的比较

特性 std::map std::unordered_map std::multimap
有序性 有序(按键排序) 无序 有序(按键排序)
底层结构 红黑树(平衡BST) 哈希表 红黑树(平衡BST)
查找复杂度 O(log n) 平均 O(1),最坏 O(n) O(log n)
键的唯一性 键唯一 键唯一 键可重复
需要哈希函数 否 是 否
需要比较函数 是 否 是
使用场景 需要元素有序 需要极速查找,不关心顺序 需要有序且键可重复

5. 高级用法与技巧

自定义比较函数

当键是自定义类型或需要特殊排序规则时使用。

cpp

复制代码
struct Student {
    std::string name;
    int id;
};

// 自定义比较函数对象
struct CompareByID {
    bool operator()(const Student& a, const Student& b) const {
        return a.id < b.id; // 按 ID 升序排列
    }
};

int main() {
    std::map<Student, int, CompareByID> studentScores;

    studentScores[{"Alice", 2}] = 90;
    studentScores[{"Bob", 1}] = 85;
    studentScores[{"Charlie", 3}] = 95;

    // 遍历时,会按 ID 1(Bob), 2(Alice), 3(Charlie) 的顺序输出
    for (const auto& pair : studentScores) {
        std::cout << "ID: " << pair.first.id << ", Name: " << pair.first.name << ", Score: " << pair.second << std::endl;
    }
    return 0;
}
emplace 高效构造 (C++11)

emplace 可以直接在容器内部构造元素,避免了临时对象的创建和拷贝/移动。

cpp

复制代码
std::map<std::string, int> studentScores;
studentScores.emplace("David", 92); // 直接在 map 中构造 pair("David", 92)

总结

std::map 是一个强大且灵活的有序关联容器,它通过键值对的形式存储数据,并保证键的唯一性和有序性。其基于红黑树的实现提供了稳定的对数时间复杂度操作。在选择使用 map 时,需要考虑:

  • 你是否需要元素有序?

  • 你的键是否是唯一的?

  • 你的应用场景是否能接受 O(log n) 的查找时间?

如果不需要有序,且追求极致的查找速度,可以考虑 std::unordered_map。如果需要键可以重复,则考虑 std::multimap。

相关推荐
一隅论数智2 天前
给AI一张“业务概念地图“:本体如何从哲学走向企业智能
大数据·人工智能·经验分享·笔记·学习·学习方法·政务
倒头就睡的小比特2 天前
算法竞赛C++常用的STL
c++·算法
weilx12342 天前
C++笔记-文件IO-<fcntl.h>
c++
Smileyqp沛沛2 天前
前端?C++ ?较大差异基础罗列
c++·基础·前端转c++
C语言小火车2 天前
C/C++ 为什么需要编译器?
开发语言·c++
深圳老胡2 天前
STM32F407 控制 L6470 步进电机驱动 —— 控制过程简介
笔记·stm32·单片机·嵌入式硬件·代码规范
旖旎夜光2 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
吞下星星的少年·-·3 天前
C++ 萌新语法入门篇
c++·算法比赛
Because_of_Her13 天前
并查集-听课笔记
笔记·算法·并查集
霍霍的袁3 天前
【C++】map 和 set 的使用 | 从用法到底层
开发语言·c++·学习·visual studio