【C++】--- STL之 set 和 map
一、关联式容器和键值对
1.关联式容器
(1)序列式容器:因为底层的数据结构是线性的,所以称它为序列式容器,比如说vector list deque 等等…
(2)关联式容器:也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。
2.键值对
什么叫做键值对???
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。
库中对 键值对的定义·:pair< first , second > (first = key second = value)
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)
{}
};
二、set
1.set特点
- set是按照一定次序存储元素的容器
- 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
- 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
- set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
- set在底层是用二叉搜索树(红黑树)实现的。
2.set类
(1) set类对象构造
template < class T,
// set::key_type/value_type 元素的类型
class Compare = less<T>,
// set::key_compare/value_compare 比较方式,默认小于
class Alloc = allocator<T>
// set::allocator_type 使用STL提供的空间配置器管理方式
> class set;
//构造set
explicit set (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
template <class InputIterator>
//使用迭代器区间构造set
set (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
//拷贝构造set
set (const set& x);
(2)insert插入
(1)创建set对象:
#include<iostream>
#include<set>
using namespace std;
void test_set1()
{
set<int> s;// 构造一个set对象
set<int> s1(s.begin(), s.end());// 迭代器区间构造
set<int> s2(s1);// 拷贝构造
}
(2)插入insert:
s.insert(1);
s.insert(1);
s.insert(9);
s.insert(3);
s.insert(7);
s.insert(5);
set的作用:排序+去重
(3)set遍历
(1)迭代器区间遍历:
// 遍历
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
(2)范围for:
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
(4)find查找
// 查找
set<int>::iterator it1 = s.find(9);
if (it1 != s.end())
{
cout << *it1 << endl;
}
else
{
cout << "找不到" << endl;
}
(5)erase删除
void erase (iterator position);//删除指定位置元素
size_type erase (const value_type& val);//从set中删除指定的值
void erase (iterator first, iterator last);//删除指定区间
(1)删除指定位置元素:
删除元素5:
set<int> s;
s.insert(6);
s.insert(2);
s.insert(5);
s.insert(8);
s.insert(5);
set<int>::iterator ret = s.find(5);
if (ret != s.end())
{
s.erase(ret);
}
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
(2)删除指定值元素
删除值为8的元素:
set<int> s;
s.insert(6);
s.insert(2);
s.insert(5);
s.insert(8);
s.insert(5);
//删除值为8的元素
s.erase(8);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
(3)删除指定区间
删除5之前的元素:
set<int> s;
s.insert(6);
s.insert(2);
s.insert(5);
s.insert(8);
s.insert(5);
//删除5之前的元素
set<int>::iterator ret = s.find(5);
s.erase(s.begin(), ret);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
(6)计数count( )
set<int> s;// 构造一个set对象
set<int> s1(s.begin(), s.end());// 迭代器区间构造
set<int> s2(s1);// 拷贝构造
s.insert(1);
s.insert(1);
s.insert(9);
s.insert(3);
s.insert(7);
s.insert(5);
cout << s.count(1) << endl;
cout << s.count(1) << endl;
计算set里面传入参数元素的个数,因为他不允许重复出现,所以说只有一个。
(7)交换swap( )
set<int> s;// 构造一个set对象
s.insert(1);
s.insert(1);
s.insert(9);
s.insert(3);
s.insert(7);
s.insert(5);
set<int> s1;
s1.insert(2);
s1.insert(4);
s1.insert(6);
s1.insert(8);
s1.insert(10);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
swap(s, s1);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
(8)清空所有元素clear( )
s.clear();
cout << "clear后的s" << endl;
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
(9)求set的元素个数size( )
cout << s1.size() << endl;
三、map
1.map特点
- map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
- 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:
typedef pair<const key, T> value_type;
- 在内部,map中的元素总是按照键值key进行比较排序的。
- map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
- map支持下标访问符,即在[ ]中放入key,就可以找到与key对应的value。
- map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。
2.map类
(1)map类的构造
template < class Key, // map::key_type,Key的类型
class T, // map::mapped_type,value的类型
class Compare = less<Key>, // map::key_compare,比较器类型,默认小于,自定义类型需写函数指针或仿函数
class Alloc = allocator<pair<const Key,T> > // map::allocator_type 空间配置器
> class map;
//构造map
explicit map (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
使用迭代器区间构造map
template <class InputIterator>
map (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
//拷贝构造map
map (const map& x);
(2)插入insert( )
map的插入函数的函数原型如下:
pair<iterator,bool> insert (const value_type& val);
insert函数的参数显示是value_type类型的,实际上value_type就是pair类型的别名
typedef pair<const Key, T> value_type;
下面介绍了4种插入方法,其中第4种就是多参数的构造函数支持隐式类型转换!是最常用的,也是最简洁的!!!
void test_map()
{
map<string,string> dict;
pair<string, string> kv1("string","字符串");
dict.insert(kv1);// 1、插入有名对象
dict.insert(pair<string, string>("right", "右边"));// 2、插入匿名对象
dict.insert(make_pair("test", "测试"));// 3、调用函数模版
dict.insert({ "left","左边" });// 4、多参数的构造函数,支持隐式类型转换(C++11 常用!!!)
//
}
int main()
{
//test_set1();
test_map();
return 0;
}
(3)map遍历
(1)迭代器遍历:
// 遍历:
map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
cout << it->first << ":" << it->second << endl;
it++;
}
cout << endl;
这里遍历的顺序是按照key的字典序来进行遍历的!
(2)范围for遍历:
for (auto e : dict)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
(4)删除erase( )
也就是说,我们既可以根据key值删除指定元素,也可以根据迭代器删除指定元素,若是根据key值进行删除,则返回实际删除的元素个数。
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
map<int, string> m;
m.insert(make_pair(2, "two"));
m.insert(make_pair(1, "one"));
m.insert(make_pair(3, "three"));
//方式一:根据key值进行删除
m.erase(3);
//方式二:根据迭代器进行删除
map<int, string>::iterator pos = m.find(2);
if (pos != m.end())
{
m.erase(pos);
}
return 0;
}
(5)查找find()
auto it = dict.find("string");
if (it != dict.end())
// 如果找到了就会返回他的迭代器,如果没找到就会返回这个map对象的end。
{
cout << it->second << endl;
}
else
{
cout << "找不到" << endl;
}
auto it1 = dict.find("str");
if (it1 != dict.end())
// 如果找到了就会返回他的迭代器,如果没找到就会返回这个map对象的end。
{
cout << it1->second << endl;
}
else
{
cout << "找不到" << endl;
}
(6)operator[ ](重要)
map的[ ]运算符重载函数的函数原型如下:
mapped_type& operator[] (const key_type& k);
[ ]运算符重载函数的参数就是一个key值,而这个函数的返回值如下:
(*((this->insert(make_pair(k, mapped_type()))).first)).second
就这样看着不太好理解,我们整理一下,实际上[ ]运算符重载实现的逻辑实际上就是以下三个步骤:
- 调用insert函数插入键值对。
- 拿出从insert函数获取到的迭代器。
- 返回该迭代器位置元素的值value。
总之一句话总结:传参传的是k(调用insert函数把参数key插入),然后返回的是:value&
传参传的是:key
返回的是:value&
不管插入成功还是失败,pair中的iterator始终指向key所在节点的iterator。
如果插入失败,那么插入(insert)就相当于查找(find)
这个函数操作应用在: 计数 上极大缩短了代码量:
// 统计次数
void test_map2()
{
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
map<string, int> countMap;
法1:
//for (auto& e : arr)
//{
// // 这里的map对象:countMap是 一边遍历,一边插入形成二叉搜索树!
// auto it = countMap.find(e);
// if (it != countMap.end())
// 如果在二叉搜索树里面没有找到 就会返回countMap的end()
// {
// it->second++; // 找到了,second++
// }
// else
// {
// countMap.insert({ e,1 }); // 没有找到就初始化 1
// }
//}
// 法2:operator[]的运用!(传参传的是key,返回的是val&)
for (auto& e : arr)
{
countMap[e]++;
}
四、multiset 和 multimap
multimap容器与map容器的底层实现一样,也都是平衡搜索树(红黑树),其次,multimap容器和map容器所提供的成员函数的接口都是基本一致的,这里也就不再列举了,multimap容器和map容器的区别与multiset容器和set容器的区别一样,multimap允许键值冗余,即multimap、multiset 容器当中存储的元素是可以重复的。
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
multimap<int, string> mm;
//插入元素(允许重复)
mm.insert(make_pair(2, "two"));
mm.insert(make_pair(2, "double"));
mm.insert(make_pair(1, "one"));
mm.insert(make_pair(3, "three"));
for (auto e : mm)
{
cout << "<" << e.first << "," << e.second << ">" << " ";
}
cout << endl; //<1,one> <2,two> <2,double> <3,three>
return 0;
}
其次,由于multimap容器允许键值冗余,调用[ ]运算符重载函数时,应该返回键值为key的哪一个元素的value的引用存在歧义,因此在multimap容器当中没有实现[ ]运算符重载函数。
好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!