c++map的使用

  • 自动排序:元素始终按照键的顺序存储

  • 唯一键值:每个键在 map 中只能出现一次

  • 高效操作:查找、插入、删除时间复杂度均为 O(log n)

  • 双向迭代:支持正向和反向迭代器遍历


前言

1. map 容器概述

std::map 是 C++ 标准模板库(STL)中的一个关联容器,它以键值对(key-value)的形式存储元素,并且会根据键(key)自动进行排序。map 内部通常实现为红黑树(一种自平衡二叉查找树),这保证了元素操作的高效性。

2. 基础用法详解

2.1 头文件与基本声明

#include <iostream>
#include <map>
#include <string>

int main() {
    // 声明一个键为string,值为int的map
    std::map<std::string, int> studentScores;
    
    // 声明并初始化map
    std::map<std::string, int> productPrices = {
        {"apple", 5},
        {"banana", 3},
        {"orange", 4}
    };
    
    return 0;
}
  1. 必须包含 <map> 头文件   模板参数:std::map<KeyType, ValueType>

  2. 可以使用初始化列表在声明时直接初始化

2.2 元素插入操作

// 继续上面的代码
// 方法1:使用insert成员函数
studentScores.insert(std::make_pair("Alice", 90));
studentScores.insert({"Bob", 85});  // C++11风格

// 方法2:使用operator[]
studentScores["Charlie"] = 88;  // 如果键不存在会自动创建
studentScores["David"];         // 会创建一个值为0的元素(int的默认值)

// 方法3:使用emplace(C++11)
studentScores.emplace("Eve", 92);  // 直接在map中构造元素,避免临时对象

emplace是原位构造比insert要省空间

  1. insert 方法不会覆盖已存在的键值

  2. operator[] 如果键不存在会创建新元素(值类型默认构造)

2.3 元素访问与查找

// 访问元素
std::cout << "Alice's score: " << studentScores["Alice"] << std::endl;

// 安全访问(不自动插入)
if (studentScores.find("Frank") != studentScores.end()) {
    std::cout << "Frank's score: " << studentScores["Frank"] << std::endl;
} else {
    std::cout << "Frank not found" << std::endl;
}

// 使用at方法(会检查边界)
try {
    std::cout << "Bob's score: " << studentScores.at("Bob") << std::endl;
} catch (const std::out_of_range& e) {
    std::cerr << "Error: " << e.what() << std::endl;
}

// 使用count检查存在性(返回0或1)
if (studentScores.count("Eve")) {
    std::cout << "Eve exists in the map" << std::endl;
}
  • 如果"Alice"存在,返回对应的值 如果"Alice"不存在,会自动插入一个默认构造的值(对于int是0)注意:这种访问方式会修改map(当key不存在时)

  • find方法不会自动插入不存在的key    如果找到,返回指向该元素的迭代器 如果没找到,返回end()迭代器    这是一种安全的检查方式,不会意外修改map

  • 如果"Bob"存在,返回对应的值    如果"Bob"不存在,抛出std::out_of_range异常   这是一种更安全的访问方式,适合需要异常处理的场景

  • count对于map返回0或1(因为key唯一)  不会修改map   比find更简洁,但如果你需要访问值,find更合适(因为它返回迭代器)

3. 元素删除与修改

3.1 删除操作

// 通过键删除
size_t erased = studentScores.erase("David");  // 返回删除的元素数量(0或1)

// 通过迭代器删除
auto it = studentScores.find("Charlie");
if (it != studentScores.end()) {
    studentScores.erase(it);  // 更高效,因为不需要再次查找
}

// 删除一定范围内的元素
auto begin = studentScores.begin();
auto end = studentScores.find("Eve");  // 不包括Eve
studentScores.erase(begin, end);

// 清空整个map
studentScores.clear();
  • 如果"David"存在,删除该键值对并返回1   时间复杂度:O(log n)(因为需要查找)注意:这种方式需要先查找key,再删除

  • 先通过find获取迭代器  如果找到(it != end()),使用迭代器删除  删除后迭代器会失效

  • 删除从begin到end(不包括end)之间的所有元素

3.2 修改元素值

// 修改现有元素的值
studentScores["Alice"] = 95;  // 直接通过键修改

// 通过迭代器修改
auto it = studentScores.find("Bob");
if (it != studentScores.end()) {
    it->second = 90;  // 可以修改value
    // it->first = "Bobby";  // 错误!不能修改key
}

// 插入或修改(C++17)
studentScores.insert_or_assign("Eve", 93);  // 如果存在则修改,不存在则插入

4. 遍历与迭代

4.1 多种遍历方式

// 1. 使用迭代器(传统方式)
std::cout << "\nAll scores (iterator):" << std::endl;
for (auto it = studentScores.begin(); it != studentScores.end(); ++it) {
    std::cout << it->first << ": " << it->second << std::endl;
}

// 2. 基于范围的for循环(C++11)
std::cout << "\nAll scores (range-based for):" << std::endl;
for (const auto& pair : studentScores) {
    std::cout << pair.first << ": " << pair.second << std::endl;
}

// 3. 结构化绑定(C++17)
std::cout << "\nAll scores (structured binding):" << std::endl;
for (const auto& [name, score] : studentScores) {
    std::cout << name << ": " << score << std::endl;
}

// 4. 反向迭代
std::cout << "\nAll scores (reverse order):" << std::endl;
for (auto rit = studentScores.rbegin(); rit != studentScores.rend(); ++rit) {
    std::cout << rit->first << ": " << rit->second << std::endl;
}
  • 通过 it->first 访问键(key),it->second 访问值(value)

  • pair 是一个 std::pair<const Key, Value>,包含 first(键)和 second(值 

  • 直接解构 std::pair[name, score] 分别绑定键和值

5. 容量与状态查询

// 检查是否为空
if (studentScores.empty()) {
    std::cout << "The map is empty" << std::endl;
} else {
    std::cout << "The map is not empty" << std::endl;
}

// 获取元素数量
std::cout << "Number of students: " << studentScores.size() << std::endl;

// 获取最大可能大小
std::cout << "Max possible size: " << studentScores.max_size() << std::endl;

6. 高级特性与应用

6.1 自定义排序规则

// 自定义比较函数:按字符串长度排序
struct LengthCompare {
    bool operator()(const std::string& a, const std::string& b) const {
        if (a.length() == b.length()) {
            return a < b;  // 长度相同则按字典序
        }
        return a.length() < b.length();
    }
};

int main() {
    std::map<std::string, int, LengthCompare> lenMap = {
        {"apple", 1},
        {"banana", 2},
        {"cherry", 3},
        {"kiwi", 4}
    };

    std::cout << "\nMap ordered by string length:" << std::endl;
    for (const auto& [fruit, num] : lenMap) {
        std::cout << fruit << ": " << num << std::endl;
    }
    // 输出顺序:kiwi, apple, banana, cherry
    return 0;
}
  1. 比较函数必须定义严格的弱序   如果两个键"等价"(即比较函数返回false),map会视为相同键    自定义比较函数可以实现各种复杂排序需求

6.2 合并两个map(C++17)

std::map<std::string, int> map1 = {{"A", 1}, {"B", 2}};
std::map<std::string, int> map2 = {{"B", 20}, {"C", 3}};

// 合并map2到map1
map1.merge(map2);

// 此时:
// map1: {"A":1, "B":2, "C":3} (B的值保留map1的)
// map2: {"B":20} (冲突的键留在原map中)

merge 会尝试移动元素到目标map 对于冲突的键保留目标map的值  源map中未移动的元素会保留

7. 性能分析与优化

7.1 时间复杂度对比

操作时间复杂度说明
插入O(log n)需要保持树平衡
删除O(log n)需要保持树平衡
查找O(log n)二分查找
遍历O(n)需要访问每个节点
[]访问O(log n)可能触发插入操作

8. 常见问题解答

map和unordered_map如何选择?

std::unordered_map 是 C++ 标准库中的一种关联容器,它提供基于哈希表的键值对存储。与 std::map 不同,unordered_map 中的元素不是排序的。

  • 需要元素有序 → map     需要最高查找性能 → unordered_map

  • 需要内存效率 → 通常unordered_map更好  需要稳定性能 → map(没有哈希冲突问题)

 map的迭代器失效情况?

插入操作:不会使任何迭代器失效  删除操作:只会使被删除元素的迭代器失效

修改值:不会使迭代器失效(但不能修改键)


总结

std::map 是C++中功能强大的关联容器,通过红黑树实现保证了操作的稳定效率。本文详细介绍了map的各类操作,从基础插入访问到高级特性如自定义排序、节点操作等,并提供了大量示例代码和分析。正确使用map可以显著提高程序的效率和可读性,是每个C++开发者都应该掌握的容器之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值