目录
2.3multiset容器(multiset和set是两个容器)
前言
在本文的开始前,博主想对你说:
1.确保自己的英语能力可以阅读文档(如若不然可以使用“有道翻译”等翻译软件)
2.确保知道什么是二叉搜索树(如若不然,可以看博主的这篇文章click_me)
3.推荐一个查阅C++知识相关文档的网站——cplusplus(点我跳转click_me)
如果前面几项都准备好了,那么就可以开始阅读该博博文了。
一、set及相关功能介绍
1.set组织数据的逻辑
set的底层逻辑采用了二叉搜索树,这意味着:set这一容器中不应该存在键值相同的元素,set容器数据中序遍历的结果必然有序。(注意:字符/字符串等有序是根据ASC||码来确定的)
根据set的存储结构我们可以利用它来进行排序,根据它的存储逻辑我们可以用来滤掉相同键值的元素来保证set容器中键值的唯一性。
![](https://i-blog.csdnimg.cn/blog_migrate/11839e1ffc6c5f0d061efa459c9496fa.png)
二.C++中的set容器
2.1如何查阅文档
我们首先点击前言中的cplusplus官网
![](https://i-blog.csdnimg.cn/blog_migrate/03e20ff1db2de1b40bc258296b7f9a79.png)
输入set敲击回车进行搜索(请确保输入的内容是正确的,如若不然是没有搜索结果的而且会一直“卡”在搜索页面),页面跳转后下拉网页页面可以看到C++中set相关函数(不只有这一部分)。
![](https://i-blog.csdnimg.cn/blog_migrate/b4c1f53c08da75d1bb2a2c534c82d26c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f3fd1a0b9f199b1803be1e99b2edd285.png)
根据自己需求自行点击对应的功能函数就会自行跳转到详情页。(这里就不过多解释了,希望读者能自行尝试。)
2.2set修改函数的介绍
set是一种容器,所以在实例set的时候需要对set存储数据的类型进行显示声明,实例化set之后我们可以使用insert函数来对数据进行插入,可以使用erase函数来对数据进行删除,使用clear函数对容器进行清空,使用find函数来进行元素的查找等等。遍历容器内容如同其他模板一样,我们可以使用迭代器来进行遍历。
void test_bulid_a_set()
{
//使用insert()函数来插入数据
set<int> tree;
tree.insert(1);
tree.insert(2);
tree.insert(3);
tree.insert(4);
tree.insert(5);
tree.insert(6);
tree.insert(7);
tree.insert(8);
tree.insert(9);
//注意:这里插入了重复元素,根据set的性质
//这一元素不应该存放进容器
tree.insert(1);
//获取tree变量的迭代器
set<int>::iterator it = tree.begin();
//使用迭代器遍历容器内容
while (it != tree.end())
{
cout << *it++ << " " ;
}
//也可使使用范围for来遍历容器内容(范围for底层使用的就是迭代器)
cout << endl;
for (auto& e : tree)
{
cout << e << " ";
}
cout << endl;
//使用erase()来删除一个元素
tree.erase(1);
for (auto& e : tree)
{
cout << e << " ";
}
//find()函数,对在容器内的元素会返回其迭代器
//对于不在容器内的函数会返回空指针
set<int>::iterator del1 = tree.find(20);
if (del1 != tree.end())
{
cout << "找到目标节点" << endl;
tree.erase(del1);
}
else
{
cout << "未找到目标节点" << endl;
}
set<int>::iterator del2 = tree.find(7);
if (del2 != tree.end())
{
cout << "找到目标节点" << endl;
tree.erase(del2);
}
else
{
cout << "未找到目标节点" << endl;
}
cout << endl;
for (auto& e : tree)
{
cout << e << " ";
}
}
注意:在涉及到迭代器的问题时,往往要考虑迭代器是否会失效的问题,迭代器失效又往往发生在对容器内容操作的时候。本文介绍一种由erase()引起的迭代器失效问题,以示警戒(请读者阅读并找出代码的迭代器失效问题):
void del_set_range()
{
set<int> tree;
tree.insert(1);
tree.insert(2);
tree.insert(3);
tree.insert(4);
tree.insert(5);
tree.insert(6);
tree.insert(7);
tree.insert(1);
cout << endl;
for (auto& e : tree)
{
cout << e << " ";
}
auto start = tree.lower_bound(1);
auto finish = tree.upper_bound(6);
while (start!=finish)
{
tree.erase(start);
start++;
}
cout << endl;
for (auto& e : tree)
{
cout << e << " ";
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/d192580b81da80f8c03d846cc82613f4.png)
那么为什么迭代器会失效呢?别急我们来查看一下erase()函数的文档:
![](https://i-blog.csdnimg.cn/blog_migrate/224242f2e45cabb7a2fbcdaa00c399c2.png)
文档关于erase()函数销毁元素的大概意思就是:这个函数会从容器中移除一个元素,如果是有效的删除(即删除元素存在),那么这个容器的大小会随着元素的删除而改变。这就意味着,erase()函数销毁后的容器不一定与销毁前容器在内存中的位置一致,而迭代器还只是重复着销毁元素前容器位置的++。这一点是我们需要注意的。
既然提到了迭代器失效问题,就不得不提到两个获取迭代器位置的函数:
![](https://i-blog.csdnimg.cn/blog_migrate/47335dc126b7caa5a4c4e435b63093c0.png)
lower_bound()获取的是大于等于输入键值的位置的迭代器,upper_bound获取的是大于输入键值位置的迭代器,举个例子:
![](https://i-blog.csdnimg.cn/blog_migrate/0f967edcd25d9d4451ce5097b6b4bfec.png)
2.3multiset容器(multiset和set是两个容器)
multiset与set的区别是,multiset允许存入键值相同的元素,也就意为着multiset实例化后的数据结构可以对数据只进行排序而不过滤掉相同键值的元素,这里再介绍一个新的计数函数count(),这个函数可以统计输入键值在容器中出现的次数。
![](https://i-blog.csdnimg.cn/blog_migrate/262d3c3eb43da2ce9448b2c1cf6e1d69.png)
此外,multiset的其他部分与set均有相似的性质这里就不过多介绍了。
三.C++中的map容器
3.1map容器的介绍
map容器实际上就是用一个键值去绑定其他数据的容器。比如说我们可以用一个唯一的符号作为汉字的标识,因为每一个汉字可能往往都不知含有一种意思,也就是说在存储某一符号对应的意思时,可能是用到类似vector等其他的容器或者基本数据类型等。
3.2map容器的基本功能介绍
map容器虽然有了相较set更清晰的存储内容的能力,但是再对mep容器进行操作时,函数大多都是去检索键值而不是去检索对应绑定的数据。
比如说,在set容器中,不会允许insert插入相同键值的内容,在map中也一样不会接收相同键值的内容。在multiset容器中,允许insert插入相同键值的内容,在multimap容器中,也允许插入相同键值的内容(绑定的数据可能不同)。
但是由于,map将键值与数据相绑定,但是绑定数据的类型是无法预料的所以mao相较set会额外需要一个模板参数来解决这一问题,但这恰恰与set有所出入,set只有一个键值,如果需要修改编译器会很清楚要改变哪个值。但是对于map则不同,编译器无法推断,哪一个是键值(编译器默认以第一个模板参数对应的内容作为键值),而且在输出容器内容的时候,也会十分麻烦,所以在实现map容器的时候,相比于set,C++库中额外实现了first与second分别用来获取第一个模板参数对应的内容和第二个模板参数对应的内容。
此外,在对map容器插入内容时的输入也有了额外的情形(因为是多参数传递)。
![](https://i-blog.csdnimg.cn/blog_migrate/b930074e8f30b9cdc30001456047ac59.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b9c150e5d9e64b95e6caf233878d370f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ad2ad0d082ab683a7a4ab867a2714d0a.png)
find()函数只会找到第一个键值匹配的元素并返回指向其的迭代器
![](https://i-blog.csdnimg.cn/blog_migrate/69ac94de90936693d6bc7cf261144a16.png)
3.3map容器中[]运算符重载
我们再查看一下文档:
![](https://i-blog.csdnimg.cn/blog_migrate/396b25a2f750a2da492a3a05856e95a2.png)
文档中红框框住的意思是使用[]等同于使用:
(*((this->insert(make_pair(k,mapped_type()))).first)).second
这里需要额外说一句的是,在本段代码中,当ret容器的第二个参数操作是:当插入一个新的元素的时候。会将这一值设置为true,如果不是第一次插入那么这一值会被设置成false。这一段在文档中有所提及(有翻译问题就使用翻译软件,别看的一脸懵逼产生误解。)
当ret的第二个参数是false的时候我们就可以对同一键值出现的次数做统计,及调用ret中第一个参数(这个参数在本代码中是一个迭代器指向我们的countmap中最近一次插入元素的位置)的第二个位置,也就是对countmap中某一元素的第二个参数++。
![](https://i-blog.csdnimg.cn/blog_migrate/cb8769e10225b30db6142b702c7687b9.png)
![](https://i-blog.csdnimg.cn/blog_migrate/cc73ce2d5f6b5f4a6e0bd41eb15e9d8d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/05e37c49b5572ca7312a09e7bbdec4be.png)
——本文【完】