关联式容器和序列式的容器
首先,我们今天介绍的set和map容器是属于STL里面的关联式容器。不同于我们先前所学的string,vector还有list这样的线性逻辑结构。 set和map容器是属于关联式的,也就是set和map的元素的关系不在是先前那样简单的线性逻辑。set和map容器的底层是一个搜索二叉树! 更确切地说,是一棵兼具平衡性的红黑树! 不过我们今天的任务只是简单认识一下如何使用这两个容器就可以了!
set容器介绍及其的使用
和学习其他的容器一样,我们也是通过文档来学习set的使用
set的使用文档
接下来我们通过代码来看一看set的使用
#include<iostream>
#include<set>
using namespace std;
void test_set1()
{
set<int>s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
int main()
{
test_set1();
return 0;
}
那么假设我往set里面插入重复的值会发生什么呢?
#include<iostream>
#include<set>
#include<map>
using namespace std;
void test_set2()
{
set<int>s;
s.insert(1);
s.insert(1);
s.insert(1);
s.insert(1);
s.insert(1);
s.insert(4);
s.insert(3);
s.insert(5);
s.insert(2);
for (int e : s)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test_set2();
return 0;
}
从结果不难看出:set容器对于插入的元素不仅会进行排序,还会去掉重复插入的元素!所以set容器的作用就是去重+排序! set的删除对应的就是erase方法,提供了如下三种的重载删除的版本: 那么对应我们来使用erase方法:
#include<iostream>
#include<set>
using namespace std;
void test_set3()
{
set<int>s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
set<int>::iterator pos = s.find(3);
if (pos != s.end())
{
cout << "找到待删除的节点" << endl;
s.erase(pos);
}
for (int e : s)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test_set3();
return 0;
}
然而提供迭代器的删除方法不仅要先查找,而且还要检查返回的迭代器的合法性!因此这种方法不是特别好用!而相对更常用的就是直接按值进行删除的版本
void test_set4()
{
set<int>s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
s.erase(3);
for (int e : s)
{
cout << e << " ";
}
cout << endl;
}
使用这个重载版本的erase不仅很好地查找到了节点,同时也把这个节点删除了,而且不需要我们检查是否合法,函数内部会自行处理! 接下来再来介绍两个set里面比较特殊的方法。 首先第一个是lower_bound 一样我们可以通过查阅对应的文档了解使用:
lower_bound的使用
那么对于lower_bound的解释是这样的:接受一个参数x,返回一个指向的元素>=x的迭代器,具体我们可以通过下面这段代码来体会一下。
#include<iostream>
#include<set>
#include<map>
using namespace std;
void test_set5()
{
set<int>s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
s.insert(6);
set<int>::iterator it1 = s.lower_bound(3);
cout << *it1 << endl;
auto it2 = s.lower_bound(5);
cout << *it2;
}
int main()
{ test_set5();
return 0;
}
从运行结果也不难看出,有3就返回指向3的迭代器,没有5就返回大于5的元素6的迭代器。 我们再来看看对应的upper_bound方法
upper_bound的说明文档
upper_bound的解释:接受一个参数x,返回指向>x的set中元素的迭代器 我们同样可以通过一段代码来验证是否是这样:
#include<iostream>
#include<set>
#include<map>
using namespace std;
void test_set6()
{
set<int>s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
s.insert(6);
set<int>::iterator it1 = s.upper_bound(3);
cout << *it1 << endl;
auto it2 = s.upper_bound(5);
cout << *it2;
}
int main()
{
test_set6();
return 0;
}
可以看到即使这里的set里面有3这个元素,upper_bound也不会返回3,而是返回了4元素的迭代器! 对比lower_bound和upper_bound我们不难可以看出: lower_bound和upper_bound的设计也是为了实现左闭右开! 所以我们就可以利用这两个方法来做区间删除的操作!
include<iostream>
#include<set>
#include<map>
using namespace std;
void test_set7()
{
set<int>s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
s.insert(6);
auto start = s.lower_bound(3);
auto finish = s.upper_bound(5);
for (int e : s)
cout << e << " ";
cout << endl;
s.erase(start, finish);
for (int e : s)
cout << e << " ";
}
int main()
{
test_set7();
return 0;
}
注意:set里面的元素是不能够被修改的!所以即使是使用普通的迭代器,我们也不能对set里面的元素进行写入操作!
map容器介绍及其使用
说完了set容器,那么接下来我们就要来看看map容器。同样我们可以通过文档进行学习。
map容器的说明文档
map的所有的元素都是一个pair,而为什么是pair的原因就是:c++不支持一个函数返回多个值!但是map又是<K,V>模型的搜索树,所以才有了pair这个类型。 下面我们来看一看map的简单使用:
#include<iostream>
#include<map>
using namespace std;
void test_map1()
{
map<int, int>m;
int a[] = { 5,6,4,3,7,5,4,6,11,10,2,1 };
for (int e : a)
m.insert(make_pair(e, e));
for (auto& kv : m)
cout << kv.first << ":" << kv.second << endl;
}
int main()
{
test_map1();
return 0;
}
对于map来说,它是<K,V>模型的二叉搜索树。map中元素的key参数是不能修改的,但是对应的val是可以进行修改的! 而map的erase和set几乎是一模一样的,这里就不在演示了。接下来我们看一看map的使用案例:
#include<iostream>
#include<set>
#include<map>
#include<string>
using namespace std;
void test_map2()
{
map<string, int>m;
string arr[] = { "C","C++" ,"java","python","php","C++","java","php"};
for (const auto& str : arr)
{
map<string, int>::iterator it = m.find(str);
if (it != m.end())
{
it->second++;
}
else
{
m.insert(make_pair(str, 1));
}
}
for (const auto& kv : m)
{
cout << kv.first << " : " << kv.second << endl;
}
}
int main()
{
test_map2();
return 0;
}
这是一种解决的方式:但是这样显得很麻烦,上面的问题还可以用下面的方式解决:
#include<iostream>
#include<set>
#include<map>
#include<string>
using namespace std;
void test_map3()
{
map<string, int> m;
string arr[] = { "C","C++" ,"java","python","php","C++","java","php" };
for (const auto& str : arr)
{
m[str]++;
}
for (const auto& kv : m)
{
cout << kv.first << ": " << kv.second << endl;
}
}
int main()
{
test_map3();
return 0;
}
可以看到,这种方式的统计结果也是正确的。 而map[str]肯定就是调用了对应的operator[]函数,下面我们就来分析这个operator[]函数。
operator[]
接下来,我们就重点介绍这个operator[]函数 我们先来看文档里面对于operator[]的介绍
map的operator[]
那么在介绍operator[]之前,我们有必要了解一下insert函数的返回值
pair<iterator,bool> insert (const value_type& val);
insert方法的返回值是一个pair,这个pair的第一个成员是iterator,第二个则是标注是否成功插入
#include<>
void test_map4()
{
map<string, int> m;
string arr[] = { "C","C++" ,"java","python","php","C++","java","php" };
for (const auto& str : arr)
{
auto ret = m.insert(make_pair(str, 1));
if (!ret.second)
ret.first->second++;
}
}
int main()
{
test_map4();
return 0;
}
那么operator[]函数的实现方式类似下面这段代码:
(*((this->insert(make_pair(k,mapped_type()))).first)).second
看起来非常复杂,但是我们可以将其简化
auto ret=insert( make_pair(k,mapper()));
return ret.first->second;
而当时可能因为编译器对箭头运算符的支持度不够好,所以用了(*ret).second 那么当我们调用operator[]的时候,实际的过程会是这样的:
map<string, int> m;
string arr[] = { "C","C++" ,"java","python","php","C++","java","php" };
for (const auto& str : arr)
{
m[str]++;
}
总结:operator[]的作用:1.key不在map中,构建pair插入 2.key在map中是,返回pair中second成员的引用!
multiset和multimap
前面我们提到的set和map容器的元素的key都是不允许重复的。但是有的情况下又出现需要键值冗余的情况。所以官方库提供了multiset和multimap这两个允许键值冗余的容器。
multiset的使用文档 multimap的使用文档
#include<iostream>
#include<set>
void test_multiset1()
{
multiset<int> ms;
ms.insert(1);
ms.insert(1);
ms.insert(1);
ms.insert(2);
ms.insert(2);
ms.insert(3);
ms.insert(4);
ms.insert(5);
for (int e : ms)
cout << e << " ";
cout << endl;
}
int main()
{
test_multiset();
return 0;
}
可以看到这里确实是插入了多个相同的值。对于multimap也是如此。 这两个容器的操作和set,map类似。但是multimap不支持operator[],因为它有多个key,所以无法确定是哪一个key。 对于multiset和multimap我们就介绍到这里。
以上就是本文对于map和set容器的简单介绍。如果有不足或错误之处还望指出。希望大家一起共同进步。
|