自学内容网 自学内容网

探索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的构造和迭代器 

set的⽀持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是⼆叉搜索树,迭代器遍历⾛的中序;⽀持迭代器就意味着⽀持范围for,set的iterator和const_iterator都不⽀持迭代器修改数据,修改关键字数据,破坏了底层搜索树的结构。

 

// 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的差异 

multiset和set的使⽤基本完全类似,主要区别点在于multiset⽀持值冗余,那么
insert/find/count/erase都围绕着⽀持值冗余有所差异
#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类的介绍

map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key⽀持⼩于⽐较,如果不⽀持或者需要的话可以⾃⾏实现仿函数传给第⼆个模版参数,map底层存储数据的内存是从空间配置器申请的。⼀般情况下,我们都不需要传后两个模版参数。map底层是⽤红⿊树实现,增删查改效率是 O ( logN ) ,迭代器遍历是⾛的中序,所以是按key有序顺序遍历的。
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的构造我们关注以下⼏个接⼝即可。

map的⽀持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是红黑树(⼆叉搜索树一种),迭代器遍历⾛的中序;⽀持迭代器就意味着⽀持范围for,map⽀持修改value数据,不⽀持修改key数据,修改关键字数据,破坏了底层搜索树的结构。
// 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的增删查

map的增删查关注以下⼏个接⼝即可:
map增接⼝,插⼊的pair键值对数据,跟set所有不同,但是查和删的接⼝只⽤关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value

 

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 中的元素时,无论是通过迭代器直接访问还是通过 inserterasefind 等成员函数,你都是在处理 std::pair 类型的对象。

map⽀持修改mapped_type 数据,不⽀持修改key数据,修改关键字数据,破坏了底层搜
索树的结构。
map第⼀个⽀持修改的⽅式时通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map还有⼀个⾮常重要的修改接⼝operator[],但是operator[]不仅仅⽀持修改,还⽀持插⼊数据和查找数据,所以他是⼀个多功能复合接⼝

 

需要注意从内部实现⻆度,map这⾥把我们传统说的value值,给的是T类型,typedef为mapped_type。⽽value_type是红⿊树结点中存储的pair键值对值。⽇常使⽤我们还是习惯将这⾥的T映射值叫做value。
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-> 实现如下:

  1. 迭代器首先会解引用自己(即 *it),这会得到容器中的一个元素。
  2. 然后,operator-> 返回这个元素的指针。
  3. 在使用 -> 的时候,实际上是在返回的指针上再次使用 ->,从而访问元素的成员。

对于 std::map 的迭代器,*it 会得到一个 std::pair<const Key, Value> 类型的对象,然后 it->firstit->second 分别访问这个 pairfirstsecond 成员。

it.operator->() 是一个迭代器的成员函数,它返回一个指向迭代器所指向元素的指针。对于 std::map 的迭代器来说,这个元素是一个 std::pair<const Key, Value> 类型的对象,其中 Key 是键的类型,Value 是值的类型。

当你使用 -> 运算符时,它首先调用迭代器的 operator->() 函数来获取指向当前元素的指针,然后它就像使用指针一样通过箭头运算符访问成员。

cout << it.operator->()->first << ":" << it.operator->()->second << endl;

行代码做了以下几件事情:

  1. it.operator->():调用迭代器 it 的 operator->() 成员函数,返回一个指向 std::pair<const Key, Value> 的指针。
  2. ->first:使用箭头运算符访问指针指向的 std::pair 对象的 first 成员,即键。
  3. ->second:使用箭头运算符访问指针指向的 std::pair 对象的 second 成员,即值。

因此,it.operator->()->first 等价于 (*it).first,it->firstit.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的差异

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

 


原文地址:https://blog.csdn.net/2401_84107961/article/details/143869108

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!