关联式容器:map 与 multimap 的键值对存储

关联式容器:map 与 multimap 的键值对存储

在上一篇博客中,我们讲解了set与multiset的有序存储特性------它们以"元素本身作为关键字",核心解决"有序去重"和"有序可重复"的存储需求。而今天要介绍的map与multimap,同样是C++ STL中常用的有序关联式容器,却有着本质区别:它们以"键值对(key-value)"为存储单元,关键字(key)与值(value)分离,专门解决"通过关键字快速查找对应值"的场景。

很多开发者在初学时,容易混淆map与multimap,也常把它们和set系列弄混------比如"误以为map的value是关键字""想用map存储重复关键字却发现失败""不知道multimap如何高效获取重复key对应的所有value"。更有甚者,在需要键值对存储时,盲目用vector存储结构体模拟键值对,忽略了map/multimap自带的有序性和高效查找特性,导致代码冗余、性能低下。

今天这篇博客,我们聚焦map与multimap的核心------键值对存储,延续上一篇的讲解逻辑,从底层原理简化解析、核心特性对比、基础用法、进阶实操,到适用场景与避坑指南,用通俗的讲解+可直接运行的示例代码,帮你彻底吃透这两个容器,搞懂"它们如何存储键值对""什么时候用map、什么时候用multimap""怎么高效运用它们的键值对有序特性"。

一、先搞懂核心:map 与 multimap 是什么?

map与multimap同属于STL中的有序关联式容器,和set/multiset一样,底层同样基于红黑树(自平衡二叉搜索树)实现。它们的核心功能都是"按关键字(key)有序存储键值对(key-value)",区别仅在于"是否允许存储重复的关键字(key)"------这也是我们区分两者的关键,没有其他本质差异。

这里先明确一个核心区别(与set系列对比):

  • set/multiset:存储"单一值",元素本身就是关键字,核心是"有序存储元素";

  • map/multimap:存储"键值对",关键字(key)与值(value)分离,核心是"通过key有序查找value"。

1. 核心定义(精准不冗余)

  • map(映射):存储唯一关键字(key)的键值对,自动按关键字升序(默认)排序,不允许重复key。若插入重复key,会被自动忽略,不会报错;每个key唯一对应一个value,实现"一对一"映射。

  • multimap(多重映射):存储可重复关键字(key)的键值对,自动按关键字升序(默认)排序,允许重复key。若插入重复key,会全部保留,且按排序规则插入到对应位置,维持整体有序;一个key可对应多个value,实现"一对多"映射。

一句话总结:两者都按key有序存储键值对,唯一区别是map的key唯一(一对一),multimap的key可重复(一对多)

2. 底层原理简化解析(复用红黑树逻辑,重点讲键值对特性)

map与multimap的底层实现和set/multiset一致,均基于红黑树,因此它们继承了红黑树的核心优势:自动排序、高效操作(插入、删除、查找O(log n))。但由于存储的是键值对,红黑树的节点结构有所不同------每个节点存储一个pair<const key_type, value_type>类型的键值对,其中:

  • key是const修饰的:不可修改,因为key是红黑树排序的依据,修改key会破坏红黑树的有序性,导致容器失效;

  • value是可修改的:value仅用于存储数据,与红黑树的排序无关,修改value不会影响容器的有序性;

  • 排序规则:红黑树仅根据key进行排序,与value无关,默认按key升序排列,也可自定义key的排序规则。

补充两个关键细节(与set系列对比):

  • 去重机制(仅map):map的去重仅针对key,与value无关------即使两个键值对的value不同,只要key相同,插入时就会被忽略;

  • 重复存储(仅multimap):multimap的重复仅针对key,多个相同key对应的value可以不同,且会按插入顺序(或排序规则)相邻存储。

3. map vs multimap 核心特性对比表(一目了然)

为了快速区分两者,避免用错,同时对比set系列的核心差异,这里整理了核心特性对比(聚焦键值对存储、key唯一性、操作效率等关键维度):

特性 map(映射) multimap(多重映射) 补充(与set对比)
存储单元 键值对(key-value),pair类型 键值对(key-value),pair类型 set存储单一元素(元素=key)
有序性 按key自动升序(默认),维持有序 按key自动升序(默认),维持有序 set按元素本身(key)有序
重复key 不允许,插入重复key会被忽略 允许,重复key全部保留,按序排列 set不允许重复元素(key)
key特性 key唯一、不可修改 key可重复、不可修改 set元素(key)不可修改
value特性 可修改,与排序无关 可修改,与排序无关 set无value,仅存储key
映射关系 一对一(一个key对应一个value) 一对多(一个key对应多个value) set无映射关系,仅存储key
插入/删除/查找效率 O(log n)(基于红黑树,按key操作) O(log n)(基于红黑树,与map一致) set效率一致,按元素(key)操作
迭代器特性 双向迭代器,解引用返回pair对象 双向迭代器,与map完全一致 set迭代器解引用返回单一元素
核心用途 通过唯一key快速查找value(一对一映射) 通过重复key获取多个value(一对多映射) 有序去重存储、高效查找元素
关键提醒1:map和multimap中的key不可修改,但value可修改!修改key会破坏红黑树的有序性,修改value不影响有序性。

关键提醒2:map的[]运算符是其特有用法(multimap不支持),可快速插入/访问键值对,但需注意使用陷阱(后续避坑部分讲解)。

二、基础用法:从初始化到核心操作(附示例,可直接复制)

使用map与multimap前,需包含头文件 #include <map>,并使用std命名空间(或显式调用std::map、std::multimap)。由于两者用法高度一致,我们统一讲解,重点标注差异点(如map的[]运算符、multimap的重复key操作)。

核心前提:map与multimap存储的键值对是std::pair类型,pair<const K, V>,其中K是key的类型,V是value的类型。可通过pair.first访问key,pair.second访问value。

1. 初始化(5种常用方式,map和multimap通用,差异标注)

初始化时可指定key的排序规则(默认升序),以下示例默认升序,自定义排序后续单独讲解。注意:multimap不支持用[]运算符初始化。

cpp 复制代码
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
    // 方式1:默认初始化,空容器,默认key升序
    map<int, string> m1;
    multimap<int, string> mm1;
    
    // 方式2:初始化时插入多个键值对(C++11及以上,初始化列表,最简洁)
    map<int, string> m2 = {{1, "张三"}, {3, "李四"}, {2, "王五"}, {1, "赵六"}}; 
    // 自动按key去重+排序,最终m2: {1:"张三", 2:"王五", 3:"李四"}(重复key=1被忽略)
    multimap<int, string> mm2 = {{1, "张三"}, {3, "李四"}, {2, "王五"}, {1, "赵六"}}; 
    // 允许重复key+按key排序,最终mm2: {1:"张三", 1:"赵六", 2:"王五", 3:"李四"}
    
    // 方式3:通过pair对象插入初始化(兼容所有C++版本)
    map<int, string> m3;
    m3.insert(pair<int, string>(4, "孙七"));
    m3.insert(make_pair(5, "周八")); // make_pair更简洁,推荐
    multimap<int, string> mm3;
    mm3.insert(make_pair(1, "吴九"));
    mm3.insert(make_pair(1, "郑十"));
    
    // 方式4:拷贝构造(从另一个map/multimap拷贝)
    map<int, string> m4(m2); // m4与m2完全一致:{1:"张三", 2:"王五", 3:"李四"}
    multimap<int, string> mm4(mm2); // mm4与mm2完全一致:{1:"张三", 1:"赵六", 2:"王五", 3:"李四"}
    
    // 方式5:指定排序规则初始化(示例:key降序,后续详解)
    map<int, string, greater<int>> m5 = {{1, "张三"}, {3, "李四"}, {2, "王五"}}; 
    // 降序+去重:{3:"李四", 2:"王五", 1:"张三"}
    multimap<int, string, greater<int>> mm5 = {{1, "张三"}, {3, "李四"}, {2, "王五"}, {1, "赵六"}}; 
    // 降序+重复:{3:"李四", 2:"王五", 1:"张三", 1:"赵六"}
    
    // 打印测试(后续讲遍历方式,此处暂用简单遍历)
    cout << "m2: ";
    for (auto it = m2.begin(); it != m2.end(); ++it) {
        // it是迭代器,解引用返回pair对象,first=key,second=value
        cout << "(" << it->first << "," << it->second << ") ";
    }
    cout << endl; // 输出:(1,张三) (2,王五) (3,李四)
    
    cout << "mm2: ";
    for (auto it = mm2.begin(); it != mm2.end(); ++it) {
        cout << "(" << it->first << "," << it->second << ") ";
    }
    cout << endl; // 输出:(1,张三) (1,赵六) (2,王五) (3,李四)
    
    return 0;
}

2. 核心操作(高频必学,区分map与multimap差异)

map与multimap的大部分操作完全一致,差异主要集中在"插入重复key""访问value""删除重复key"上,重点标注差异点,同时讲解map特有的[]运算符。

(1)插入元素:insert()(核心,体现键值对+key唯一性特性)

插入元素时,容器会自动按key排序并(map)去重,时间复杂度O(log n),支持插入单个键值对、多个键值对、区间键值对,插入的是pair类型对象。

  • map:插入重复key,返回"插入失败"的标识(pair类型,first是迭代器,指向当前key对应的pair;second是bool值,false表示插入失败);

  • multimap:插入重复key,直接成功,返回指向插入的pair对象的迭代器,无bool标识。

cpp 复制代码
map<int, string> m = {{1, "张三"}, {2, "王五"}};
multimap<int, string> mm = {{1, "张三"}, {2, "王五"}};

// 1. 插入单个键值对(两种方式:make_pair和pair构造)
// map插入重复key=1(已存在),插入失败
auto res1 = m.insert(make_pair(1, "赵六"));
cout << "map插入key=1:" << (res1.second ? "成功" : "失败") << endl; // 输出:失败
// map插入新key=3,插入成功
auto res2 = m.insert(pair<int, string>(3, "李四"));
cout << "map插入key=3:" << (res2.second ? "成功" : "失败") << endl; // 输出:成功,m变为{1:"张三", 2:"王五", 3:"李四"}

// multimap插入重复key=1(已存在),插入成功
auto it_mm = mm.insert(make_pair(1, "赵六"));
cout << "multimap插入key=1,插入位置键值对:(" << it_mm->first << "," << it_mm->second << ")" << endl; // 输出:(1,赵六)
// multimap插入新key=3,插入成功
mm.insert(make_pair(3, "李四")); // mm变为{1:"张三", 1:"赵六", 2:"王五", 3:"李四"}

// 2. 插入多个键值对(初始化列表)
m.insert({{4, "孙七"}, {5, "周八"}, {2, "吴九"}}); // 去重+按key排序,m变为{1:"张三", 2:"王五", 3:"李四", 4:"孙七", 5:"周八"}(key=2重复被忽略)
mm.insert({{4, "孙七"}, {5, "周八"}, {2, "吴九"}}); // 重复+按key排序,mm变为{1:"张三", 1:"赵六", 2:"王五", 2:"吴九", 3:"李四", 4:"孙七", 5:"周八"}

// 3. 插入区间键值对(从另一个map/multimap拷贝)
map<int, string> m_temp = {{6, "郑十"}, {7, "钱十一"}};
m.insert(m_temp.begin(), m_temp.end()); // m变为{1:"张三", 2:"王五", ..., 6:"郑十", 7:"钱十一"}
multimap<int, string> mm_temp = {{6, "郑十"}, {6, "钱十一"}};
mm.insert(mm_temp.begin(), mm_temp.end()); // mm变为{1:"张三", ..., 6:"郑十", 6:"钱十一"}
(2)map特有:[]运算符(快速插入/访问键值对,注意陷阱)

map支持[]运算符,可通过key快速访问或插入对应的value,这是map与multimap、set系列的核心区别之一(multimap、set均不支持[]运算符)。其底层逻辑是:

  • 若key已存在:返回该key对应的value的引用,可直接修改value;

  • 若key不存在:自动插入一个键值对(key为指定值,value为默认构造值,如string默认空串、int默认0),并返回value的引用。

cpp 复制代码
map<int, string> m = {{1, "张三"}, {2, "王五"}};

// 1. 访问已存在的key,返回value引用
cout << "m[1] = " << m[1] << endl; // 输出:m[1] = 张三
// 修改已存在key的value
m[1] = "赵六";
cout << "修改后m[1] = " << m[1] << endl; // 输出:修改后m[1] = 赵六,m变为{1:"赵六", 2:"王五"}

// 2. 访问不存在的key,自动插入键值对(key=3,value为空串)
cout << "m[3] = " << m[3] << endl; // 输出:m[3] = (空串),m变为{1:"赵六", 2:"王五", 3:""}
// 给自动插入的key赋值
m[3] = "李四";
cout << "赋值后m[3] = " << m[3] << endl; // 输出:赋值后m[3] = 李四

// 3. 直接用[]插入新键值对
m[4] = "孙七"; // 插入{4:"孙七"},m变为{1:"赵六", 2:"王五", 3:"李四", 4:"孙七"}

// 关键陷阱:不要用[]判断key是否存在!
// 原因:即使key不存在,[]也会自动插入该key(value默认构造),导致误插入
if (m[5] == "") { // 此时key=5被自动插入,m的size增加1
    cout << "key=5不存在" << endl;
}
cout << "m的size:" << m.size() << endl; // 输出:5(多了key=5)

// 正确判断key是否存在:用find()或count()(后续讲解)
(3)删除元素:erase()(3种常用方式,注意multiset删除重复key的细节)

删除元素时,时间复杂度O(log n),支持按迭代器删除、按key删除、按区间删除,map与multiset的差异在于"按key删除"时的行为(与set/multiset的差异逻辑一致)。

  • map:按key删除,最多删除1个元素(因为key唯一),返回删除的元素个数(0或1);

  • multimap:按key删除,会删除所有key等于该值的键值对,返回删除的元素个数。

cpp 复制代码
map<int, string> m = {{1, "张三"}, {2, "王五"}, {3, "李四"}, {4, "孙七"}};
multimap<int, string> mm = {{1, "张三"}, {1, "赵六"}, {2, "王五"}, {3, "李四"}, {3, "周八"}, {3, "吴九"}};

// 方式1:按迭代器删除(删除指定位置的键值对)
auto it_m = m.begin();
++it_m; // 指向key=2的键值对(2,"王五")
m.erase(it_m); // 删除key=2,m变为{1:"张三", 3:"李四", 4:"孙七"}

auto it_mm = mm.begin();
++it_mm; // 指向第一个key=1的键值对(1,"赵六")
mm.erase(it_mm); // 删除该键值对,mm变为{1:"张三", 2:"王五", 3:"李四", 3:"周八", 3:"吴九"}

// 方式2:按key删除(核心差异点)
int del_key = 3;
// map按key删除3,删除1个(key唯一)
int m_del_cnt = m.erase(del_key);
cout << "map删除key=" << del_key << ",删除个数:" << m_del_cnt << endl; // 输出:1,m变为{1:"张三", 4:"孙七"}

// multimap按key删除3,删除所有key=3的键值对(共3个)
int mm_del_cnt = mm.erase(del_key);
cout << "multimap删除key=" << del_key << ",删除个数:" << mm_del_cnt << endl; // 输出:3,mm变为{1:"张三", 2:"王五"}

// 方式3:按区间删除(左闭右开,删除[m.begin(), m.end()-1])
auto it1 = m.begin();
auto it2 = m.end();
--it2; // 指向key=4的键值对(4,"孙七")
m.erase(it1, it2); // 删除key=1,m变为{4:"孙七"}

auto it3 = mm.begin();
auto it4 = mm.begin();
++it4; // 指向key=2的键值对(2,"王五")
mm.erase(it3, it4); // 删除key=1,mm变为{2:"王五"}
(4)查找元素:find() + count()(高效查找,核心优势)

基于红黑树的查找,时间复杂度O(log n),远优于vector模拟键值对的O(n),核心是按key查找,用法与set系列类似,但返回值和解引用结果不同(返回pair对象)。

① find():查找key,返回迭代器
  • 查找成功:返回指向该key对应的键值对(pair)的迭代器;map中key唯一,仅返回一个迭代器;multimap中key可重复,返回第一个匹配key的迭代器;

  • 查找失败:返回容器的end()迭代器(不可解引用)。

② count():统计key出现的次数
  • map:返回0或1(key唯一),可用于判断key是否存在;

  • multimap:返回该key对应的键值对个数(>=0),可用于获取重复key的数量。

cpp 复制代码
map<int, string> m = {{1, "张三"}, {2, "王五"}, {3, "李四"}};
multimap<int, string> mm = {{1, "张三"}, {1, "赵六"}, {2, "王五"}, {3, "李四"}, {3, "周八"}};

// 1. find()查找
// map查找存在的key=2
auto it_m1 = m.find(2);
if (it_m1 != m.end()) {
    cout << "map找到key=2,键值对:(" << it_m1->first << "," << it_m1->second << ")" << endl; // 输出:(2,王五)
}

// multimap查找存在的key=1(返回第一个匹配的迭代器)
auto it_mm1 = mm.find(1);
if (it_mm1 != mm.end()) {
    cout << "multimap找到key=1,第一个键值对:(" << it_mm1->first << "," << it_mm1->second << ")" << endl; // 输出:(1,张三)
}

// 查找不存在的key=4
auto it_m2 = m.find(4);
if (it_m2 == m.end()) {
    cout << "map未找到key=4" << endl; // 输出:未找到
}

auto it_mm2 = mm.find(4);
if (it_mm2 == mm.end()) {
    cout << "multimap未找到key=4" << endl; // 输出:未找到
}

// 2. count()统计
cout << "map中key=1的个数:" << m.count(1) << endl; // 输出:1(map key唯一)
cout << "multimap中key=1的个数:" << mm.count(1) << endl; // 输出:2(两个key=1的键值对)
cout << "map中key=5的个数:" << m.count(5) << endl; // 输出:0(不存在)

// 正确判断key是否存在(推荐用find(),效率更高)
if (m.find(3) != m.end()) {
    cout << "map中存在key=3" << endl;
}
if (mm.count(3) > 0) {
    cout << "multimap中存在key=3" << endl;
}
(5)其他高频操作(map与multiset通用)
  • size():返回容器中键值对的个数,时间复杂度O(1);

  • empty():判断容器是否为空(size() == 0),返回bool值,高效推荐;

  • clear():清空容器中所有键值对,释放红黑树节点内存,size变为0;

  • swap():交换两个同类型容器的键值对,时间复杂度O(1)(仅交换红黑树的根节点,无需拷贝元素);

  • begin()/end():返回双向迭代器,指向容器的第一个/最后一个键值对(end()指向末尾后一个位置,不可解引用);

  • rbegin()/rend():返回反向迭代器,用于逆序遍历容器。

cpp 复制代码
map<int, string> m = {{1, "张三"}, {2, "王五"}};
multimap<int, string> mm = {{1, "张三"}, {1, "赵六"}};

cout << "m的大小:" << m.size() << endl; // 输出:2
cout << "mm是否为空:" << (mm.empty() ? "是" : "否") << endl; // 输出:否

// 交换容器
map<int, string> m_temp = {{3, "李四"}, {4, "孙七"}};
m.swap(m_temp);
cout << "交换后m的元素:";
for (auto val : m) { cout << "(" << val.first << "," << val.second << ") "; } // 输出:(3,李四) (4,孙七)

// 清空容器
mm.clear();
cout << "清空后mm的大小:" << mm.size() << endl; // 输出:0

// 反向遍历
cout << "m反向遍历:";
for (auto it = m.rbegin(); it != m.rend(); ++it) {
    cout << "(" << it->first << "," << it->second << ") "; // 输出:(4,孙七) (3,李四)
}
cout << endl;

3. 遍历方式(3种常用,适配双向迭代器,重点看pair访问)

map与multimap的迭代器是双向迭代器 ,支持++、--操作,不支持随机访问(无法用下标[ ]访问,除了map的[]运算符用于访问value),常用3种遍历方式,两者用法完全一致。核心是:迭代器解引用返回pair<const K, V>对象,通过->first访问key,->second访问value。

cpp 复制代码
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
    map<int, string> m = {{1, "张三"}, {2, "王五"}, {3, "李四"}};
    multimap<int, string> mm = {{1, "张三"}, {1, "赵六"}, {2, "王五"}, {3, "李四"}};
    
    // 方式1:迭代器遍历(最通用,推荐)
    // 普通迭代器(可读,可修改value,不可修改key)
    cout << "map迭代器遍历:";
    map<int, string>::iterator it_m;
    for (it_m = m.begin(); it_m != m.end(); ++it_m) {
        // 两种访问方式:it->first / (*it).first(推荐前者,更简洁)
        cout << "(" << it_m->first << "," << it_m->second << ") "; 
        // 修改value(合法)
        it_m->second += "_修改后";
    }
    cout << endl; // 输出:(1,张三) (2,王五) (3,李四)(修改后的值后续打印)
    
    // 常量迭代器(只读,更安全,不可修改value和key)
    cout << "multiset常量迭代器遍历:";
    multimap<int, string>::const_iterator cit_mm;
    for (cit_mm = mm.cbegin(); cit_mm != mm.cend(); ++cit_mm) {
        cout << "(" << cit_mm->first << "," << cit_mm->second << ") ";
        // cit_mm->second = "修改"; // 编译报错,常量迭代器不可修改value
    }
    cout << endl; // 输出:(1,张三) (1,赵六) (2,王五) (3,李四)
    
    // 打印修改后的map
    cout << "修改value后map:";
    for (auto it = m.begin(); it != m.end(); ++it) {
        cout << "(" << it->first << "," << it->second << ") "; // 输出:(1,张三_修改后) (2,王五_修改后) (3,李四_修改后)
    }
    cout << endl;
    
    // 方式2:范围for循环(C++11及以上,最简洁,推荐)
    cout << "map范围for遍历:";
    for (auto& val : m) { // 用引用可修改value,不用引用则只读
        cout << "(" << val.first << "," << val.second << ") ";
        val.second = "再次修改"; // 用引用修改value(合法)
    }
    cout << endl;
    
    // 方式3:反向迭代器(从尾部到头部遍历,逆序)
    cout << "multimap反向遍历:";
    multimap<int, string>::reverse_iterator rit_mm;
    for (rit_mm = mm.rbegin(); rit_mm != mm.rend(); ++rit_mm) {
        cout << "(" << rit_mm->first << "," << rit_mm->second << ") "; // 输出:(3,李四) (2,王五) (1,赵六) (1,张三)
    }
    cout << endl;
    
    // 错误示例1:不支持随机访问,无法用下标访问(除了map的[]访问value)
    // cout << m[0]->second << endl; // 编译报错
    // auto it = m.begin() + 2; // 编译报错,只能用++、--跳转
    
    // 错误示例2:修改key(非法)
    // it_m = m.begin();
    // it_m->first = 10; // 编译报错,key是const修饰的,不可修改
    
    return 0;
}

三、进阶用法:自定义排序与自定义key/value类型

map与multimap默认按"key升序"排序,value可以是任意基本类型或自定义类型。实际开发中,我们经常需要自定义key的排序规则(如降序、按自定义结构体key排序),或存储自定义类型的value,这也是键值对存储的核心进阶场景。

核心原则:排序规则仅针对key,与value无关;自定义key类型时,必须指定排序规则(否则编译器无法比较key大小)。

1. 自定义排序规则(3种常用方式,与set系列一致)

自定义排序的核心是:向map/multimap传递"排序函数"或"函数对象",告诉容器如何比较key的大小,维持有序性。

方式1:使用STL内置函数对象(最简单,适用于内置类型key)

STL提供了greater<K>(key降序)、less<K>(key升序,默认)两个函数对象,直接作为模板参数即可(K是key的类型)。

cpp 复制代码
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
    // 方式1:key降序排序(内置greater函数对象)
    map<int, string, greater<int>> m_desc = {{1, "张三"}, {3, "李四"}, {2, "王五"}}; 
    // 降序+去重:{3:"李四", 2:"王五", 1:"张三"}
    multimap<int, string, greater<int>> mm_desc = {{1, "张三"}, {3, "李四"}, {2, "王五"}, {1, "赵六"}}; 
    // 降序+重复:{3:"李四", 2:"王五", 1:"张三", 1:"赵六"}
    
    cout << "map key降序遍历:";
    for (auto val : m_desc) { cout << "(" << val.first << "," << val.second << ") "; } // 输出:(3,李四) (2,王五) (1,张三)
    cout << endl;
    
    cout << "multimap key降序遍历:";
    for (auto val : mm_desc) { cout << "(" << val.first << "," << val.second << ") "; } // 输出:(3,李四) (2,王五) (1,张三) (1,赵六)
    cout << endl;
    
    return 0;
}
方式2:自定义全局排序函数(适用于简单自定义规则,内置类型key)

定义一个返回bool值的全局函数,参数为两个key(K类型),返回"a是否应该排在b前面",作为模板参数传递给容器。

cpp 复制代码
#include <iostream>
#include <map>
#include <string>
using namespace std;

// 自定义排序规则:key按绝对值降序排序(key是int类型)
bool cmpKeyAbsDesc(int a, int b) {
    return abs(a) > abs(b); // 绝对值大的key排在前面
}

int main() {
    map<int, string, decltype(cmpKeyAbsDesc)*> m(cmpKeyAbsDesc); // 传递排序函数
    multimap<int, string, decltype(cmpKeyAbsDesc)*> mm(cmpKeyAbsDesc);
    
    m.insert({{-5, "张三"}, {3, "李四"}, {-2, "王五"}, {5, "赵六"}}); // 去重(key=-5和5绝对值相同,保留第一个)+ 按绝对值降序:{5:"赵六", -2:"王五", 3:"李四"}
    mm.insert({{-5, "张三"}, {3, "李四"}, {-2, "王五"}, {5, "赵六"}}); // 重复(key=-5和5绝对值相同,视为不同key)+ 按绝对值降序:{-5:"张三",5:"赵六",3:"李四",-2:"王五"}
    
    cout << "map key按绝对值降序:";
    for (auto val : m) { cout << "(" << val.first << "," << val.second << ") "; }
    cout << endl;
    
    cout << "multimap key按绝对值降序:";
    for (auto val : mm) { cout << "(" << val.first << "," << val.second << ") "; }
    cout << endl;
    
    return 0;
}
方式3:自定义函数对象(适用于复杂规则、自定义类型key)

定义一个结构体/类,重载()运算符(成为函数对象),在运算符中实现key的排序规则,适用于自定义类型key或复杂的排序逻辑。

cpp 复制代码
#include <iostream>
#include <map>
#include <string>
using namespace std;

// 自定义key类型:学生学号(结构体)
struct StudentID {
    int grade; // 年级
    int num;   // 学号(年级内唯一)
    
    // 构造函数(方便初始化)
    StudentID(int g, int n) : grade(g), num(n) {}
};

// 自定义函数对象:按key(StudentID)排序,先按年级升序,年级相同按学号升序
struct CmpStudentID {
    bool operator()(const StudentID& a, const StudentID& b) const {
        if (a.grade != b.grade) {
            return a.grade < b.grade; // 年级升序
        } else {
            return a.num < b.num; // 年级相同,学号升序
        }
    }
};

// 自定义value类型:学生信息(结构体)
struct StudentInfo {
    string name;
    int age;
    StudentInfo(string n, int a) : name(n), age(a) {}
};

int main() {
    // 存储自定义key(StudentID)和自定义value(StudentInfo),使用自定义函数对象排序
    map<StudentID, StudentInfo, CmpStudentID> m_stu;
    multimap<StudentID, StudentInfo, CmpStudentID> mm_stu;
    
    // 插入键值对(key=StudentID,value=StudentInfo)
    m_stu.insert(make_pair(StudentID(3, 101), StudentInfo("张三", 18)));
    m_stu.insert(make_pair(StudentID(2, 201), StudentInfo("李四", 17)));
    m_stu.insert(make_pair(StudentID(3, 102), StudentInfo("王五", 18)));
    m_stu.insert(make_pair(StudentID(3, 101), StudentInfo("赵六", 18))); // key重复(3年级101号),插入失败
    
    mm_stu.insert(make_pair(StudentID(3, 101), StudentInfo("张三", 18)));
    mm_stu.insert(make_pair(StudentID(3, 101), StudentInfo("赵六", 18))); // key重复,插入成功
    
    // 遍历输出
    cout << "map学生信息(按年级+学号升序):" << endl;
    for (const auto& val : m_stu) {
        cout << "学号:" << val.first.grade << "年级" << val.first.num << "号,姓名:" << val.second.name << ",年龄:" << val.second.age << endl;
    }
    
    cout << "\nmultimap学生信息:" << endl;
    for (const auto& val : mm_stu) {
        cout << "学号:" << val.first.grade << "年级" << val.first.num << "号,姓名:" << val.second.name << ",年龄:" << val.second.age << endl;
    }
    
    return 0;
}

注意:自定义key类型时,必须指定排序规则(函数对象或全局函数);自定义value类型时,无需指定排序规则(排序与value无关),但需确保value类型支持必要的构造、赋值操作。

四、适用场景:map 与 multimap 什么时候用?(精准匹配)

结合两者的键值对存储特性和key唯一性差异,以下场景优先选择map或multimap,替代vector模拟键值对的方式,提升代码效率和简洁度。同时对比set系列的适用场景,帮你快速区分。

1. map 的适用场景(key唯一+键值对+有序)

  • 一对一映射存储:需要通过唯一key快速查找对应value,且希望key有序,无需手动排序(如用户ID→用户信息、学号→学生成绩、字典(单词→释义));

  • 高效查找+修改:需要频繁根据key查找value,并修改value的值(如根据用户ID更新用户的登录状态、积分等);

  • 有序键值对统计:需要统计一组唯一key对应的value,并按key有序展示(如统计每个产品的销量,按产品ID有序排列)。

典型示例:用户信息管理(用户ID为唯一key,映射用户姓名、年龄等信息)、字典查询(单词为唯一key,映射释义)、产品销量统计(产品ID为key,映射销量)。

2. multimap 的适用场景(key可重复+键值对+有序)

  • 一对多映射存储:需要通过重复key获取多个对应的value,且希望按key有序排列(如班级ID→学生信息、部门ID→员工信息、关键词→相关文章);

  • 重复key分组:需要将键值对按key分组,同一个key的多个value放在一起,便于批量获取(如按年级分组展示学生信息、按部门分组展示员工信息);

  • 有序多值映射:需要一个有序的键值对队列,允许相同key对应多个value,且能高效查找、删除(如任务优先级→任务列表,相同优先级的任务对应多个)。

典型示例:班级学生管理(班级ID为key,映射该班级所有学生信息)、部门员工管理(部门ID为key,映射该部门所有员工信息)、关键词检索(关键词为key,映射所有相关文章标题)

相关推荐
SCLchuck1 小时前
std::function 在析构阶段触发非法内存访问
c++·lambda
云泽8081 小时前
从图形界面到跨平台王者:Qt 客户端开发全解析
开发语言·qt
散峰而望1 小时前
【算法竞赛】二叉树
开发语言·数据结构·c++·算法·深度优先·动态规划·宽度优先
持梦远方1 小时前
QML 与 C++ 后端交互学习笔记
c++·qt·学习·交互
REDcker1 小时前
从 SS7 到 VoLTE:核心信令协议栈与移动网络演进详解
开发语言·网络·sip·移动网络·volte·ss7·七号信令
王德印2 小时前
工作踩坑之导入数据库报错:Got a packet bigger than ‘max_allowed_packet‘ bytes
java·数据库·后端·mysql·云原生·运维开发
Never_Satisfied2 小时前
在c#中,缩放jpg文件的尺寸
算法·c#
那起舞的日子2 小时前
卡拉兹函数
java·算法
天若有情6732 小时前
我发明的 C++「数据注入模型(DWM)」:比构造函数更规范、更专业的结构体创建写法
开发语言·c++·rpc