探索C++中的map和set容器
1.set类的介绍
- set的声明如下,T就是set底层关键字的类型
- set默认要求T⽀持⼩于⽐较,如果不⽀持或者想按⾃⼰的需求⾛可以⾃⾏实现仿函数传给第⼆个模版参数
- set底层存储数据的内存是从空间配置器申请的,如果需要可以⾃⼰实现内存池,传给第三个参数。
- ⼀般情况下,我们都不需要传后两个模版参数。
- set底层是⽤红⿊树实现,增删查效率是O(logN) ,迭代器遍历是⾛的搜索树的中序,所以是有序的。
template < class T, // set::key_type/value_type
class Compare = less<T>, // set::key_compare/value_compare
class Alloc = allocator<T> // set::allocator_type
> class set;
1.1set的构造和迭代器
// empty (1) ⽆参默认构造
explicit set(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
set(InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type & = allocator_type());
// copy (3) 拷⻉构造
set(const set& x);
// initializer list (5) initializer 列表构造
set(initializer_list<value_type> il,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// 迭代器是⼀个双向迭代器
iterator->a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();
1.2set的增删查
set的增删查关注以下⼏个接⼝即可:
Member types
key_type -> The first template parameter (T)
value_type -> The first template parameter (T)
// 单个数据插⼊,如果已经存在则插⼊失败
pair<iterator,bool> insert (const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find (const value_type& val);
// 查找val,返回Val的个数
size_type count (const value_type& val) const;
// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);
// 删除val,val不存在返回0,存在返回1
size_type erase (const value_type& val);
// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回⼤于等val位置的迭代器
iterator lower_bound (const value_type& val) const;
// 返回⼤于val位置的迭代器
iterator upper_bound (const value_type& val) const;
1.3insert和迭代器遍历使用样例:
#include<iostream>
#include<set>
using namespace std;
int main()
{
// 去重+升序排序
set<int> s;
// 去重+降序排序(给⼀个⼤于的仿函数)
//set<int, greater<int>> s;
s.insert(5);
s.insert(2);
s.insert(7);
s.insert(5);
//set<int>::iterator it = s.begin();
auto it = s.begin();
while (it != s.end())
{
// error C3892: “it”: 不能给常量赋值
// *it = 1;
cout << *it << " ";
++it;
}
cout << endl;
// 插⼊⼀段initializer_list列表值,已经存在的值插⼊失败
s.insert({ 2,8,3,9 });
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
set<string> strset = { "sort", "insert", "add" };
// 遍历string⽐较ascll码⼤⼩顺序遍历的
for (auto& e : strset)
{
cout << e << " ";
}
cout << endl;
}
1.4find和erase使用样例:
#include<iostream>
#include<set>
using namespace std;
int main()
{
set<int> s = { 4,2,7,2,8,5,9 };
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 删除最⼩值
s.erase(s.begin());
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 直接删除x
int x;
cin >> x;
int num = s.erase(x);
if (num == 0)
{
cout << x << "不存在!" << endl;
}
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 直接查找在利⽤迭代器删除x
cin >> x;
auto pos = s.find(x);
if (pos != s.end())
{
s.erase(pos);
}
else
{
cout << x << "不存在!" << endl;
}
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
// 算法库的查找 O(N)
auto pos1 = find(s.begin(), s.end(), x);
// set⾃⾝实现的查找 O(logN)
auto pos2 = s.find(x);
// 利⽤count间接实现快速查找
cin >> x;
if (s.count(x))
{
cout << x << "在!" << endl;
}
else
{
cout << x << "不存在!" << endl;
}
return 0;
}
#include<iostream>
#include<set>
using namespace std;
int main()
{
std::set<int> myset;
for (int i = 1; i < 10; i++)
myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
for (auto e : myset)
{
cout << e << " ";
}
cout << endl;
// 实现查找到的[itlow,itup)包含[30, 60]区间
// 返回 >= 30
auto itlow = myset.lower_bound(30);
// 返回 > 60
auto itup = myset.upper_bound(60);
// 删除这段区间的值
myset.erase(itlow, itup);
for (auto e : myset)
{
cout << e << " ";
}
cout << endl;
return 0;
}
1.5multiset和set的差异
#include<iostream>
#include<set>
using namespace std;
int main()
{
// 相⽐set不同的是,multiset是排序,但是不去重
multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// 相⽐set不同的是,x可能会存在多个,find查找中序的第⼀个
int x;
cin >> x;
auto pos = s.find(x);
while (pos != s.end() && *pos == x)
{
cout << *pos << " ";
++pos;
}
cout << endl;
// 相⽐set不同的是,count会返回x的实际个数
cout << s.count(x) << endl;
// 相⽐set不同的是,erase给值时会删除所有的x
s.erase(x);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}
2.map类的介绍
template < class Key, // map::key_type
class T, // map::mapped_type
class Compare = less<Key>, // map::key_compare
class Alloc = allocator<pair<const Key,T> > //map::allocator_type
> class map;
2.1pair类型介绍
在C++标准模板库(STL)中,pair<iterator, bool>是一种常用于表示两个相关值的模板类。这种类型的pair通常用作函数的返回类型,用来同时返回一个迭代器和一个布尔值。这种返回类型在容器操作中尤其常见,比如在std::map、std::set、std::unordered_map和std::unordered_set等容器的insert和emplace成员函数中。
pair<iterator, bool>的两个元素分别是:
first:一个迭代器(iterator),指向容器中插入或找到的元素。如果插入成功,迭代器指向新插入的元素;如果插入失败(通常是因为元素已存在),迭代器指向已存在的元素。
second:一个布尔值(bool),表示插入操作是否成功。如果true,则表示元素被成功插入;如果false,则表示插入操作没有进行,通常是因为要插入的元素已经存在于容器中。
以下是pair<iterator, bool>在std::map或std::set中insert函数的一个使用示例:
#include <iostream>
#include <set>
int main() {
std::set<int> mySet;
std::pair<std::set<int>::iterator, bool> result;
// 尝试插入元素
result = mySet.insert(10);
if (result.second) {
std::cout << "Element inserted." << std::endl;
} else {
std::cout << "Element already exists." << std::endl;
}
return 0;
}
在这个例子中,insert函数尝试将元素10插入到mySet中。如果插入成功,result.second将是true,并且result.first将指向mySet中新插入的元素。如果10已经存在于mySet中,result.second将是false,并且result.first将指向已存在的10。
2.2map的构造
map的构造我们关注以下⼏个接⼝即可。
// empty (1) ⽆参默认构造
explicit map (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
map (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type());
// copy (3) 拷⻉构造
map (const map& x);
// initializer list (5) initializer 列表构造
map (initializer_list<value_type> il,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// 迭代器是⼀个双向迭代器
iterator -> a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();
2.3map的增删查
Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>
// 单个数据插⼊,如果已经key存在则插⼊失败,key存在相等value不相等也会插⼊失败
pair<iterator,bool> insert (const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find (const key_type& k);
// 查找k,返回k的个数
size_type count (const key_type& k) const;
// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);
// 删除k,k存在返回0,存在返回1
size_type erase (const key_type& k);
// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回⼤于等k位置的迭代器
iterator lower_bound (const key_type& k);
// 返回⼤于k位置的迭代器
const_iterator lower_bound (const key_type& k) const;
2.4map的数据修改
在C++中,
std::map
是一个基于红黑树实现的有序关联容器,它存储的是键值对(key-value pairs)。每个键值对都是一个std::pair
对象,其中first
成员是键(Key
),second
成员是值(Value
)。因此,当你访问std::map
中的元素时,无论是通过迭代器直接访问还是通过insert
、erase
或find
等成员函数,你都是在处理std::pair
类型的对象。
Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>
// 查找k,返回k所在的迭代器,没有找到返回end(),如果找到了通过iterator可以修改key对应的
mapped_type值
iterator find (const key_type& k);
// ⽂档中对insert返回值的说明
// The single element versions (1) return a pair, with its member pair::first
// set to an iterator pointing to either the newly inserted element or to the
// element with an equivalent key in the map. The pair::second element in the pair
// is set to true if a new element was inserted or false if an equivalent key
// already existed.
// insert插⼊⼀个pair<key, T>对象
// 1、如果key已经在map中,插⼊失败,则返回⼀个pair<iterator,bool>对象,返回pair对象
first是key所在结点的迭代器,second是false
// 2、如果key不在在map中,插⼊成功,则返回⼀个pair<iterator,bool>对象,返回pair对象
first是新插⼊key所在结点的迭代器,second是true
// 也就是说⽆论插⼊成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭
代器
// 那么也就意味着insert插⼊失败时充当了查找的功能,正是因为这⼀点,insert可以⽤来实现
// operator[]
// 需要注意的是这⾥有两个pair,不要混淆了,⼀个是map底层红⿊树节点中存的pair<key, T>,另
// ⼀个是insert返回值pair<iterator,bool>
pair<iterator,bool> insert (const value_type& val);
mapped_type& operator[] (const key_type& k);
// operator的内部实现
mapped_type& operator[] (const key_type& k)
{
// 1、如果k不在map中,insert会插⼊k和mapped_type默认值,同时[]返回结点中存储
mapped_type值的引⽤,那么我们可以通过引⽤修改返映射值。所以[]具备了插⼊+修改功能
// 2、如果k在map中,insert会插⼊失败,但是insert返回pair对象的first是指向key结点的
迭代器,返回值同时[]返回结点中存储mapped_type值的引⽤,所以[]具备了查找+修改的功能
pair<iterator, bool> ret = insert({ k, mapped_type() });
iterator it = ret.first;
return it->second;
}
2.5构造遍历及增删查使用样例
#include<iostream>
#include<map>
using namespace std;
int main()
{
// initializer_list构造及迭代遍历
map<string, string> dict = { {"left", "左边"}, {"right", "右边"},
{"insert", "插⼊"},{ "string", "字符串" } };
//map<string, string>::iterator it = dict.begin();
auto it = dict.begin();
while (it != dict.end())
{
//cout << (*it).first <<":"<<(*it).second << endl;
// map的迭代基本都使⽤operator->,这⾥省略了⼀个->
// 第⼀个->是迭代器运算符重载,返回pair*,第⼆个箭头是结构指针解引⽤取pair数据
//cout << it.operator->()->first << ":" << it.operator->()-> second << endl;
cout << it->first << ":" << it->second << endl;
++it;
}
cout << endl;
// insert插⼊pair对象的4种⽅式,对⽐之下,最后⼀种最⽅便
pair<string, string> kv1("first", "第⼀个");
dict.insert(kv1);
dict.insert(pair<string, string>("second", "第⼆个"));
dict.insert(make_pair("sort", "排序"));
dict.insert({ "auto", "⾃动的" });
// "left"已经存在,插⼊失败
dict.insert({ "left", "左边,剩余" });
// 范围for遍历
for (const auto& e : dict)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
string str;
while (cin >> str)
{
auto ret = dict.find(str);
if (ret != dict.end())
{
cout << "->" << ret->second << endl;
}
else
{
cout << "⽆此单词,请重新输⼊" << endl;
}
}
// erase等接⼝跟set完全类似
return 0;
}
it->
这种用法之所以能够工作,并不是因为it
本身是一个指针,而是因为迭代器重载了operator->
。这个操作符的重载允许迭代器表现得像指针一样,即使它实际上并不是一个指针。迭代器的
operator->
实现如下:
- 迭代器首先会解引用自己(即
*it
),这会得到容器中的一个元素。- 然后,
operator->
返回这个元素的指针。- 在使用
->
的时候,实际上是在返回的指针上再次使用->
,从而访问元素的成员。对于
std::map
的迭代器,*it
会得到一个std::pair<const Key, Value>
类型的对象,然后it->first
和it->second
分别访问这个pair
的first
和second
成员。
it.operator->()
是一个迭代器的成员函数,它返回一个指向迭代器所指向元素的指针。对于std::map
的迭代器来说,这个元素是一个std::pair<const Key, Value>
类型的对象,其中Key
是键的类型,Value
是值的类型。当你使用
->
运算符时,它首先调用迭代器的operator->()
函数来获取指向当前元素的指针,然后它就像使用指针一样通过箭头运算符访问成员。cout << it.operator->()->first << ":" << it.operator->()->second << endl;
行代码做了以下几件事情:
it.operator->()
:调用迭代器it
的operator->()
成员函数,返回一个指向std::pair<const Key, Value>
的指针。->first
:使用箭头运算符访问指针指向的std::pair
对象的first
成员,即键。->second
:使用箭头运算符访问指针指向的std::pair
对象的second
成员,即值。因此,
it.operator->()->first
等价于(*it).first,it->first
,it.operator->()->second
等价于(*it).second,it->second
。这两者都是访问迭代器指向的键值对中的键和值。
2.6map的迭代器和[]功能样例:
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
// 利⽤[]插⼊+修改功能,巧妙实现统计⽔果出现的次数
string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠",
"苹果", "⾹蕉", "苹果", "⾹蕉" };
map<string, int> countMap;
for (const auto& str : arr)
{
// []先查找⽔果在不在map中
// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 0},同时返回次数的引⽤,++⼀下就变成1次了
// 2、在,则返回⽔果对应的次数++
countMap[str]++;
}
for (const auto& e : countMap)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
return 0;
}
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
// 利⽤find和iterator修改功能,统计⽔果出现的次数
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
map<string, int>countmap;
for (auto& str : arr)
{
// 先查找⽔果在不在map中
// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 1}
// 2、在,则查找到的节点中⽔果对应的次数++
auto ret = countmap.find(str);
if (ret == countmap.end())
{
countmap.insert({ str,1 });
}
else
{
ret->second++;
}
}
for (auto& e : countmap)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
return 0;
}
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<string, string> dict;
dict.insert(make_pair("sort", "排序"));
// key不存在->插⼊ {"insert", string()}
dict["insert"];
// 插⼊+修改
dict["left"] = "左边";
// 修改
dict["left"] = "左边、剩余";
// key存在->查找
cout << dict["left"] << endl;
return 0;
}
2.7multimap和map的差异