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

相关推荐
思麟呀3 小时前
Linux的基础IO流
linux·运维·服务器·开发语言·c++
lkbhua莱克瓦243 小时前
Java基础——集合进阶3
java·开发语言·笔记
QT 小鲜肉4 小时前
【QT/C++】Qt定时器QTimer类的实现方法详解(超详细)
开发语言·数据库·c++·笔记·qt·学习
MeowKnight9584 小时前
【Qt】Qt实践记录3——UDP通信
笔记·qt
REDcker4 小时前
前端打包工具 - Rollup 打包工具笔记
前端·笔记
WBluuue4 小时前
数据结构与算法:树上倍增与LCA
数据结构·c++·算法
lkbhua莱克瓦244 小时前
Java基础——集合进阶用到的数据结构知识点1
java·数据结构·笔记·github
呆瑜nuage5 小时前
C++之红黑树
c++
进化中的码农5 小时前
Go中的泛型编程和reflect(反射)
开发语言·笔记·golang