1.序列性容器和关联性容器
- 我们之前已经学习了vector、list、string、dequeue、queue、等序列性容器,序列性容器,就是指元素之间没有关联性,也就是元素之间交换不影响容器。
- 而关联性容器,不同的是元素之间不能随意交换,像map、set这样的容器,要满足二叉搜索树的性质,所以元素之间不能随意交换,不然会破坏容器的逻辑结构。
2. set 系列的使用
2.1 set类的介绍
- set的声明如下,T就是set底层关键字的类型
- set默认要求T支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第二个模版参数
- set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。
- 一般情况下,我们都不需要传后两个模版参数。
- set底层是用红黑树实现,增删查效率是O(logN),迭代器遍历是走的搜索树的中序,所以是有序的。
2.2 set的构造和迭代器

set的支持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,set的iterator和const_iterator都不支持迭代器修改数据,因为修改关键字数据,会破坏底层搜索树的结构。
下面的代码使用了迭代器区间构造:
cpp
#include<iostream>
#include<set>
using namespace std;
int main()
{
int arr[] = { 1,2,3,4,5 };
set<int> test(arr, arr + 5);
for (auto& e : test)
{
cout << e << " ";
}
return 0;
}
2.3 set的增删查
以下几个接口比较重要:
cpp
1 Member types
2 key_type ->The first template parameter (T)
3 value_type ->The first template parameter (T)
4
5 // 单个数据插入,如果已经存在则插入失败
6 pair <iterator ,bool> insert (const value_type&val);
7 // 列表插入,已经在容器中存在的值不会插入
8 void insert (initializer_list<value_type> il);
9 // 迭代器区间插入,已经在容器中存在的值不会插入
10 template <class InputIterator>
11 void insert (InputIterator first , InputIterator last);
12
13 // 查找val,返回val所在的迭代器,没有找到返回end()
14 iterator find (const value_type&val);
15 // 查找val,返回Val的个数
16 size_type count (const value_type&val) const;
17 // 删除一个迭代器位置的值
18 iterator erase (const_iterator position);
19 // 删除val,val不存在返回0,存在返回1
20 size_type erase (const value_type&val);
21 // 删除一段迭代器区间的值
22 iterator erase (const_iterator first , const_iterator last);
23
24 // 返回大于等val位置的迭代器
25 iterator lower_bound (const value_type&val) const;
26 // 返回大于val位置的迭代器
27 iterator upper_bound (const value_type&val) const;
2.4 insert和迭代器遍历使用样例:
cpp
#include<iostream>
#include<set>
using namespace std;
int main()
{
int a[] = { 2,34,26,64,68 };
set<int> first(a,a+5);
first.insert(10);
first.insert(0);
first.insert(30);
first.insert(first.begin(), 5);
set<int>::iterator it;
it = first.find(30);
first.insert(it,-1);
first.erase(-1);
cout << first.size() << endl;
for (auto& e : first)
{
cout << e << " ";
}
cout << endl;
set<int>::iterator pnum;
for (pnum = first.begin(); pnum != first.end(); pnum++)
{
cout << *pnum << " ";
}
return 0;
}

2.5 find和erase使用样例:
erase的删除方式有两种,第一种是传值,这种效率比较低,因为它是从头开始找,相当于遍历一遍,第二种是传迭代器,这种效率很高,它直接指向了元素的位置,但是迭代器必须有效,否则崩溃。
cpp
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int>tmp = { 23,45,12,4,21 };
for (auto& e : tmp)
{
cout << e << " ";
}
cout << endl;
//删除方式1
tmp.erase(tmp.begin());
for (auto& e : tmp)
{
cout << e << " ";
}
cout << endl;
//删除方式2
int x = 0;
cin >> x;
int ret = tmp.erase(x);
if (ret == 0)
{
cout << x << "不存在!" << endl;
}
else
{
cout << "删除成功!" << endl;
}
//删除方式3
cin >> x;
set<int>::iterator it = tmp.find(x);//find的返回值是迭代器
tmp.erase(it);
for (auto& e : tmp)
{
cout << e << " ";
}
return 0;
}
2.6 multiset和set的差异
multiset和set的使用基本完全类似,主要区别点在于multiset支持值冗余,那么
insert/find/count/erase都围绕着支持值冗余有所差异。
简单来说就是set只支持有一个key,但是multiset支持多个key,也就是允许多相同的值。
3. map系列的使用
3.1 map类的介绍
map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key支持小于比较,如果不支持或者需要的话可以自行实现仿函数传给第二个模版参数,map底层存储数据的内存是从空间配置器申请的。一般情况下,我们都不需要传后两个模版参数。map底层是用红黑树实现,增删查改效率是 O(logN) ,迭代器遍历是走的中序,所以是按key有序顺序遍历的。

3.2 pair类型介绍
map底层的红黑树节点中的数据,使用pair<Key, T>存储键值对数据。

3.3 map的构造
map的构造我们关注以下几个接口即可。
map的支持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,map支持修改value数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的结构。
cpp
#include<iostream>
#include<string>
#include<map>
using namespace std;
int main()
{
//下标进行赋值操作
map<string, int> first;
first["abc"] = 1;
first["egf"] = 2;
first["hij"] = 3;
auto it = first.begin();
while (it != first.end())
{
cout << (*it).first << " " << (*it).second << endl;
it++;
}
//两种构造方式
map<string, int>second(first.begin(), first.end());
map<string, int>third(first);
auto it2 = second.begin();
while (it2 != second.end())
{
cout << (*it2).first << " " << (*it2).second << endl;
it2++;
}
auto it3 = third.begin();
while (it3 != third.end())
{
cout << it3->first << " " << it3->second << endl;
it3++;
}
return 0;
}

3.4 map的增删查
map的增删查关注以下几个接口即可:
map增接口,插入的pair键值对数据,跟set所有不同,但是查和删的接口只用关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value
注意:
- find的返回值是迭代器,如果找到了,就返回当前位置的迭代器,没有找到就返回end(),最后一个有效位置的下一个值。
- 迭代器指向的先是一个指针,这个指针指向了键值对pair
insert - 插入成功:返回pair<迭代器,布尔值>,这个迭代器是新插入值所在的迭代器,并且这个迭代器指向了map里存的键值对(pair),
- 插入失败:返回pair<已经存在的跟key相等值的迭代器,false>,此时的插入相当于查找的功能。
cpp
int main()
{
map<string, int>test;
test.insert(pair<string, int>("张三", 1));
test.insert(pair<string, int>("李四", 2));
test.insert(pair<string, int>("王五", 3));
test.insert(pair<string, int>("老六", 4));
auto it = test.begin();
while (it != test.end())
{
cout << it->first << " " << it->second << endl;
it++;
}
cout << endl;
//迭代器删除
map<string, int>::iterator clear = test.end();
clear--;
test.erase(clear);
test.erase(test.begin());
/*auto find_it = test.find("李四");
if (find_it != test.end())
{
test.erase(find_it);
}*/
//传值删除
test.erase("王五");
auto e = test.begin();
while (e != test.end())
{
cout << e->first << " " << e->second << endl;
e++;
}
return 0;
}

3.5 map的修改数据
注意 :
map支持修改,但是只能修改value,不能直接修改key。这里提供了两种修改方式,第一种是用迭代器去找到并修改,第二种是使用[]去修改,map的[ ]功能比较强大,它可以修改也可以插入数据,当[ ]里的数据map里没有时,它是插入数据,存在时它是修改数据。
operator[ ]底层
- k存在时,插入+修改
- k不存在时,查找+修改
cpp
mapped_type operator[](const key_type&k)
{
pair<iterator,bool>ret =insert({k,mapped_type})
iterator it = ret.first;
return it -> second;
}
cpp
int main()
{
map<string, int>test;
test.insert(pair<string, int>("张三", 1));
test.insert(pair<string, int>("李四", 2));
test.insert(pair<string, int>("王五", 3));
test.insert(pair<string, int>("老六", 4));
map<string, int>::iterator it = test.begin();
it->second += 'x';
//it->first += 5;
test["李四"] = 88;
auto e = test.begin();
while (e != test.end())
{
cout << e->first << " " << e->second << endl;
e++;
}
return 0;
}

3.6 multimap和map的差异
multimap和map的使用基本完全类似,主要区别点在于multimap支持关键值key重复,那么insert/find/count/erase都围绕着支持关键值key冗余有所差异,这里跟set和multiset完全一样,比如find时,有多个key,返回中序第一个。其次就是multimap不支持[],因为支持key冗余,[]就只能支持插入了,不能支持修改。