人虽然可以为所欲为,但却不能得偿所愿。
前言
这是我自己学习C++的第十四篇博客总结。后期我会继续把C++学习笔记开源至博客上。
上一期笔记是关于C++的搜索二叉树知识,没看的同学可以过去看看:
序列式容器与关联式容器
1. string、vector、stack、queue、deque等都属于是序列式容器,逻辑结构为线性序列,任意两个位置的储存值之间没有紧密的关联关系。
2. 序列式容器保存和访问储存值时,一般依靠储存值在容器中储存的位置。
3. set、map、unordered_set、unordered_map等都属于是关联式容器,逻辑结构为非线性结构,任意两个位置的储存值之间存在紧密的关联关系。
4. 关联式容器保存和访问储存值时,一般依靠容器中的关键字Key。
set类
set类的介绍
1. 在使用set类时,必须包含 #include <set> 这一行。
2. set类的底层其实是一个红黑树结构,使用时需要显示实例化。
3. 下面是set类的官方文本介绍,里面有详细的用法讲解。
set类对象的常见构造
1. set<int> s1,什么也不需要传入,构造一个空的set类对象。
2. set<int> s2(s1.begin(),s1.end()),使用另一个set类对象进行迭代器构造。
3. set<int> s3(const set<int>& s2),使用另一个set类对象进行拷贝构造。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s1;
set<int> s2(s1.begin(), s1.end());
set<int> s3(const set<int>& s2);
return 0;
}
set类对象的容量操作
1. set.size(),返回set类对象中有效元素个数。
2. set.empty(),检测set类对象的有效节点是否为空,为空返回true,不为空返回flase。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s1;
s1.insert(1);
s1.insert(4);
s1.insert(3);
s1.insert(2);
s1.insert(5);
s1.insert(6);
cout << s1.size() << endl;//6
cout << s1.empty() << endl;//0
return 0;
}
set容器的修改操作
1. set.insert(int num),向set类对象中插入整数num,如果插入set类对象已有的元素,则插入失败。
2. set.erase(int num),向set类对象中删除整数num,如果删除set类对象没有的元素,则删除失败。
3. set.erase(iterator pos),向set类对象中删除迭代器为pos的值。
4. set.find(int num),检查set类对象中是否存在某个特定的元素num,效率为log(N)。如果找到了,则返回指向元素num的迭代器;如果没有找到,则返回指向end()的迭代器。
5. set.count(int num),检查set类对象中是否存在某个特定的元素num,返回元素num的个数。
6. set.lower_bound(int num),返回一个迭代器,指向set类对象中第一个大于等于num的元素。
7. set.upper_bound(int num),返回一个迭代器,指向set类对象中第一个大于num的元素。
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s1;
s1.insert(1);
s1.insert(4);
s1.insert(3);
s1.insert(2);
s1.insert(5);
s1.insert(6);
s1.insert(6);//如果插入set对象中已有元素,则插入失败
for (auto it : s1)
{
cout << it << " ";//123456
}
cout << endl;
s1.erase(6);
s1.erase(7);//如果删除set对象中未有元素,则删除失败
for (auto it : s1)
{
cout << it << " ";//12345
}
cout << endl;
s1.erase(s1.begin());
for (auto it : s1)
{
cout << it << " ";//2345
}
cout << endl;
auto it = s1.find(4);
cout << *it << endl;//4
if (s1.count(3))
{
cout << "存在3";//"存在3"
}
return 0;
}
set类对象的遍历操作
1. set类支持迭代器操作,迭代器遍历走二叉搜索树的中序遍历,因此遍历默认为升序。
2. set.begin()、set.end(),set.begin()获取第一个元素的迭代器,set.end()获取最后一个元素的下一个位置的迭代器。
3. set.rbegin()、set.rend(),set.rbegin()获取最后一个元素的迭代器,set.rend()获取第一个元素的上一个位置的迭代器。
//insert()
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s1;
s1.insert(1);
s1.insert(4);
s1.insert(3);
s1.insert(2);
s1.insert(5);
s1.insert(6);
s1.insert(6);//如果插入set对象中已有元素,则插入失败
auto it=s1.begin();
while(it!=s1.end())
{
cout << *it << " ";
it++;
}
return 0;
}
//1 2 3 4 5 6
multiset类
1. multiset和set的使用基本类似,主要区别点在于multiset支持值冗余,那么insert、find、count、erase都围绕着支持冗余有所差异。
2. insert 插入 multiset类对象 已有元素时, 不会插入失败 , 而会正常插入 。3. find 查找 multiset类对象 已有元素时, 若存在多个 , 则返回第一个元素的迭代器 。4. erase 删除 multiset类对象 已有元素时, 若存在多个 , 删除全部的该元素 。5. count 查找 multiset类对象 已有元素时, 若存在多个 , 则会返回该元素的全部个数 。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <set>
using namespace std;
int main()
{
multiset<int> m1;
m1.insert(1);
m1.insert(1);
m1.insert(2);
m1.insert(3);
m1.insert(3);
m1.insert(4);
m1.insert(5);
m1.insert(6);
m1.insert(2);
m1.insert(4);
for (auto e : m1)
{
cout << e << " ";//1122334456
}
cout << endl;
auto it = m1.find(3);
cout << *it << endl;//3
cout << m1.count(1) << endl;//2
m1.erase(2);
for (auto e : m1)
{
cout << e << " ";//11334456
}
cout << endl;
return 0;
}
map类
map类的介绍
1. 在使用map类时,必须包含 #include <map> 这一行。
2. map类的底层其实是一个红黑树结构,使用时需要显示实例化。
3. 下面是map类的官方文本介绍,里面有详细的用法讲解。
pair类型的介绍
1. map的每个节点都存储了一个键值对,键值对的数据类型为 pair < Key , Value >。
2. 键值对需要使用大括号来包裹:{ Key , Value },如果是键值对的集合,则需另外用一层大括号来包裹:{ { Key1 , Value1 } , { Key2 , Value2 } , { Key3 , Value3 } }。
template <class T1,class T2>
struct pair
{
T1 first;
T2 second;
pair()
:first(T1())
,second(T2())
{}
};
map类对象的常见构造
1. map<string,string> s1,什么也不需要传入,构造一个空的map类对象。
2. map<string,string> s2(s1.begin(),s1.end()),使用另一个map类对象进行迭代器构造。
3. map<string,string> s3(const map<string,string>& s2),使用另一个map类对象进行拷贝构造。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<string, string> m1;
map<string, string> m2(m1.begin(),m1.end());
map<string, string> m3(m2);
return 0;
}
1. map类对象的初始化分为两种。
2. 如果使用=号,则为拷贝初始化;如果不使用=号,则为直接初始化。
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<string, string> m1 = { { "1", "难绷" },{"2","呵呵"} };
map<int, string> m2{ { 1, "难绷" } ,{2,"呵呵"} };
}
map容器的修改操作
1. map.insert({key,value}),向map类对象中插入键值对,如果插入map类对象已有的键key,则插入失败。
2. map.find(key),检查map类对象中是否存在某个特定的键key,效率为log(N),返回一个迭代器。如果找到了,则返回指向键key的迭代器;如果没有找到,则返回指向end()的迭代器。
3. map.count(key),检查map类对象中是否存在某个特定的键key,返回键key的个数。
4. map.erase(key),向map类对象中删除键为key的键值对,如果删除map类对象没有键key,则删除失败。
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<string, string> m1{ { "right","右边" } };
m1.insert({ "left","左边" });
m1.insert({ "left","2边" });//向map类对象中插入已有的键,插入失败
m1.insert({ "front","前面" });
m1.insert({ "back","后面" });
if (m1.count("front"))
{
auto it = m1.find("front");
cout << it->first << " : " << it->second << endl;//front : 前面
}
m1.erase("front");
m1.erase({ "hehe","2边" });//向map类对象中删除未有的键,删除失败
if (m1.count("front"))
{
auto it = m1.find("front");
cout << it->first << " : " << it->second << endl;
}
else
{
cout << "不存在front" << endl;//不存在front
}
return 0;
}
map类对象的遍历操作
1. map类支持迭代器操作,迭代器遍历走二叉搜索树的中序遍历,因此默认为升序。
2. map.begin()、map.end(),map.begin()获取第一个元素的迭代器,map.end()获取最后一个元素的下一个位置的迭代器。
3. map.rbegin()、map.rend(),map.rbegin()获取最后一个元素的迭代器,map.rend()获取第一个元素的上一个位置的迭代器。
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<string, string> m1 = { {"1","难绷"},{"2","难说"},{"3","难道"} };
auto it = m1.begin();
while (it != m1.end())
{
cout << it->first << " ";
it++;
}
cout << endl;
for (auto e : m1)//e的类型和m1相同
{
cout << e.second << " ";
}
return 0;
}
//1 2 3
//难绷 难说 难道
map类对象的数据修改
1. map支持修改value值,不支持修改key键,如果修改关键字数据,就破坏了底层搜索树的结构。
2. map 还有一个非常重要的接口 [ ] ,但是 [ ] 不仅仅支持修改,还支持插入数据和查找数据,它是一个多功能复合接口。3. insert() 插入一个 pair<key,value>键值对 :
- 如果键key已经在map中,则插入失败,返回一个pair<iterator,bool>键值对,first是键key所在结点的迭代器,second是false。
- 如果键key不在map中,则插入成功,则返回一个pair<iterator,bool>对象,first是新插入的键key所在结点的迭代器,second是true。
- 无论插入成功还是失败,返回的pair<iterator,bool>键值对的first都会指向键key所在的迭代器。
- 也就意味着insert插入失败时也充当了查找的功能,正是因为这一点,insert可以用来实现 [ ]。
- 需要注意的是这里有两个pair,一个是map底层红黑树节点中储存的pair<key,Value>,另一个是insert返回的pair<iterator,bool>。
mapped_type& operator[] (const key_type& k)
{
pair<iterator, bool> ret = insert({ k, mapped_type() });
iterator it = ret.first;
return it->second;
}
//[]的各种功能
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<string, string> m1{ {"apple","苹果"} };
m1["pear"] = "梨子";//如果不存在键key,则[]可以实现插入
m1["left"] = "左边";
m1["banana"] = "香蕉";
m1["left"] = "左边、剩下";//如果已经存在键key,则[]可以实现修改和查找
for (auto e : m1)
{
cout << e.second << endl;
}
return 0;
}
//不用[]统计数量
#include <iostream>
#include <map>
using namespace std;
int main()
{
string arr[] = { "苹果","香蕉","西瓜","橘子","葡萄","苹果","苹果","橘子","苹果" };
map<string, int> m1;
for (auto e : arr)
{
auto it = m1.find(e);
if (it == m1.end())
{
m1.insert({e,1});
}
else
{
it->second++;
}
}
for (auto e : m1)
{
cout << e.first << " -> " << e.second << endl;
}
return 0;
}
//使用[]统计数量
#include <iostream>
#include <map>
using namespace std;
int main()
{
string arr[] = { "苹果","香蕉","西瓜","橘子","葡萄","苹果","苹果","橘子","苹果" };
map<string, int> m1;
for (auto e : arr)
{
m1[e]++;
}
for (auto e : m1)
{
cout << e.first << " -> " << e.second << endl;
}
return 0;
}
multimap类
1. multimap 与 map 的 使用 基本类似 ,主要区别点在于 multimap支持键key冗余 ,那么 insert 、 find 、 count 、 erase 都围绕着 支持键key冗余 有所差异。2. 其次就是 multimap不支持 [ ] ,因为 multimap支持键key冗余 , [ ] 就只能实现插入了,不能支持修改,此时 [ ] 实现的意义并不大 。
致谢
感谢您花时间阅读这篇文章!如果您对本文有任何疑问、建议或是想要分享您的看法,请不要犹豫,在评论区留下您的宝贵意见。每一次互动都是我前进的动力,您的支持是我最大的鼓励。期待与您的交流,让我们共同成长,探索技术世界的无限可能!