set/map
✏️ Map
map
是一种关联式容器,存储的都是 pair
对象,也就是用 pair
类模板创建的键值对。 包括 C++ 基本数据类型(int
、double
等)和使用结构体或类自定义的类型。
map是STL
里的一个模板类,用来存放键值对的数据结构,它的定义如下:
第1个参数存储了key。
第2个参数存储了mapped value。
第3个参数是比较函数的函数对象。map用它来判断两个key的大小,并返回bool类型的结果。利用这个函数,map可以确定元素在容器中遵循的顺序以及两个元素键是否相等
(!comp(a, b) && !comp(b, a))
,确保map中没有两个元素可以具有等效键。这里,它的默认值是less<key>
,定义如下:
第4个参数是用来定义存储分配模型的。
🖋️ 1、特点
map
容器存储的各个键值对,键的值既不能重复也不能被修改。换句话说,map
容器中存储的各个键值对不仅键的值独一无二,键的类型也会用 const 修饰,这意味着只要键值对被存储到map
容器中,其键的值将不能再做任何修改。在使用 map 容器存储多个键值对时,该容器会自动根据各键值对的键的大小,按照既定的规则进行排序。默认情况下,
map
容器选用std::less<T>
排序规则(其中T
表示键的数据类型),其会根据键的大小对所有键值对做升序排序。当然,根据实际情况的需要,我们可以手动指定map
容器的排序规则,既可以选用STL
标准库中提供的其它排序规则(比如std::greater<T>
),也可以自定义排序规则。
🖋️ 2、创建和初始化
🖋️ 3、成员方法
成员方法 | 功能 |
| 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 |
| 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 |
| 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 |
| 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 |
| 和 |
| 和 |
| 和 |
| 和 |
| 在 |
| 返回一个指向当前 |
| 返回一个指向当前 |
| 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 |
| 若容器为空,则返回 |
| 返回当前 |
| 返回 |
|
|
| 找到 |
| 向 |
| 删除 |
| 交换 2 个 |
| 清空 |
| 在当前 |
| 在本质上和 |
| 在当前 |
🖋️ 4、迭代器
C++ STL
标准库为 map
容器配备的是双向迭代器(bidirectional iterator
)。这意味着,map
容器迭代器只能进行 ++p
、p++
、--p
、p--
、*p
操作,并且迭代器之间只能使用 ==
或者 !=
运算符进行比较。map
类模板中还提供了 find()
成员方法,它能帮我们查找指定 key
值的键值对,如果成功找到,则返回一个指向该键值对的双向迭代器;反之,其功能和 end()
方法相同。
同时,map
类模板中还提供有 lower_bound(key)
和 upper_bound(key)
成员方法,它们的功能是类似的,唯一的区别在于:
lower_bound(key)
返回的是指向第一个键不小于key
的键值对的迭代器;upper_bound(key)
返回的是指向第一个键大于key
的键值对的迭代器;
lower_bound(key)
和 upper_bound(key)
更多用于 multimap
容器,在 map 容器中很少用到。
equal_range(key)
成员方法可以看做是 lower_bound(key)
和 upper_bound(key)
的结合体,该方法会返回一个 pair
对象,其中的 2 个元素都是迭代器类型,其中 pair.first
实际上就是 lower_bound(key)
的返回值,而 pair.second
则等同于 upper_bound(key)
的返回值。显然,equal_range(key)
成员方法表示的一个范围,位于此范围中的键值对,其键的值都为 key
。和 lower_bound(key)
一样,该方法也更常用于 multimap
容器,因为 map
容器中各键值对的键的值都是唯一的,因此通过 map
容器调用此方法,其返回的范围内最多也只有 1
个键值对。
🖋️ 5、 map获取键对应值的几种方法
🖋️ 6、insert()
insert()
🖋️ 7、 emplace()
和emplace_hint()
emplace()
和emplace_hint()
🖋️ 8、 multimap
容器
multimap
容器🖋️ 9、const与static的map
✏️ Set
set
也是一种关联式容器,set
容器中只能存储键,是单纯的键的集合,其中键是不能重复的。
🖋️ 1、特点
所有的元素都会被自动排序。
不能通过迭代器来改变set的值,因为set的值就是键。从语法上讲 set 容器并没有强制对存储元素的类型做 const 修饰,即 set 容器中存储的元素的值是可以修改的。但是,C++ 标准为了防止用户修改容器中元素的值,对所有可能会实现此操作的行为做了限制,使得在正常情况下,用户是无法做到修改 set 容器中元素的值的。
如果
set
中允许修改键值的话,那么首先需要删除该键,然后调节平衡,在插入修改后的键值,再调节平衡,如此一来,严重破坏了set
的结构,导致iterator
失效,不知道应该指向之前的位置,还是指向改变后的位置。所以STL
中将set
的迭代器设置成const
,不允许修改迭代器的值。
🖋️ 2、创建和初始化
🖋️ 3、成员方法
成员方法 | 功能 |
| 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 |
| 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 |
| 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 |
| 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 |
| 和 |
| 和 |
| 和 |
| 和 |
| 在 |
| 返回一个指向当前 |
| 返回一个指向当前 |
| 该方法返回一个 |
| 若容器为空,则返回 |
| 返回当前 |
| 返回 |
| 向 |
| 删除 |
| 交换 2 个 |
| 清空 |
| 在当前 |
| 在本质上和 |
| 在当前 |
🖋️ 4、迭代器
set
容器类模板中未提供 at()
成员函数,也未对 []
运算符进行重载。因此,要想访问 set
容器中存储的元素,只能借助 set
容器的迭代器。
C++ STL
标准库为 set 容器配置的迭代器类型为也是双向迭代器。因为 iter
迭代器指向的是 set
容器存储的某个元素,而不是键值对,因此通过 *iter
可以直接获取该迭代器指向的元素的值。除此之外,如果只想遍历 set
容器中指定区域内的部分数据,则可以借助 find()
、lower_bound()
以及 upper_bound()
实现。通过调用它们,可以获取一个指向指定元素的迭代器。需要特别指出的是,equal_range(val)
函数的返回值是一个 pair
类型数据,其包含 2
个迭代器,表示 set
容器中和指定参数 val
相等的元素所在的区域,但由于 set 容器中存储的元素各不相等,因此该函数返回的这 2
个迭代器所表示的范围中,最多只会包含 1
个元素。
虽然 C++ STL
标准中,set
类模板中包含 lower_bound()
、upper_bound()
、equal_range()
这 3 个成员函数,但它们更适用于 multiset
容器,几乎不会用于操作 set
容器。
🖋️ 5、insert()
insert()
🖋️6、删除数据
🖋️ 7、 emplace()
和emplace_hint()
emplace()
和emplace_hint()
🖋️ 8、 multiset
容器
multiset
容器 ✏️ 自定义关联式容器的排序规则
其实在 STL
标准库中,本就包含几个可供关联式容器使用的排序规则,如表表示:
排序规则 | 功能 |
std::less<T> | 底层采用 < 运算符实现升序排序,各关联式容器默认采用的排序规则。 |
std::greater<T> | 底层采用 > 运算符实现降序排序,同样适用于各个关联式容器。 |
std::less_equal<T> | 底层采用 <= 运算符实现升序排序,多用于 |
std::greater_equal<T> | 底层采用 >= 运算符实现降序排序,多用于 |
使用函数对象自定义排序规则
✏️ key的有序性之自定义类型作为key
key
🐹 1、简单方法: 重载operator<()操作符
在我们插入时,map会先通过比较函数地函数对象来比对key的大小,然后根据比对结果进行有序存储。c++标准库中,map比较函数的函数对象不可避免地会用到‘<’
运算,因此一种方法就是直接在自定义类里重载operator<()
操作符:
需要注意的是,在重载operator<(){}
时,无论是参数还是整个函数的const
都不能少。参照less
的定义,less
的参数和函数整体都是const
,那么被调用的operator<()
必然也是同等要求。
还可以以全局函数定义:
当以成员函数的方式重载 < 运算符时,该成员函数必须声明为 const 类型,且参数也必须为 const 类型,至于参数的传值方式是采用按引用传递还是按值传递,都可以(建议采用按引用传递,效率更高)。
还可以使用友元函数的形式实现:
🐹 2、其它方法:比较函数的函数对象
除了直接重载operator<()
,我们可以直接自定义比较函数的函数对象。首先简要介绍一下函数对象的概念:在《C++ Primer Plus》里面,函数对象是可以以函数方式与()结合使用的任意对象。这包括函数名、指向函数的指针和重载了“operator()”
操作符的类对象。基于此,我们提出3种定义方法。
🍈 2.1、方法1: 利用std::function
它是一种通用、多态、类型安全的函数封装,其实例可以对任何可调用目标实体(包括普通函数、Lambda表达式、函数指针、以及其它函数对象等)进行存储、复制和调用操作,方法如下:
我们利用std::function
为MyCompare()
构建函数实例。初始化时,这个函数实例就会被分配那个指向MyCompare()
的指针。因此,在对group进行申明时,需要构造函数指明函数实例。
另外,c++11增加了一个新的关键词decltype
,它可以直接获取自定义哈希函数的类型,并把它作为参数传送。因此,group的声明可以如下修改:
🍈 2.2、方法2: 重载operator()的类
利用重载operator()的类,将比较函数打包成可以直接调用的类:
值得注意的是,这时group的声明不再需要将函数对象的引用传入构造器里。因为map会追踪类定义,当需要比较时,它可以动态地构造对象并传递数据。
🍈 2.3、方法3: less函数的模板定制
通过map的定义可知,第三个参数的默认值是less。显而易见,less属于模板类。那么,我们可以对它进行模板定制,如下所示:
最后更新于