🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
文章目录
- 一、两个概念
- 二、set
- ①set的两种遍历方式
- ②set的erase
- ③set的count
- 三、map
- ①SGI-STL中关于键值对的定义
- ②map的insert
- ③访问键值对
- ④map的operator->
- ⑤map的operator[]
- ⑥map的at
一、两个概念
序列是容器:
在初阶阶段,已经接触STL的部分容器,vector,list,deque,forward_list等等,这些都哦是序列是容器,因为其底层为线性序列的数据结构,里面存的是元素本身,那么什么是关联式容器?
关联式容器:
也是用来存储数据的,与序列式容器不同的是,其里面村的是<Key,Value>这样结构的键值对,在数据检索时效率更高
二、set
①set的两种遍历方式
首先,set是Key的模型,不允许修改,所以set只有增删查
//初始化
set<int> s={1,2,1,6,3,8,5};
//可以这样进行初始化,这个是C++11中的列表初始化
//initializer_list
int a[] = (1,2,1,6,3,8,5);
set<int>s(a,a+sizeof(a)/sizeof(int));
//第一种
set<int>::iterator it = s.begin();
while(it!=s.end())
{
cout<<*it;
it++;
}
//第二种
for(auto k:s)
{
cout<<k;
}
但是范围for是用迭代器实现的,所以这严格来说只能算是一种遍历方式
set的地曾是一可红黑树(红黑树也是搜索二叉树),set间接可以完成的任务就是排序+去中,然后真正干的事就是去找在不在
迭代器也是一种设计模式,访问容器,在没有暴露容器的底层结构的前提下提供了统一的访问方式,哪怕他是红黑树,所以迭代器是很牛的,这也让他成为了STL六大组件之一
②set的erase
//1.
s.erase(x);//某个数
//2.
auto pos = s.find(x);//找到某个数位置
s.erase(pos);
需要注意的是,我们在实际使用过程当中,我们需要把所有容器的erase都当作迭代器已经失效来看待,每一次的erase都要重新去更新迭代器的位置。避免迭代器失效而奔溃
//1.erase提供下一个位置迭代器,直接用
while(...)
{
pos = s.erase(x);
}
//2.不断的find
pos = s.find(x);
s.erase(pos);
pos = s.find(y);
...
③set的count
count的作用是计数set当中的该键值个数,但是搜索二叉树/红黑树本来就不允许键值冗余,set当中留一个count来干嘛,结果不都是0/1吗,我为什么不用find?
因为count是为了接口一致性的问题,除了set外,还有一个multimap,它是允许键值冗余的
三、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需要插入的是一个pair结构体,底层也是红黑树
//插入方法
map<string,string> dict;
//1.
pair<string,string> kv1("sort","排序");
dict.insert(kv1);
//2.
dict.insert(pair<string,string>("zhupi","帅哥"));
//3.
typedef pair<string,string> DictKV;
dict.insert(DictKV("zhupi","帅哥"));
//4.make_pair
dict.insert(make_pair("zhupi","帅哥"));
//5.列表初始化
dict.insert({"zhupi","帅哥"});
而对于make_pair,其实就是一个函数模板,优势就是自动推导,需要包含头文件utility,实用工具
而且呢,这种短代码一般定义成内联,在预编译阶段就会被替换
③访问键值对
//map<string,string>::iterator it = dict.begin();
auto it = dict.begin();
//错误写法
while(it!=dict.end())
{
cout<<*it<<" ";
++it;
}
cout<<endl;
上面这样写是不行的,通过上面键值对pair的定义我们可以看出,他是一个结构体,但是ostream类中并没有重载关于pair<string,string>的重载。所以我们需要通过以下方式进行访问
//1.
while(it!=dict.end())
{
cout<<(*it).first<<(*it).second<<endl;
++it;
}
//2.先看下面的operator->
while(it!=dict.end())
{
cout<< it -> first << it -> second <<endl;
++it;
}
④map的operator->
Ptr/*模板参数*/ operator->()
{
return &(*this);
}
这一步的操作是为了从迭代器转换为单纯的指针,因为迭代器是一层封装,我们无法拿到底层的结。
而当我们使用it->first的时候,它其实是it->->first,第一个代表转换为纯指针,第二个才代表拿到pair中的first,只不过编译器为了可读性把他弄成一个
⑤map的operator[]
map的[]已经有点违背之前使用string和vector的伊斯兰,之前的目的的下标随机访问,得益于底层空间的连续,但是map是红黑树
operator[]的作用是通过Key找到对应的Value
1.map中没有这个key,则插入一个pair<Key,V()>,后面的是value的匿名对象,因为V可能是自定义类型,所以int也有了int(),默认是0.插入之后,返回Value的引用
2.map中有这个key,返回value的引用
我们来看看库中的写法
//已简化
V& operator[](const K& key)
{
pair<iterator,bool> ret=insert(make_pair(Key,Value));
//因为insert返回的是一个pair<iterator,bool>;
//insert返回的是一个pair<iterator,bool>
//第一个参数的second就是value,
//第二个参数代表是新插入true还是本来就在false
return ret.first->second;
//ret是一个pair结构体,ret.first就是迭代器,然后(*(ret.first)).second或者(ret.first)->second
}
⑥map的at
at与operator[]来说,就少了一个功能
at只能够查找+修改
也就是说,如果key在,那么返回value的引用
如果不再,那么我就抛异常