作者:@小萌新
专栏:@C++进阶
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:简单介绍C++中map和set容器
C++STL包含了序列式容器和关联式容器:
序列式容器: 序列式容器里面储存的式元素本身 其底层为线性序列的数据结构 比如说:vector list deque等
关联式容器: 关联式容器里面储存的是
小Tip: C++STL中的queue和stack并不属于容器 而是容器适配器 本质是由容器封装而成的 默认封装容器是deque
根据应用场景的不同 C++STL总共实现了两种不同结构的关联式容器: 树形结构和哈希结构
关联式容器 | 容器结构 | 底层实现 |
---|---|---|
set、map、multiset、multimap | 树型结构 | 平衡搜索树(红黑树) |
unordered_set、unordered_map、unordered_multiset、unordered_multimap | 哈希结构 | 哈希表 |
其中 树形结构容器中的序列是一个有序的序列
而哈希结构容器中的序列是一个无序的序列
键值对是用来表示一种具有一一对应关系的一种结构 该结构中一般只包含两个成员变量key和value
key标志键值 value表示key对应的信息
比如说我们现在要建立一个英译汉的词典
那么该词典中的英文单词和其对应的中文意思之间就是一一对应的关系
在SGI版本的STL中 对于键值对的定义如下
template
struct pair
{typedef T1 first_type;typedef T2 second_type;T1 first;T2 second;pair() : first(T1()) , second(T2()){}pair(const T1& a, const T2& b) : first(a), second(b){}
};
set s1; // 构造int类型的空容器
set s2(s1) // 拷贝构造与s1相同的容器
string str("hello world");set s3(str.begin(), str.end()); // 使用string的迭代器拷贝构造
set> s4; // 构造int类型的空容器 比较大小为greater
常用接口函数
迭代器相关函数
使用代码如下
set s1;s1.insert(1);s1.insert(3);s1.insert(5);s1.insert(3);s1.insert(4);s1.insert(9);s1.insert(7);s1.insert(4);for (auto x : s1){cout << x << endl;}
我们使用set容器 并且插入多组重复数据 之后使用范围for遍历 达到一个去重排序的效果
set s1;s1.insert(1);s1.insert(3);s1.insert(5);s1.insert(3);s1.insert(4);s1.insert(9);s1.insert(7);s1.insert(4);for (auto x : s1){cout << x << " ";}cout << endl;s1.erase(3);for (auto x : s1){cout << x << " ";}cout << endl;
我们在使用erase括号后面跟上了要删除的值 之后遍历整个set容器
我们可以发现 3被删除了
那么如果我们连续两次删除3会有什么发生呢?
我们可以发现 还是和上面一样 只是删除了3而已
set s1;s1.insert(1);s1.insert(3);s1.insert(5);s1.insert(3);s1.insert(4);s1.insert(9);s1.insert(7);s1.insert(4);set::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}cout << endl;set::reverse_iterator it1 = s1.rbegin();while (it1 != s1.rend()){cout << *it1 << " ";it1++;}
我们还是采用了上面的几组数据 并且使用正向反向迭代器来遍历数据 结果如下
set s1;s1.insert(1);s1.insert(3);s1.insert(5);s1.insert(3);s1.insert(4);s1.insert(9);s1.insert(7);s1.insert(4);cout << s1.count(3) << endl; cout << s1.count(2) << endl;cout << s1.size() << endl;s1.clear();cout << s1.size() << endl;cout << s1.empty() << endl;
我们可以发现查找 3 2 出现的次数
s1的大小 清空后的大小 以及s1是否为空
set s1;s1.insert(1);s1.insert(3);s1.insert(5);s1.insert(3);s1.insert(4);s1.insert(9);s1.insert(7);s1.insert(4);set s2;cout << s2.size() << endl;cout << s1.size() << endl;s2.swap(s1);cout << s2.size() << endl;cout << s1.size() << endl;
我们可以发现 交换完之后它们的大小发生了改变
multiset和set的底层实现都是相同的 接口函数也十分类似
它们之间最大的区别就是 multiset中允许键值对冗余
multiset s1;s1.insert(1);s1.insert(3);s1.insert(5);s1.insert(3);s1.insert(4);s1.insert(9);s1.insert(7);s1.insert(4);for (auto x : s1){cout << x << " ";}
我们可以发现 相比于set 这里多出来了很多冗余的数据
由于multiset容器允许键值冗余,因此两个容器中成员函数find和count的意义也有所不同
成员函数find | 功能 |
---|---|
set对象 | 返回值为val的元素的迭代器 |
multiset对象 | 返回底层搜索树中序的第一个值为val的元素的迭代器 |
成员函数count | 功能 |
---|---|
set对象 | 值为val的元素存在则返回1,不存在则返回0(find成员函数可代替) |
multiset对象 | 返回值为val的元素个数(find成员函数不可代替) |
map m1; // 创造一个key为int value为char的空容器
map m2(m1) // 拷贝构造一个m1的复制品m2
map m3(m2.begin(),m2.end()); // 使用m2的迭代器拷贝构造m3
map> m4;// 创造一个key为int value为char 比较方式为greater的空容器
map插入函数的原型如下
pair insert (const value_type& val);
insert函数的参数
insert函数的参数是value_type类型的 实际上就是pair类型的别名
typedef pair value_type;
因此 我们向map容器插入元素的时候 我们需要用key和value构造一个pair对象 然后再将pair对象作为参数传入insert函数
map dict;// 调用pair的构造函数 构造一个匿名对象dict.insert(pair("left", "左边"));dict.insert(pair("right", "右边"));dict.insert(pair("hello", "你好"));for (auto x : dict){cout << x.first << " : " << x.second << endl;}
运行结果如下
但是这种插入方式会导致代码变得很长 所以我们不经常使用它 更多的是使用方式二
在库当中提供了以下的模板
templatepair make_pair(T1 x, T2 y){return (pair(x, y));}
因此我们只需要向函数传递key和value 该函数模板就会根据类型自动推导 最终返回一个pair对象
比如说我们上面的代码可以改写成这样子
map dict;// 调用make_pair函数dict.insert(make_pair("left", "左边"));dict.insert(make_pair("right", "右边"));dict.insert(make_pair("hello", "你好"));for (auto x : dict){cout << x.first << " : " << x.second << endl;}
是不是感觉代码整体简洁了不少呢
insert的返回值
我们首先来看官网的原文
The single element versions (1) return a pair, with its member pair::first set to an iterator pointing to either the newly inserted element or to the element with an equivalent key in the map. The pair::second element in the pair is set to true if a new element was inserted or false if an equivalent key already existed.
The versions with a hint (2) return an iterator pointing to either the newly inserted element or to the element that already had an equivalent key in the map.
Member type iterator is a bidirectional iterator type that points to elements.
pair is a class template declared in (see pair).
我们从上面可以知道
insert函数的返回值是一个pair对象 该对象中的第一个成员是map的迭代器类型 第二个成员是一个bool类型具体含义如下
map查找函数的原型如下
iterator find (const key_type& k);
map查找函数是根据key值在map当中查找
map dict;// 调用make_pair函数dict.insert(make_pair("left", "左边"));dict.insert(make_pair("right", "右边"));dict.insert(make_pair("hello", "你好"));// 查找right并且打印right的中文意思auto it = dict.find("right");if (it != dict.end()){cout << it->second << endl;}
}
map删除的函数原型如下:
删除指定k值
size_type erase (const key_type& k);
删除迭代器位置
void erase(iterator position);
这里和set是一样的
我们使用两种方式删除
map dict;// 调用make_pair函数dict.insert(make_pair("left", "左边"));dict.insert(make_pair("right", "右边"));dict.insert(make_pair("hello", "你好"));auto it = dict.find("right");dict.erase(it); // 删除right的迭代器dict.erase("left"); // 删除key值为left的数据for (auto x : dict){cout << x.first << " : " << x.second << endl;}
我们可以发现 最后dict被删除的只剩下了 hello
map的【】运算符重载的函数原型如下:
mapped_type& operator[] (const key_type& k);
【】运算符重载函数的参数就是一个key值 而这个函数的返回值如下
(*((this->insert(make_pair(k, mapped_type()))).first)).second
看上去可能有点难理解 但是我们分成三个步骤就好理解多了
对应的分解代码如下
mapped_type& operator[] (const key_type& k){//1、调用insert函数插入键值对pair ret = insert(make_pair(k, mapped_type()));//2、拿出从insert函数获取到的迭代器iterator it = ret.first;//3、返回该迭代器位置元素的值valuereturn it->second;}
那么这个重载【】有什么意义呢 我们看下面的这段代码
map dict;// 调用make_pair函数dict.insert(make_pair("left", "左边"));dict.insert(make_pair("right", "右边"));dict.insert(make_pair("hello", "你好"));dict["right"] = "正确";dict["go"] = "出发";for (auto x : dict){cout << x.first << " : " << x.second << endl;}
以这两行代码为例
dict["right"] = "正确";dict["go"] = "出发";
通过了【】的三个步骤之后 不管原来的dict里面有没有我们的key值
都会通过insert函数来返回一个迭代器的value的引用
从而会发生下面的情况
我们修改了right的含义 并且增加了go的含义 于是我们可以总结一下
其他成员函数的用法基本上和set一样
这里就不过多介绍了 代码和运行结果如下
map dict;// 调用make_pair函数dict.insert(make_pair("left", "左边"));dict.insert(make_pair("right", "右边"));dict.insert(make_pair("hello", "你好"));map dict1;// 调用make_pair函数dict1.insert(make_pair("left", "左边"));dict1.insert(make_pair("right", "右边"));dict1.insert(make_pair("hello", "你好"));cout << dict.size() << endl;cout << dict.empty() << endl;cout << dict.count("left") << endl;dict.clear();cout << dict.empty() << endl;dict.swap(dict1);cout << dict.size() << endl;
multimap和map的底层一样 都是由红黑树构造的
它们的接口函数也类似
它允许键值冗余
代码和演示结果如下
multimap dict;// 调用make_pair函数dict.insert(make_pair("right", "对的"));dict.insert(make_pair("left", "左边"));dict.insert(make_pair("right", "右边"));dict.insert(make_pair("hello", "你好"));for (auto x : dict){cout << x.first << " : " << x.second << endl;}
由于multimap容器允许键值冗余 因此两个容器中成员函数find和count的意义也有所不同
成员函数find | 功能 |
---|---|
map对象 | 返回值为键值为key的元素的迭代器 |
multimap对象 | 返回底层搜索树中序的第一个键值为key的元素的迭代器 |
成员函数count | 功能 |
---|---|
map对象 | 键值为key的元素存在则返回1,不存在则返回0(find成员函数可代替) |
multimap对象 | 返回键值为key的元素个数(find成员函数不可代替) |
由于multimap允许键值冗余 所以说调用【】时 返回值存在歧义 所以在muitimap中没有重载【】