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

相关推荐
qq_310658515 小时前
mediasoup源码走读(六)——NetEQ
服务器·c++·音视频
风123456789~5 小时前
【健康管理】第13章 医学伦理与职业道德
笔记·证书·健康管理
FakeOccupational5 小时前
【电路笔记 信号】信号能量在传输线中指数衰减+衰减系数特性+能量介质传播与指数衰减的关系+dB标度+功率指数衰减
笔记
qq_433554546 小时前
C++树形DP(树上分组背包)
c++·算法·深度优先
电子_咸鱼6 小时前
常见面试题——滑动窗口算法
c++·后端·python·算法·leetcode·哈希算法·推荐算法
hit56实验室7 小时前
北京历年住房公积金月缴存额上限及同比增长率表
笔记
挪威的深林7 小时前
【ROS笔记5】tf 和 tf2_ros 的使用区别
笔记
阿蒙Amon7 小时前
JavaScript学习笔记:7.数字和字符串
javascript·笔记·学习
一语雨在生无可恋敲代码~8 小时前
hello-agents 笔记1
笔记
TL滕8 小时前
从0开始学算法——第十五天(滑动窗口练习)
笔记·学习·算法