C++ STL | map、multimap

目录

map

map的介绍

member_types

map的使用

模板参数说明

​编辑

构造函数

map的插入

insert()成员函数插入

[]运算符插入

[emplace() 成员函数插入](#emplace() 成员函数插入)

insert_or_assign()成员函数插入

map的查找

find()成员函数查找

[]运算符查找

count()成员函数查找

[lower_bound ()/upper_bound ()/equal_range ()成员函数查找](#lower_bound ()/upper_bound ()/equal_range ()成员函数查找)

map的删除

erase()成员函数

clear成员函数

map的迭代器

结构及其类型

特性

[迭代器有效性(删除 / 插入后)](#迭代器有效性(删除 / 插入后))

遍历map代码示例:

迭代器遍历过程中删除元素的坑

在c++11及以上的版本,借助erase的返回值来更新迭代器

在c++11以前的版本,需要提前保存好下一个迭代器

map的容量函数

multimap

multimap的介绍

multimap的使用

std::map与std::multimap的比较


map

map的介绍

cplusplus中关于map的文档:map - C++ Reference

翻译过来就是:

  • map是关联式容器,它按照特定的次序(按照key来比较)存储键值key和值value组成的元素,使用map的迭代器遍历map中的元素,可以得到有序序列。
  • 在map中,键值key通常用于排序和唯一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,并取别名为pair。
  • map容器中元素的键值key不能被修改,但是元素的值value可以被修改,因为map底层的二叉搜索树是根据每个元素的键值key进行构建的,而不是值value。
  • 在内部,map中的元素总是按照键值key进行比较排序的。当不传入内部比较对象时,map中元素的键值key默认按照小于来比较。
  • map容器通过键值key访问单个元素的速度通常比unordered_map容器慢,但map容器允许根据顺序对元素进行直接迭代。
  • map容器支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
  • map在底层是用平衡搜索树(红黑树)实现的,所以在map当中查找某个元素的时间复杂度为O( log₂N)

member_types


map的使用

头文件:<map>

模板参数说明

  • key: 键值对中key的类型
  • T: 键值对中value的类型
  • Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比
  • 较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)
  • Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器

构造函数

以下是常用构造函数类型及示例

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

int main() {
    // 构造函数1:默认构造(空map)
    map<int, string> map1;  // 空的map,键为int,值为string

    // 构造函数2:初始化列表构造(C++11及以上)
    map<int, string> map2 = {
        {1, "apple"},
        {2, "banana"},
        {3, "orange"}
    };

    // 构造函数3:拷贝构造(从已有map复制)
    map<int, string> map3(map2);  // map3 是 map2 的副本

    // 构造函数4:范围构造(从其他容器/迭代器范围初始化)
    vector<pair<int, string>> vec = {{4, "pear"}, {5, "grape"}};
    map<int, string> map4(vec.begin(), vec.end());  // 从vector的迭代器范围构造

    // 构造函数5:自定义比较规则的构造(默认是less<T>,即升序)
    // 示例:按键降序排列
    map<int, string, greater<int>> map5 = {{1, "a"}, {2, "b"}};

    // 验证输出
    cout << "map2: ";
    for (auto& p : map2) {
        cout << "{" << p.first << ":" << p.second << "} ";
    }
    cout << endl;

    cout << "map5(降序): ";
    for (auto& p : map5) {
        cout << "{" << p.first << ":" << p.second << "} ";
    }
    cout << endl;

    return 0;
}

map的插入

insert()成员函数插入
复制代码

insert的返回值是pair<iterator, bool>,通过bool可以判断是否插入成功

  • 若待插入元素的键值key在map当中不存在,则insert函数插入成功,并返回插入后元素的迭代器和true。
  • 若待插入元素的键值key在map当中已经存在,则insert函数插入失败,并返回map当中键值为key的元素的迭代器和false。

代码示例:

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

int main() {
    map<int, string> myMap;

    // 写法1:显式构造 pair 对象(C++98 风格)
    // 缺点:类型名重复书写,冗长
    myMap.insert(pair<int, string>(1, "Apple"));

    // 写法2:使用 make_pair 模板函数(C++98 推荐)
    // 优点:自动推导类型,代码简洁
    myMap.insert(make_pair(2, "Banana"));

    // 写法3:使用 C++11 初始化列表(现代 C++ 推荐)
    // 优点:最简洁,无需记忆函数名
    myMap.insert({3, "Orange"});

    // 判断插入结果
    // insert 返回值是一个 pair<iterator, bool>
    auto ret = myMap.insert({1, "Green Apple"}); 
    if (!ret.second) {
        cout << "插入失败,键 1 已存在,原值为:" << ret.first->second << endl;
    }

    return 0;
}
[]运算符插入

c++ stl map通过operator[]运算符重载,支持直接对象名[key] = value的方式直接插入或修改key对应的value

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

int main() {
    map<int, string> myMap;

    // 1. 插入新元素
    myMap[1] = "Apple"; 
    // 2. 覆盖已有元素
    myMap[1] = "Red Apple"; 

    // 读取不存在的键
    // cout << myMap[2]; 
    // 后果:map 会自动插入一个 {2, ""} 的空值元素,导致 map 意外扩容。

    return 0;
}
emplace() 成员函数插入

emplace()成员函数插入的方式仅支持c++11及以上版本,"就地构造",适用于追求极致性能,或者插入自定义类对象的场景

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

int main() {
    map<int, string> myMap;

    // 直接传入构造 pair 所需的参数,而不是构造好的 pair 对象
    // map 内部会直接在内存中构造这个对象,避免了临时对象的拷贝开销
    myMap.emplace(4, "Pear");

    // 返回值用法与 insert 完全一致
    auto ret = myMap.emplace(4, "Yellow Pear");
    if (!ret.second) {
        cout << "emplace 失败,键已存在" << endl;
    }

    return 0;
}
insert_or_assign()成员函数插入

c++官方网站对该特性的介绍:std::map<Key,T,Compare,Allocator>::insert_or_assign - cppreference.cn - C++参考手册

(ps:cplusplus不是c++官方运营的,但是在视觉布局上比官方网站好)

insert_or_assign成员函数插入的方式仅支持c++17及以上版本,支持覆盖与插入二合一

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

int main() {
    map<int, string> myMap;
    myMap[1] = "Apple";

    // 键存在则覆盖
    myMap.insert_or_assign(1, "Green Apple");
    // 键不存在则插入
    myMap.insert_or_assign(5, "Grape");

    return 0;
}

map的查找

find()成员函数查找

c++ stl map的find查找函数是根据所给key值在map当中进行查找,若找到了,则返回对应元素的迭代器,若未找到,则返回容器中最后一个元素下一个位置的正向迭代器。

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

int main() {
    map<int, string> fruitMap = {
        {1, "apple"},
        {2, "banana"},
        {3, "orange"}
    };

    // 1. 基础用法:查找指定键
    int targetKey = 2;
    auto it = fruitMap.find(targetKey);

    // 2. 判断是否找到
    if (it != fruitMap.end()) {
        cout << "找到键 " << targetKey << ",对应值:" << it->second << endl;
    } else {
        cout << "未找到键 " << targetKey << endl;
    }

    // 3. 查找不存在的键
    it = fruitMap.find(4);
    if (it == fruitMap.end()) {
        cout << "键 4 不存在" << endl;
    }

    return 0;
}
[]运算符查找

c++ stl map 对[] 运算符进行了重载,既可用于插入 / 赋值,也可用于查找,但存在严重的 "隐式插入" 问题,仅适合确定键存在的场景

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

int main() {
    map<int, string> fruitMap = {{1, "apple"}, {2, "banana"}};

    // 1. 键存在:正常获取值
    cout << "键 1 的值:" << fruitMap[1] << endl;

    // 2. 键不存在:隐式插入空值元素)
    cout << "键 3 的值:" << fruitMap[3] << endl; // 输出空字符串
    // 此时 fruitMap 已新增 {3, ""},map 大小从 2 变为 3
    cout << "map 大小:" << fruitMap.size() << endl; // 输出 3

    return 0;
}
count()成员函数查找

c++ stl map的成员函数count() 用于判断指定键是否存在,返回值为 size_type(通常是 size_t),由于 map 键唯一,返回值只能是 0(不存在)或 1(存在)。

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

int main() {
    map<int, string> fruitMap = {{1, "apple"}, {2, "banana"}};

    // 1. 判断键是否存在
    int key1 = 2;
    if (fruitMap.count(key1)) {
        cout << "键 " << key1 << " 存在" << endl;
    } else {
        cout << "键 " << key1 << " 不存在" << endl;
    }

    // 2. 查找不存在的键
    int key2 = 4;
    if (fruitMap.count(key2) == 0) {
        cout << "键 " << key2 << " 不存在" << endl;
    }

    return 0;
}
lower_bound ()/upper_bound ()/equal_range ()成员函数查找
  • lower_bound(key) :返回指向第一个大于等于 key 的元素的迭代器;
  • upper_bound(key) :返回指向第一个大于 key 的元素的迭代器;
  • equal_range(key):返回一个 pair,包含 lower_bound 和 upper_bound 的结果,等价于 "键等于 key 的元素范围"(map 中最多一个);

这三个方法适用于有序遍历或范围查找场景,返回键的边界迭代器

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

int main() {
    map<int, string> fruitMap = {{1, "apple"}, {2, "banana"}, {3, "orange"}, {5, "pear"}};

    // 1. lower_bound(key):返回 >= key 的第一个元素的迭代器
    auto itLower = fruitMap.lower_bound(3);
    cout << "lower_bound(3):键=" << itLower->first << ",值=" << itLower->second << endl; // 3, orange

    // 2. upper_bound(key):返回 > key 的第一个元素的迭代器
    auto itUpper = fruitMap.upper_bound(3);
    cout << "upper_bound(3):键=" << itUpper->first << ",值=" << itUpper->second << endl; // 5, pear

    // 3. equal_range(key):返回 pair<lower_bound, upper_bound>
    auto range = fruitMap.equal_range(3);
    cout << "equal_range(3) 左边界:" << range.first->first << endl;  // 3
    cout << "equal_range(3) 右边界:" << range.second->first << endl; // 5

    // 4. 查找不存在的键(返回插入位置)
    itLower = fruitMap.lower_bound(4);
    cout << "lower_bound(4):键=" << itLower->first << endl; // 5(4 应插入在 3 和 5 之间)

    return 0;
}

map的删除

erase()成员函数

通过迭代器指向待删除元素,直接移除该元素

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

int main() {
    map<int, string> fruitMap = {
        {1, "apple"}, {2, "banana"}, {3, "orange"}, {4, "pear"}
    };

    // 步骤1:先查找迭代器(避免删除不存在的元素)
    auto it = fruitMap.find(2);
    if (it != fruitMap.end()) {
        // 步骤2:通过迭代器删除
        fruitMap.erase(it);
        cout << "删除键 2 成功" << endl;
    } else {
        cout << "键 2 不存在,删除失败" << endl;
    }

    // 遍历验证结果
    cout << "\n删除后的 map:" << endl;
    for (const auto& p : fruitMap) {
        cout << p.first << ":" << p.second << endl;
    }

    return 0;
}

通过指定待删除元素key删除元素

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

int main() {
    map<int, string> fruitMap = {{1, "apple"}, {2, "banana"}, {3, "orange"}};

    // 方式1:直接删除键
    size_t deletedCount = fruitMap.erase(3); // 返回删除的元素个数(map 中只能是 0 或 1)
    if (deletedCount > 0) {
        cout << "删除键 3 成功" << endl;
    } else {
        cout << "键 3 不存在" << endl;
    }

    // 方式2:删除不存在的键(返回 0)
    deletedCount = fruitMap.erase(4);
    cout << "删除键 4 的返回值:" << deletedCount << endl;

    return 0;
}

通过指定待删除元素迭代器范围删除元素

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

int main() {
    map<int, string> fruitMap = {
        {1, "apple"}, {2, "banana"}, {3, "orange"}, {4, "pear"}, {5, "grape"}
    };

    // 步骤1:确定删除范围(删除键 2 ~ 4 的元素,左闭右开)
    auto itStart = fruitMap.find(2);
    auto itEnd = fruitMap.find(5); // 不包含 5

    // 步骤2:批量删除
    if (itStart != fruitMap.end() && itEnd != fruitMap.end()) {
        fruitMap.erase(itStart, itEnd);
        cout << "批量删除 2~4 的元素成功" << endl;
    }

    // 遍历验证
    cout << "\n批量删除后的 map:" << endl;
    for (const auto& p : fruitMap) {
        cout << p.first << ":" << p.second << endl;
    }

    return 0;
}

需要注意的是:

erase(iterator)和erase(key)的返回值,在c++11之前为void,在c++11及以上版本为iterator,指向被删除元素下一个元素的迭代器,可用于遍历中删除。

erase(key)的返回值,在c++11之前为void,在c++11及以上版本为size_t,返回删除元素的个数,在map仅为0/1,在multimap中可能大于1

clear成员函数

通过成员函数clear清空map中所有元素,使容器变为空,但不会释放map本身占用的内存,如果需要释放map本身占用的内存,可通过shrink_to_fit()释放

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

int main() {
    map<int, string> fruitMap = {{1, "apple"}, {2, "banana"}, {3, "orange"}};

    cout << "清空前 map 大小:" << fruitMap.size() << endl; // 3

    // 清空所有元素
    fruitMap.clear();

    cout << "清空后 map 大小:" << fruitMap.size() << endl; // 0
    cout << "map 是否为空:" << boolalpha << fruitMap.empty() << endl; // true

    // 可选:释放多余内存(C++11+)
    fruitMap.shrink_to_fit();

    return 0;
}

map的迭代器

结构及其类型

map 的迭代器本质是指向红黑树节点的指针封装 ,每个迭代器指向的是 std::pair<const Key, T> 类型的键值对

  • 键(first)是 const 类型:不允许通过迭代器修改键(保证 map 有序性);
  • 值(second)是普通类型:可通过迭代器修改值。

map 提供 4 种迭代器类型,适配不同读写场景:

迭代器类型 关键字 读写权限 适用场景
普通迭代器 iterator 可读可写 修改值、遍历中删除元素
常量迭代器 const_iterator 只读 只读遍历(如 const map)
反向迭代器 reverse_iterator 反向读写 从后往前遍历
常量反向迭代器 const_reverse_iterator 反向只读 const map 反向遍历
特性
  • 双向遍历,不支持随机访问,仅支持++it/it++向后移动,--it/it--向前移动
  • ❌ 不支持 it + n/it - n(随机访问)、it[] 等操作(区别于 vector 迭代器)
迭代器有效性(删除 / 插入后)
  • 插入元素 :除 end() 外,所有原有迭代器仍有效;
  • 删除元素:仅被删除元素的迭代器失效,其余迭代器仍有效;
  • 清空 / 批量删除:被删除范围内的迭代器失效,其余仍有效。

需要注意的是:

end() 是尾后迭代器,不指向任何元素,解引用 end() 会触发未定义行为

cpp 复制代码
// ❌ 错误
auto it = fruitMap.end();
cout << it->first << endl; // 崩溃

// ✅ 正确:先判断
if (it != fruitMap.end()) {
    cout << it->first << endl;
}
遍历map代码示例:
cpp 复制代码
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
    map<int, string> fruitMap = {{1, "apple"}, {2, "banana"}, {3, "orange"}};

    //正向遍历

    // 方式1:普通 for 循环(新手友好)
    cout << "正向遍历(普通迭代器):" << endl;
    for (map<int, string>::iterator it = fruitMap.begin(); it != fruitMap.end(); ++it) {
        cout << "键:" << it->first << ",值:" << it->second << endl;
    }

    // 方式2:C++11 范围 for 循环(推荐,自动用 iterator)
    cout << "\n范围 for 遍历:" << endl;
    for (auto& pair : fruitMap) { // 用 & 避免拷贝,可修改值
        pair.second += "_new"; // 修改值
        cout << "键:" << pair.first << ",值:" << pair.second << endl;
    }
/////////////////////////////////////////////////////////    

    // 反向遍历
    cout << "\n反向遍历:" << endl;
    for (map<int, string>::reverse_iterator rit = fruitMap.rbegin(); rit != fruitMap.rend(); ++rit) {
        // 注意:反向迭代器的 ++ 等价于普通迭代器的 --
        cout << "键:" << rit->first << ",值:" << rit->second << endl;
    }
    return 0;
}


正向遍历(普通迭代器):
键:1,值:apple
键:2,值:banana
键:3,值:orange

范围 for 遍历:
键:1,值:apple_new
键:2,值:banana_new
键:3,值:orange_new

反向遍历:
键:3,值:orange_new
键:2,值:banana_new
键:1,值:apple_new
迭代器遍历过程中删除元素的坑

遍历中删除元素是迭代器最易出错的场景,需遵循 "先获取下一个迭代器,再删除" 的原则:

在c++11及以上的版本,借助erase的返回值来更新迭代器
cpp 复制代码
#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
    map<int, string> fruitMap = {{1, "apple"}, {2, "banana"}, {3, "orange"}, {4, "pear"}};

    // 遍历删除键为偶数的元素
    for (auto it = fruitMap.begin(); it != fruitMap.end();) { // 注意:无 ++it
        if (it->first % 2 == 0) {
            // erase 返回下一个有效迭代器,直接赋值
            it = fruitMap.erase(it);
        } else {
            ++it;
        }
    }

    // 验证结果
    cout << "删除偶数键后的 map:" << endl;
    for (auto& pair : fruitMap) {
        cout << pair.first << ":" << pair.second << endl;
    }

    return 0;
}
在c++11以前的版本,需要提前保存好下一个迭代器
cpp 复制代码
// 遍历删除逻辑替换为:
for (auto it = fruitMap.begin(); it != fruitMap.end();) {
    if (it->first % 2 == 0) {
        auto nextIt = ++it; // 先记录下一个迭代器
        fruitMap.erase(--it); // 删除当前迭代器
        it = nextIt; // 更新迭代器
    } else {
        ++it;
    }
}

map的容量函数

empty():返回一个布尔值,表示 std::map 是否为空。如果为空,则返回 true;否则,返回 false

cpp 复制代码
std::map<Key, Value> myMap;
if (myMap.empty()) {
    // map为空
}

size():返回 std::map 容器中键值对的数量。

cpp 复制代码
std::map<Key, Value> myMap;
std::size_t count = myMap.size();

multimap

multimap的介绍

cplusplus中关于multimap的介绍:multimap - C++ Reference

  • Multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key, value>,其中多个键值对之间的key是可以重复的。
  • 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内 容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起, value_type是组合key和value的键值对: typedef pair<const Key, T> value_type;
  • 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对 key进行排序的。
  • multimap通过key访问单个元素的速度通常比unordered_multimap容器慢,但是使用迭代 器直接遍历multimap中的元素可以得到关于key有序的序列。
  • multimap在底层用二叉搜索树(红黑树)来实现。

注意: multimap map 的唯一不同就是: map 中的 key 是唯一的,而 multimap key 是可以
重复的


multimap的使用

multimap 中的接口可以参考 map ,功能都是类似的。
注意:

  • multimap中的key是可以重复的。
  • multimap中的元素默认将key按照小于来比较
  • multimap中没有重载operator[]操作。
  • 使用时与map包含的头文件相同

为什么muiltimap没有重载operator[]?

multimap 的核心特性是键可以重复 ,这就导致了 operator[] 的语义完全无法适配:

  • 当键存在时,可能有多个值与之关联,multimap[key] 无法确定要返回哪一个值的引用。
  • 当键不存在时,operator[] 隐式插入的行为也违背了 multimap 作为 "多值映射" 的设计初衷(用户可能只是想查询,而非插入)。

std::map与std::multimap的比较

相同点:

  • 存储键值对:std::map 和 std::multimap 都用于存储键值对,并提供了对键值对的有序存储和访问能力。
  • 基于红黑树:std::map 和 std::multimap 在内部实现上都使用了红黑树(Red-Black Tree),以保持元素的有序性。
  • 迭代器支持:它们都提供了迭代器的支持,可以使用迭代器进行遍历、访问和修改容器中的元素。

不同点:

  • 唯一性:最主要的区别在于键的唯一性。std::map 中的键是唯一的,每个键只能对应一个值,如果插入具有相同键的元素,则会替换原有键对应的值。而 std::multimap 允许多个元素具有相同的键,因此可以在 std::multimap 中存储重复的键值对。
  • 插入和查找:由于 std::map 中的键是唯一的,插入新元素时要进行键的比较和查找,以保持键的唯一性。这会导致插入和查找的时间复杂度是对数级别的。而 std::multimap 允许重复的键,因此插入新元素时只需按照键的顺序插入即可,插入的时间复杂度为常数级别。
  • 迭代器范围:对于 std::map,每个键只有一个对应的值,因此迭代器范围是唯一的。而对于 std::multimap,由于允许重复的键,因此迭代器范围可以包含多个具有相同键的元素。
相关推荐
ytttr8732 小时前
地震数据频率波数域变换与去噪的MATLAB实现
开发语言·matlab
极客小云2 小时前
【基于 PyQt6 的红外与可见光图像配准工具开发实战】
c语言·python·yolo·目标检测
Sarvartha2 小时前
二分查找学习笔记
数据结构·c++·算法
墨瑾轩2 小时前
C# PictureBox:5个技巧,从“普通控件“到“图像大师“的蜕变!
开发语言·c#·swift
墨瑾轩2 小时前
WinForm PictureBox控件:3个让图片“活“起来的骚操作,90%的开发者都踩过坑!
开发语言·c#
daidaidaiyu2 小时前
一文入门 Android NDK 开发
c++
Ethernet_Comm2 小时前
从 C 转向 C++ 的过程
c语言·开发语言·c++
难得的我们2 小时前
C++与区块链智能合约
开发语言·c++·算法
jllllyuz2 小时前
基于MATLAB的D2D通信模式选择仿真
开发语言·网络·matlab