一、引言
在 C++ 编程里,map 和 set 是两个特别好用的 "智能工具箱",专门帮我们存数据、找数据、管数据 ,比普通数组、列表方便太多,而且自带两个超实用的 "隐藏技能":自动排序 + 自动去重。
可以把它们简单理解成两种不同的 "记录本":
**Set 就是一个 "独一无二的有序名单"**你往里面放数字、字符串都行,它会自动帮你做两件事:
- 绝不重复:同样的东西放两次,它只留一个;
- 自动排好序:放进去是乱的,拿出来自动按大小 / 先后排整齐。它只存一个值,适合用来做 "去重、查重、有序列表"。
Map 就是一个 "带名字的有序字典" 它存的是一对一对的数据(名字 + 内容),比如 "学号→姓名""单词→解释":
- 名字(key)不能重复,还会自动排序;
- 想查数据,直接报 "名字",瞬间就能找到对应的内容。它存键 + 值,适合用来做 "映射、查找、对应关系"。
两个工具底层都用了高效的树形结构,找数据特别快,数据再多也不卡顿,是写程序时处理数据最常用、最省心的两个容器。
两个容器的底层都是红黑树,是优化版本的高效的二叉搜索树,关于二叉搜索树的内容在这里:🔜二叉搜索树
二、set的相关接口
头文件<set>,性质:只能存储key值不同的元素,并按照指定顺序排列成树。

一、构造和遍历:
1.构造:
构造分为三种:
首先介绍一下set的模板参数:
1.存储数据类型
2.定义内部数据存储顺序的比较仿函数(默认是排升序,即每个节点"左小右大")
3.空间配置器(一般不用传参,使用缺省值)
其次是构造函数的使用:
1.空构造
例如:set<int> a;
2.c数组风格构造
例如:set<int> arr={1,2,3,4};
3.迭代器区间构造
将已有容器或数组中的元素赋值到set中:传入参数是(容器第一个元素迭代器或数组首元素地址,容器最后一个元素下一个位置的迭代器或数组末尾元素下一个元素的地址)
4.拷贝构造
利用已有的set对象来构造一个新的set对象
2.迭代器和遍历:
使用迭代器++方式的遍历默认走的是中序遍历,一定是有序的。
set的迭代器遍历只支持读,不支持改。
3.代码演示:
cpp
//构造升序排列的set
//c数组风格构造
cout << "------------set1----------------" << endl;
set<int> set1 = { 1,2,3,4,5,6,7,8,9,10 };
set<int>::iterator it1 = set1.begin();
while (it1 != set1.end())
{
cout << *it1 << " ";
it1++;
}
cout << endl;//1 2 3 4 5 6 7 8 9 10
//迭代器区间构造
cout << "-----------------set2--------------" << endl;
vector<int> arr = { 1,2,3,4,5,6,7,8,9,10 };
set<int> set2(arr.begin(), arr.end());
set<int>::iterator it2 = set2.begin();
while (it2 != set2.end())
{
cout << *it2 << " ";
it2++;
}
cout << endl;//1 2 3 4 5 6 7 8 9 10
//构造降序排列的set
cout << "------------set3----------------" << endl;
set<int, greater<int> >set3 = { 1,2,3,4,5,6,7,8,9,10 };
set<int, greater<int>>::iterator it3 = set3.begin();
while (it3 != set3.end())
{
cout << *it3 << " ";
it3++;
}
cout << endl;//10 9 8 7 6 5 4 3 2 1
//迭代器区间构造
cout << "-----------------set4--------------" << endl;
vector<int>arr2 = { 1,2,3,4,5,6,7,8,9,10 };
set<int, greater<int>> set4(arr2.begin(), arr2.end());
set<int, greater<int>>::iterator it4 = set4.begin(); // 类型匹配
while (it4 != set4.end())
{
cout << *it4 << " ";
it4++;
}
cout << endl;//10 9 8 7 6 5 4 3 2 1
//拷贝构造
cout << "---------------set5----------------" << endl;
set<int> set5(set1);
set<int, greater<int>>::iterator it5 = set5.begin(); // 类型匹配
while (it5 != set5.end())
{
cout << *it5 << " ";
it5++;
}
cout << endl;//1 2 3 4 5 6 7 8 9 10
二、增删查:
插入insert
1.插入一个值 返回值是pair类型:
pair第二个参数是一个布尔值,取决于插入的成功或失败,由于set只允许存储不同信息的元素,所以插入失败意味着set中已有该元素,返回该已有元素的迭代器作为pair第一个参数,插入成功就返回新插入节点的迭代器。
2.指定迭代器位置的附近插入一个元素,可以认为传参的迭代器是你给编译器的一个提示,编译器会根据二叉搜索树的结构插入元素并返回新插入节点的迭代器。
3.插入一个迭代器区间指示的元素:相当于将该区间涵盖的元素排序加去重存储到set中
代码示例:
cpp
set<int> a;
//插入一个值
a.insert(3);
auto it = a.begin();
while (it != a.end())
{
cout << *it << " ";
it++;
}
cout << endl;//3
//指定迭代器位置的附近插入一个元素
a.insert(it, 4);
it = a.begin();
while (it != a.end())
{
cout << *it << " ";
it++;
}
cout << endl;//3 4
//插入一段迭代器区间指示的元素
vector <int> v1 = { 1,2,3,4,5 };
a.insert(v1.begin(), v1.end());
it = a.begin();
while (it != a.end())
{
cout << *it << " ";
it++;
}
cout << endl;//1 2 3 4 5
查找和计数
find:
给定值返回相应的迭代器位置,如果不存在就返回set的末尾元素的下一个位置的迭代器
count:
给定值判断这个值是否在set中,存在返回1,反之返回0;
lower_bound :传入一个值,返回中序遍历情况下第一个满足>=这个值节点的迭代器
upper_bound :传入一个值,返回中序遍历情况下第一个满足> 这个值节点的迭代器
equal_range:传入值,返回两个参数都是指示这个值的迭代器的pair(意义不大,主要是为了适配multiset)
删除erase
1.按迭代器位置删除,搭配find使用
2.按值删除,返回删除元素的个数,1个表示删除成功,0表示set中无该元素
3.删除一段区间,通常搭配 lower_bound,upper_bound 使用
代码示例:
cpp
set<int> s = { 1,2,3,4,5,6,7,23,44,78 };
for (int i = 1; i <20 ; i *= 2)
{
auto it=s.find(i);
if (it == s.end())
cout << i << "不存在" << endl;
else
cout << i << "存在" << endl;
}
cout << "-------------------------" << endl;
/*1存在
2存在
4存在
8不存在
16不存在*/
//按值删除
for (int i = 1; i < 20; i *= 2)
{
auto it = s.erase(i);
if (it == 0)
cout << i << "不存在" << endl;
else
cout << i << "删除成功" << endl;
}
/* 1删除成功
2删除成功
4删除成功
8不存在
16不存在
*/
//迭代器区间插入
s.insert({ 1,2,4 });//s:1,2,3,4,5,6,7,23,44,78
auto start = s.lower_bound(1);//找>=1的第一个位置
auto finish = s.upper_bound(7);//找>1的第一个位置
s.erase(start, finish);//按区间删除1-7,传参区间左闭右开。
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;//23 44 78
//按迭代器删除
s.insert({ 1,2,3,4,5,6,7 });//s:1,2,3,4,5,6,7,23,44,78
for (int i = 1; i < 20; i *= 2)
{
auto it = s.find(i);
if (it == s.end())
{
;
}
else
s.erase(it);
}
it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;// 3 5 6 7 23 44 78
//看1是否在set中
cout << s.count(1) << endl;//0
三、multiset相关接口
multiset是set的一对孪生兄弟,和set的区别就是它可以存储不同key值的元素
所以大部分接口都和set相同,这里介绍二者有不同之处的接口:
1.构造和区间插入
multiset的构造和区间插入不会删除重复值。
2.count
与set的count相比,当所给定的key值在multiset中时,multiset的count返回的是这个key值相同的所有元素的个数,不存在具有这个key值的元素时,返回值还是0/
3.find
与set相比,multiset之中find返回的所有迭代器都是中序遍历第一个出现那个节点所对应的迭代器,
4.erase
multiset的按值删除会删除所有key值为这个值的节点
5.equal_range 返回值为key的这段元素的迭代器区间(左闭右开)构成的pair
multiset和set的差异示意代码:
cpp
#include <iostream>
#include <set>
using namespace std;
// 辅助打印函数
template<typename T>
void print(const T& container) {
for (auto& val : container) {
cout << val << " ";
}
cout << endl;
}
int main() {
//==================== set:不允许重复 ====================
set<int> s;
// 1. 插入:重复元素会失败
cout << "=== set 测试 ===" << endl;
auto ret1 = s.insert(10); // 成功
auto ret2 = s.insert(20); // 成功
auto ret3 = s.insert(10); // 重复,失败
// insert 返回 pair<迭代器, 是否成功>
cout << "插入10是否成功:" << boolalpha << ret3.second << endl;
cout << "set 内容:";
print(s); // 自动排序:10 20
// 2. find:返回唯一元素的迭代器
auto it = s.find(10);
if (it != s.end())
cout << "找到了:" << *it << endl;
// 3. count:只能是 0 或 1
cout << "count(10) = " << s.count(10) << endl;
// 4. erase(值):只删一个
s.erase(10);
cout << "删除10后:";
print(s);
cout << endl;
//==================== multiset:允许重复 ====================
multiset<int> ms;
cout << "=== multiset 测试 ===" << endl;
ms.insert(10);
ms.insert(20);
ms.insert(10); // 重复也能插入成功
ms.insert(10);
cout << "multiset 内容:";
print(ms); // 自动排序,保留重复:10 10 10 20
// 1. find:返回第一个匹配元素
auto pos = ms.find(10);
if (pos != ms.end())
cout << "第一个10:" << *pos << endl;
// 遍历所有相同元素
cout << "所有10:";
while (pos != ms.end() && *pos == 10) {
cout << *pos << " ";
++pos;
}
cout << endl;
// 2. count:返回实际个数(可大于1)
cout << "count(10) = " << ms.count(10) << endl;
// 3. erase(值):删除所有等于该值的元素
ms.erase(10);
cout << "erase(10) 后:";
print(ms);
// 4. 只想删除一个:用迭代器删除
ms.insert(10);
ms.insert(10);
cout << "重新插入两个10:";
print(ms);
auto one = ms.find(10);
if (one != ms.end()) {
ms.erase(one); // 只删这一个
}
cout << "只删除一个10后:";
print(ms);
//==================== 核心区别总结====================
/*
set 与 multiset 使用时的核心区别:
1. 唯一性:
set 不允许重复元素,插入重复会失败;
multiset 允许重复元素,插入总是成功。
2. insert 返回值:
set 返回 pair<迭代器, bool>,可判断是否插入成功;
multiset 只返回迭代器,无需判断成功与否。
3. find:
set 找到唯一元素;
multiset 找到第一个相同元素,后续要自己遍历。
4. count:
set 结果只能是 0 或 1;
multiset 返回元素实际个数(≥0)。
5. erase(值):
set 删除这一个值(最多1个);
multiset 删除所有等于该值的元素。
共同点:
都自动升序排序;底层都是红黑树;迭代器用法一致。
*/
return 0;
}
四、map相关接口
头文件:<map>
map的性质:存放的是键值对,有两个数据key,val,key不可以重复,map的每一个元素是一个pair<key,val>

一,map的构造和遍历
map的构造:
1.默认构造,不手动调用。
2.迭代器区间构造,根据已有的存储数据类型为pair<key,val>的容器或结构体数组构造,传入指示容器首位的两个迭代器或指针,注意是左闭右开。
3.拷贝构造,使用已有的map进行构造
4.补充:c结构体风格构造。
和结构体数组的定义方式类似.,具体操作见代码演示.
map的遍历:
和set一样,map只可以使用迭代器进行中序遍历,遍历的结果对key而言是有序的,但是值得注意的是,由于pair<key,val>没有重载operator<< >>所以不能对map的单个节点直接进行输入输出,由于pair中key,val是公有的,并且不能修改key,所以输出要用pair指针->first/second pair对象.first/second
代码演示:
cpp
//迭代器区间构造
pair<string, int> p[] = { {"apple",1},{"peach",4},{"orange",6} };
map<string, int> map1(p, p + 3);
for (auto e : map1)
{
cout << e.first << ":" << e.second << endl;
}
//apple:1
//orange : 6
//peach : 4
//c结构体风格构造
map<string, int> map2= { {"apple",1},{"peach",4},{"orange",6} };
auto it = map2.begin();
while (it != map2.end())
{
cout << it->first << ":" << it->second << endl;
it++;
}
//apple:1
//orange : 6
//peach : 4
//拷贝构造
map<string, int> map3(map2);
for (auto e : map3)
{
cout << e.first << ":" << e.second << endl;
}
//apple:1
//orange : 6
//peach : 4
二,map的增删查
插入insert
使用方式和set大同小异,只不过把key值换成了pair<key,val>类型的对象。
1.插入一个pair<key,val>类型的对象,返回值是pair类型:
pair第二个参数是一个布尔值,取决于插入的成功或失败,由于set只允许存储不同信息的元素,所以插入失败意味着set中已有该元素,返回该已有元素的迭代器作为pair第一个参数,插入成功就返回新插入节点的迭代器。
2.指定迭代器位置的附近插入一个pair<key,val>类型的对象,可以认为传参的迭代器是你给编译器的一个提示,编译器会根据二叉搜索树的结构插入元素并返回新插入节点的迭代器。
3.插入一个迭代器区间指示的元素(pair<key,val>类型的对象):相当于将该区间涵盖的元素排序加去重存储到set中。
代码演示:
cpp
//插入一个pair<key,val>类型的对象
map<string, int> index;
index.insert({ "apple",1 });
pair<string, int> p1 = { "peach",6 };
index.insert(p1);
index.insert(make_pair("orange", 3));
for (auto it : index)
{
cout << it.first << ": " << it.second << endl;
}
//apple: 1
//orange : 3
//peach : 6
//指定迭代器位置的附近插入一个pair<key,val>类型的对象
index.insert(index.begin(), { "banana",4 });
for (auto it : index)
{
cout << it.first << ": " << it.second << endl;
}
//apple: 1
//banana: 4
//orange : 3
//peach : 6
index.clear();//容器清空
index.insert({ {"apple",1},{"peach",4},{"orange",6} });
for (auto it : index)
{
cout << it.first << ": " << it.second << endl;
};
//apple: 1
//orange : 6
//peach : 4
pair<string, int> p[] = { {"pineapple",1},{"watermelon",4},{"grape",6} };
index.insert(p, p + 3);
for (auto &it : index)
{
cout << it.first << ": " << it.second << endl;
};
//apple: 1
//grape : 6
//orange : 6
//peach : 4
//pineapple : 1
//watermelon : 4
查找和计数
find
给定key值返回相应的迭代器位置,如果不存在就返回set的末尾元素的下一个位置的迭代器
count:
给定值判断这个值是否在set中,存在返回1,反之返回0;
lower_bound :传入一个值,返回中序遍历情况下第一个满足>=这个值节点的迭代器
upper_bound :传入一个值,返回中序遍历情况下第一个满足> 这个值节点的迭代器
equal_range:传入值,返回两个参数都是指示这个值的迭代器的pair(意义不大,主要是为了适配multimap)
删除erase
和set几乎一模一样:
1.按迭代器位置删除,搭配find使用
2.按值删除,返回删除元素的个数,1个表示删除成功,0表示set中无该元素
3.删除一段区间,通常搭配 lower_bound,upper_bound 使用
由于和set操作别无二异,因此不再做代码展示,触类旁通即可
三、operator
在pair<key,val>中key充当索引关键字的作用,在map中存储具有唯一性,可以根据key来查找val,就好比数组的下标一样。
所以实现【】的重载,可以和数组的【】作类比,得出它在map中的使用原理:
1.map中无key,调用
插入一个val为val的默认构造值的元素节点,返回值pair中第一个元素为当前元素的迭代器以方便实现复制和修改
2.map中有key,还是调用
返回值pair中第一个元素为当前元素的迭代器以方便实现复制和修改。
代码示例:
cpp
map<string, int> index;
index.insert({ "apple",5 });
index["apple"] = 13;
index["peach"] = 23;
index["banana"] = 15;
for (auto e : index)
{
cout << e.first << ":" << e.second << endl;
}
//apple:13
//banana : 15
//peach : 23
五、multimap相关接口
multimap是map的一对孪生兄弟,和map的区别就是它可以存储不同key值的元素
所以大部分接口都和map相同,这里介绍二者有不同之处的接口:
1.构造和区间插入
multiset的构造和区间插入不会删除key重复的值。
2.count
与set的count相比,当所给定的key值在multimap中时,multimap的count返回的是这个key值相同的所有元素的个数,不存在具有这个key值的元素时,返回值还是0.
3.find
与map相比,multimap之中find返回的所有迭代器都是中序遍历第一个出现那个节点所对应的迭代器,
4.erase
multimap的按值删除会删除所有key值为这个值的节点
5.equal_range 返回值为key的这段元素的迭代器区间(左闭右开)构成的pair
6.
[]运算符
map:支持[]访问 / 修改multimap:不支持[](因为 key 不唯一,不知道取哪个)
代码示例:
cpp
#include <iostream>
#include <map> // multimap 包含在这里
using namespace std;
void testMultimap()
{
// 1. 创建 multimap(key 可重复)
multimap<string, int> mm;
// 2. 插入:允许 key 重复(multimap 最核心特点)
mm.insert({ "apple", 1 });
mm.insert({ "apple", 2 }); // 重复 key,插入成功!
mm.insert({ "apple", 3 });
mm.insert({ "banana", 10 });
mm.insert({ "orange", 20 });
cout << "=== 插入重复 key 后 ===" << endl;
for (auto& e : mm) {
cout << e.first << " : " << e.second << endl;
}
// 结果:apple 出现 3 次
// 3. multimap 不支持 [] 操作!
// mm["apple"]; // ❌ 报错!
// 4. find:找到第一个匹配的 key
cout << "\n=== find 查找 apple(返回第一个)===" << endl;
auto pos = mm.find("apple");
if (pos != mm.end()) {
cout << pos->first << " : " << pos->second << endl;
}
// 5. 遍历所有相同 key 的元素(multimap 特有)
cout << "\n=== 遍历所有 apple ===" << endl;
while (pos != mm.end() && pos->first == "apple") {
cout << pos->first << " : " << pos->second << endl;
pos++;
}
// 6. count:统计 key 出现次数
cout << "\napple 出现次数:" << mm.count("apple") << endl;
// 7. erase(key):删除所有同 key 的元素
mm.erase("apple");
cout << "\n=== erase(\"apple\") 后 ===" << endl;
for (auto& e : mm) {
cout << e.first << " : " << e.second << endl;
}
// 8. 清空
mm.clear();
}
int main()
{
testMultimap();
return 0;
}













