🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
文章目录
- 一、两个概念
- ①关联式容器与序列式容器
- ②键值对
- 二、set、map
- ①set
- set的两种遍历方式
- set的升降序排序
- set的erase
- set的count
- ②map
- SGI-STL中键值对的定义
- map的insert
- 打印map中的元素
- 经典Key/Value模型,水果数数
- map的operator[]
- map的at
- map的operator[]底层简单分析
一、两个概念
①关联式容器与序列式容器
序列式容器也就是vector,list,deque,forword_list等等这种,因为其底层为线性序列的数据结构,里面存的是元素本身
关联式容器:关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存的是<Key,Value>结构的键值对,在数据检索时比序列式容器效率更高
②键值对
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量Key和Value,Key代表值,Value表示与Key对应的信息,比如:现在要建立一个英汉翻译的字典,Key存英文,用于检索它的中文Value
二、set、map
①set
set的本质就是一个Key的模型,用于确认在与不在,几乎所有函数的返回值都是bool
Key模型是不允许修改的,因为也没办法修改,不同的Key对应的是不同的东西,所以说set只能删除掉,再重新添加。
set的两种遍历方式
set和map的遍历都是通过迭代器,迭代器也是一种设计模式,访问容器,并没有暴露容器的底层结构,不关心底层细节,以统一的方式去进行访问,所以迭代器也是STL六大组件之一。
int a[] = {1,2,1,6,3,8,5};
set<int>s(a,a+sizeof(a)/sizeof(int));
//set<int>s{1,2,1,6,3,8,5}列表初始化,initializer_list
//1.while(it!=s.end())
set<int>::iterator it = s.begin();
while(it!=s.end())
{
...
}
//2.for循环,但是for底层是迭代器,所以严格来说算作一种
for(auto e:s)
{
...
}
set的升降序排序
因为set是Key的模型,底层是一颗搜索二叉树,所以它对应的搜索二叉树的中序遍历就差异达到排序+去重的效果
可以看出,默认是升序,那么我们想让他降序就需要传仿函数过去set<int,greater<int>> s;//第三个是空间配置器,这里可以不用管
set的erase
set的erase,因为set底层就是搜索二叉树,所以说白了搜索二叉树的删除就分为三种情况
1.删除结点只有一个孩子并且是根结点,那么根结点转移
2.删除结点只有一个孩子并且不是根,那么父节点领养
3.删除结点有两个孩子:需要使用替换法
搜索二叉树详解
但是set提供的就是:删除迭代器位置,删除值,删除迭代器区间
所以我们这时可以先去找到要删除的值或者位置–>find,一般来说都会配套使用
auto pos = s.find(x);
if(pos!=s.end()) s.erase(pos);
这时,又会有迭代器失效的问题,其实不管我们用什么容器,它的迭代器失效还是不失效,我们一律把它当做失效来处理,也就是说每一次的删除我们都要重新的去找,或者说有些容器的erase提供了下一个位置的迭代器,我们用pos来接收一下
//set:
auto pos = s.find(x);
if(pos!=s.end()) s.erase(pos);
pos = s.find(y);
if(pos!=s.end()) s.erase(pos);
//erase返回迭代器的容器
...
pos = v/*容器名*/.erase(pos);
set的count
set的count我们有时也用来看这个元素在不在set当中,但是count是用来数这个元素一共有几个,set本来就是搜索二叉树不允许键值冗余,那要他来干什么??其实是为了接口的一致性,像用哈希来实现的multiset就会允许键值冗余,会提供count接口
②map
SGI-STL中键值对的定义
template<class T1,class T2>
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)
{}
};
map的insert
但是呢,map不是插入普通的值,map是Key Value模型,底层是红黑树,需要插入的是pair这个结构体
map<string,string> dict;
//1
pair<string,string>kv1("sort","排序");
dict.insert(kv1);
//2
dict.insert(pair<string,string>("zhupi","小菜鸟"))
//3
typdef pair<string,string> DictKV;
dict.insert(DictKV("left","左"));
//4
//常用的make_pair,需要包含头文件 utility(实用工具)
//也有可能被间接包了,比如map,最好自己包一下
dict.insert(make_pair("string","字符串"));
make_pair是一个函数模板,优势就在于可以自动推导,因为较短,一般会被定义成内联,在预编译阶段就可替换
打印map中的元素
//错误写法
while(it!=dict.end())
{
cout<<*it<<" ";
++it;
}
cout<<endl;
因为map中存的是pair结构体,cout中并没有pair的重载,所以输出错误,从上面的键值对pair的定义中我们可以看出,pair有两个成员,first和second,所以:
cout<<(*it).first<<(*it).second<<endl;
*除此之外,之前在模拟实现的时候,会重载一个operator->,返回的是&(this),迭代器转换为地址,但在使用的时候实际上是it->->但是编译器为了可读性,优化成了->,所以我们还可以这样访问pair中的成员
//map的迭代器很喜欢用->
cout<<it->first<<it->second<<endl;
for(const auto & kv:dict)
{
cout<<kv.first<<":"<<kv.second<<endl;
}
经典Key/Value模型,水果数数
string arr[] = {"苹果","西瓜","苹果 ","西瓜","苹果","苹果","西瓜","苹果","香蕉","苹果","香蕉"};
map<string,int> countMap;
for(auto &str:arr)
{
auto it = count.find(str);
if(it!=countMap.end())
{
it->second++;
}
else
{
countMap.insert(make_pair(str,1));
//make_pair自动推导
}
auto it = countMap.begin();
while(it!=countMap.end())
{
cout<<it->first<<":"<<it->second<<endl;
it++;
}
}
map的operator[]
可以看出,上面这样操作很麻烦,map中还提供了更方便的方式去修改value,那就是operator[]
它并不是像数组那样的下标随机访问,map这样的树形结构,[]可以很容易的想到并不是下标
map的作用是,给一个key,返回value的引用
作用:
①map中有这个key,返回value的引用
②map中没有这个key,会插入一个pair(key,V());
因为这个value有可能是string或者说更复杂的自定义类型,需要调用构造函数来进行初始化
这样一来,水果数数就变得很方便
for(auto &str:arr)
{
countMap[str]++;
}
map的at
说到了operator[],那么就很容易联想到一起的at
at与operator[]来说就少了一个功能,at只能查找+修改,在就返回Value的引用,不在就抛异常
map的operator[]底层简单分析
先来看看insert的细节
insert返回的是一个pair<iterator,bool>
①没有该Key,就插入,并且返回哪个位置的迭代器,把second设为true
②有该Key,返回迭代器,把second设为false
第二个参数就代表插入是否成功
那么这下我们就可以很容易的写出map的operator[]了
V& operator[](const K&key)
{
pair<iterator,bool> ret = insert(make_pair(key,V()));
return ret.first->second;
}
//通过拿到迭代器,再用operator->->拿到second也就是value返回即可