前言
本系列文章承接C++基础的学习,需要++有C语言的基础++ 才能学会哦~
第19篇主要讲的是有关于C++的++set++ 和++map++ 。
C++才起步,都很简单!!
序列式容器和关联式容器
**序列式容器:**逻辑结构为线性,存储的值之间一般没有紧密联系,两个位置的值交换后依旧是序列式容器。如string、vector、list、deque、array和forward_list等。
**关联式容器:**逻辑结构通常为非线性结构,存储的值之间有紧密的关联,两个位置的值交换后存储结构就会被破坏。如map/set系列和unordered_map/unordered_set系列。
map和set的底层是红黑树,红黑树是平衡二叉搜索树。
set和multiset
set不允许冗余,multiset允许冗余。
set类介绍
声明如下:
cpp
template<class T,
class Compare = less<T>,
class Alloc = allocator<T>
>class set;
共三个模板参数。
Ⅰ.第一个参数T为底层关键字(码)的类型。
Ⅱ.第二个参数Compare为比较函数,不传递时,默认要求T支持小于比较,如果T类型不支持或者想按自己的需求,就传递自行实现的仿函数。
Ⅲ.第三个参数Alloc为底层存储数据的内存空间,不传递时,默认在空间配置器中申请,如果需要,可以传递自行实现的内存池。
Ⅳ.一般只传第一个参数即可。
Ⅴ.set底层用红黑树实现,增删查的效率是O(logN),迭代器遍历用中序,所以是有序的遍历。
set的构造和迭代器
set支持双向迭代器,迭代器的迭代顺序遵循二叉搜索树的中序。
使用方法和之前学习的序列式容器几乎相同,这里不多赘述。
set的功能和接口
insert()
cpp
set<int> s;
//也可以
//set<int, greater<int>> s;
s.insert(5);
s.insert(7);
s.insert(1);
s.insert(2);
//也可以
//s.insert({5,7,1,2});
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
set<string> strset = {"sort", "insert", "add"};
//string按照ascll码大小进行遍历
for(auto& e : strset)
{
cout << e << " ";
}
cout << endl;
erase()
返回删除成功的个数。
cpp
int main()
{
set<int> s = { 4,2,7,2,8,5,9 };
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
//删除最小值
s.erase(s.begin());
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
//直接删除x
int x;
cin >> x;
int num = s.erase(x);
if (num == 0)
{
cout << x << "不存在!" << endl;
}
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}
找到就删,找不到就不删,返回删除对象的个数。


swap()
交换两个容器之内的内容。
cpp
#include <iostream>
#include <set>
#include <functional>
int main() {
// 定义两个类型一致的set(都是int,默认less<int>)
std::set<int> s1 = {1, 2, 3};
std::set<int> s2 = {10, 20, 30};
std::cout << "交换前:" << std::endl;
std::cout << "s1: ";
for (int num : s1) std::cout << num << " "; // 输出:1 2 3
std::cout << "\ns2: ";
for (int num : s2) std::cout << num << " "; // 输出:10 20 30
// 调用swap交换s1和s2
s1.swap(s2);
std::cout << "\n\n交换后:" << std::endl;
std::cout << "s1: ";
for (int num : s1) std::cout << num << " "; // 输出:10 20 30
std::cout << "\ns2: ";
for (int num : s2) std::cout << num << " "; // 输出:1 2 3
return 0;
}
set的swap交换很快,他只交换两个容器之间的指针。
lower_bound()和upper_bound()
用于查找某区间。
cpp
int main()
{
set<int> s = { 4,2,7,2,8,5,9 };
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
//返回≥2的第一个数值的位置
auto itlow = s.lower_bound(2);
//返回>5的第一个数值的位置
auto itup = s.upper_bound(5);
//实际上删除的是[2,7)这个区间
s.erase(itlow, itup);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}
删除掉了区间 [ 2,7 )的数值

upper_bound( n )返回的是>n 的第一个数值的位置;
lower_bound( n )返回的是 ≥ n 的第一个数值的位置,从而实现左闭右开。
find()
set自身实现了一个find()函数,而不是用标准库里的find(),这个set.find()能更好兼容set,时间复杂度为O(logN),标准库的find()适配的是序列式容器,时间复杂度为O(N)。
count()
返回某个key的个数。
set因为不支持冗余,因此只会返回0或者1。multiset会返回实际存储的个数,返回结果 ≥ 0。
可以用来查找对象是否存在于set中。
multiset类与set的不同点
①multiset类可以插入冗余值x,不去重。
②find查找时,x存在多个时,找中序的第一个。
③erase删除时,会删除所有的x值。
④count会返回x的实际个数。
map和mutimap
map类介绍
声明如下:
cpp
template<class Key,
class T,
class Compare = less<key>,
class Alloc = allocator<pair<const Key, T>>
>class map
pair类型
pair是map底层的红黑树结点的数据,它是Key,T的键值对数据,是一个类模板。
cpp
//initializer_list构造和迭代遍历
map<string, string> dict = { {"left","左边"} ,{"right","右边"},{"insert","插入"},{"string","字符串"} };
//其中,调用了pair的构造,即等效于:
pair<string, string> kv1 = { "left", "左边" };
pair<string, string> kv2 = { "right","右边" };
pair<string, string> kv3 = { "insert","插入" };
pair<string, string> kv4 = { "string","字符串" };
map<string, string> dict = { kv1,kv2,kv3,kv4 };
pair.first为pair的键,pair.second为pair的值。
cpp
map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
cout << (*it).first << ":" << (*it).second << " ";
++it;
}
cout << endl;
map的->重载过,map迭代器的->运算符直接返回对应的pair对象指针,使得(*it).first 等效于 it->first。
cpp
map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
cout << it->first << ":" << it->second << " ";
++it;
}
cout << endl;
++pair不支持流插入。++
map的构造和迭代器
map同样支持双向迭代器~
map的功能与接口
insert()
有四种方法进行插入,推荐第四种,最方便~
cpp
pair<string, string> kv1 = { "first", "第一个" };
//initializer_list构造和迭代遍历
map<string, string> dict = { {"left","左边"} ,{"right","右边"},{"insert","插入"},{"string","字符串"} };
//直接插入
dict.insert(kv1);
//插入匿名对象,匿名对象要自行指明类型
dict.insert(pair<string, string>("second", "第二个"));
//使用函数模板make_pair,则不需要自行指明类型
dict.insert(make_pair("sort", "排序"));
//多参数隐式类型转换
dict.insert({"auto","自动的"});
其中make_pair为函数模板:
cpp
template <class T1, class T2>
inline pair<T1, T2> make_pair (T1 x, T2 y)
{
return ( pair<T1, T2>(x, y) );
}
find()
同set,map也推荐使用自带的find(),而不是标准库的find()。自带的find()时间复杂度低。
erase()
通过key,来删除map中的对象。
operator[ ]
通过key,返回对应value的引用。
insert的返回值
insert()会返回一个pair<iterator, bool>的对象。
first 为传入key所在结点的迭代器,second为代表插入成功与否的bool值。
故无论插入成功与否,first都会返回对应的key结点迭代器,所以插入失败的时候,可以充当一个查找的功能。基于这一点,我们可以用来实现operator[ ]。
operator[ ] 的内部实现
cpp
mapped_type& operator[] (const key_type& k)
{
//若k不在map中,insert会插入k和mapped_typed的类型默认值
//,同时返回传入默认值的引用,通过引用修改返回的映射值,从而达到"插入"+"修改"的功能
//若k在map中,insert插入失败,返回k对应结点的迭代器,
//同时,该函数返回了对应迭代器的映射值的引用,同理通过修改返回的映射值,
//达到"查询"+"修改"的功能。
pair<iterator, bool> ret = insert({k, mapped_type()});
iterator it = ret.first;
return it->second;
}
so,我们可以通过operator[ ]来++实现①查询+修改②插入+修改++ 。但是如果只想查询的话,使用find即可,因为operator底层会调用insert,++不是单纯的查询,所以会导致非期望的默认插入,出现性能损耗,逻辑错误等问题。++
multimap与map的区别
①multimap没有operator[ ] 接口。因为multimap可能会存放多个相同key的对象,索引会有歧义。
补充~
结构化绑定(C++17支持)
cpp
for(const auto& [k,v] : dict)
{
cout << k << ":" << v << endl;
}
cout << endl;
结构化绑定是 C++17 引入的语法糖,核心作用是:将一个聚合类型(如数组、结构体、std::pair/std::tuple、map 的键值对)的多个成员,一次性绑定到多个变量上 ,无需手动通过 . 或 -> 访问成员,让代码更简洁。
简单说:它帮你 "拆解" 复杂类型,把内部的多个值直接赋值给多个变量,就像 "一次性解构赋值"。
❤~~本文完结!!感谢观看!!接下来更精彩!!欢迎来我博客做客~~❤