Hellow! 铁子们,今天博主来介绍C++map容器的使用,这个容器灰常有用哦,学习C++铁子们一定要会使用。

map系列的使⽤

1 map和multimap参考⽂档

https://legacy.cplusplus.com/reference/map/

2 map类的介绍

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

3 pair类型介绍

map底层的红⿊树节点中的数据,使⽤pair<Key,T>存储键值对数据。

typedef pair<const Key, T> value_type;
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)
    {}
template<class U, class V>
pair (const pair<U,V>& pr): first(pr.first), second(pr.second)
{}
};

template <class T1,class T2>
inline pair<T1,T2> make_pair (T1 x, T2 y)
{
return ( pair<T1,T2>(x,y) );
}

4 map的构造

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

#include <iostream>
#include <map>

//函数实现仿函数的功能传入给map改变二叉树的插入规则
bool fncomp (char lhs, char rhs) {return lhs<rhs;}

struct classcomp {
  bool operator() (const char& lhs, const char& rhs) const
  {return lhs<rhs;}
};

int main ()
{
  std::map<char,int> first;

  first['a']=10;
  first['b']=30;
  first['c']=50;
  first['d']=70;

  std::map<char,int> second (first.begin(),first.end());

  std::map<char,int> third (second);

  //仿函数类型作为模板参数传入进去
  std::map<char,int,classcomp> fourth;                 // class as Compare

  bool(*fn_pt)(char,char) = fncomp;//函数指针
  std::map<char,int,bool(*)(char,char)> fifth (fn_pt); // function pointer as Compare

  return 0;
}

5 map之比较函数的自定义实现及注意点(重点)

关键点:

  • map容器内部需要保证比较操作的稳定性

  • 比较函数被声明为const后,编译器能确保该函数不会修改对象成员

  • STL容器要求比较函数是"纯函数"(不依赖外部状态且不产生副作用)

解决方案: 自定义的比较函数的operator()添加const修饰符,这是STL容器的通用要求,不仅适用于map,也适用于set等其他有序容器。

扩展说明: 这种设计是C++类型安全的一部分,确保容器在排序元素时不会意外改变比较器的状态,从而保证排序的一致性。

总得来说就是传入的比较函数不能够修改map中元素的值。什么你说你不知道仿函数,那去看看博主C++专栏里面有没有介绍(我记得是有的),没有的话可以在评论区留言哦。


//用函数实现比较函数的注意点:参数的类型必须为const 引用或者能够发生值拷贝的类型
// 比如const int& 虽然拿到的是原数据但是const修饰的话不能修改原数据
bool cmp(const int& Insert, const int& Cur)
{
    return Insert < Cur;
}
//这个发生的是值拷贝把原数据的值拷贝给形参,修改形参不影响原数据
bool cmp( int Insert, int Cur)
{
    return Insert < Cur;
}
//这个就会报错,引用拿到原数据没有const修饰可能会修改原数据
bool cmp( int& Insert, int& Cur)
{
    return Insert < Cur;
}
//上面的比较函数表示的插入逻辑为如果当前插入的元素的key小于当前比较的key就往左边寻找空位置插入
//这样key小于根节点的都在根节点左边中序遍历出来的key就是升序的,反之如果是>符好,那么就表示如果当前插入的元素的key大于当前比较的key就往左边寻找空位置插入,这样key大于根节点的都在根节点左边中序遍历出来的key就是降序的


//使用重载()实现比较函数的功能的注意点:
//1 使用类或者结构体实现重载()时,不要有任何的成员变量或者其它成员函数
//2 重载的成员函数必须是常量成员函数(后面加const修饰的)表示该函数不会修改对象
//3 除了必须是常量成员函数,参数条件也和函数实现比较函数一致遵循不能修改map的数据的原则
struct Cmp
{
    bool operator()(  int Insert,  int Cur)const 
    {
        return Insert > Cur;
    }
    
     bool operator()( const int& Insert, const  int& Cur)const 
    {
        return Insert > Cur;
    }
    //这种重载就会报错
     bool operator()(  int Insert,  int Cur)
    {
        return Insert > Cur;
    }
};
int main()
{
    bool(*f)(const int&  , const int& ) = cmp;
    //注意点:以函数作为仿函数时,模板参数要给这个函数指针的类型,然后构造map对象时
    //把这个函数的地址传入进去
    //  map<int, char,bool(*)(int,int)> Hash(cmp)
    
    //结构体或者类实现比较函数直接把类型传给模板参数就行了
    //map<int, char, Cmp> Hash;
    map<int, char, bool(*)(const int&, const int&)> Hash(cmp);
    Hash.insert({1,'a'});
    Hash.insert({ 2,'b' });
    Hash.insert({ 3,'c' });
    for (auto& kv : Hash)
    {
        std::cout << kv.first << " :" << kv.second << std::endl;
    }
    return 0;
}
库提供的仿函数--greater<T>,less<int>
map<int, char, std::greater<int>> Hash;
//greater<int>这个模板类修改的插入逻辑就是大于往左边找空位置插入
//中序遍历是降序
map<int, char, std::less<int>> Hash1;	
Hash.insert({1,'a'});
Hash.insert({ 2,'b' });
Hash.insert({ 3,'c' });
for (auto& kv : Hash)
{
    std::cout << kv.first << " :" << kv.second << std::endl;
}
return 0;

6 map的增删查

map增接⼝,插⼊的pair键值对数据,跟set所有不同,但是查和删的接⼝只⽤关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value。铁子们去上面提供的链接自行学习即可,锻炼一下只学能力,哪里看不懂可以评论区留言哦。

7 map的数据修改--重载[ ]--灰常方便

// accessing mapped values
#include <iostream>
#include <map>
#include <string>

int main ()
{
  std::map<char,std::string> mymap;
	
    //mymap['a']:首先先判断给定的key在map中存不存在,如果存在则返回key对应的value的引用
    //可以修改value的值,如果不存在则插入该给定的key,对应的value值为value类型的默认值,比如value是int,那么value值就是0,自定义类型调用默认构造出一个value值,然后返回该value的引用。
  mymap['a']="an element";
  mymap['b']="another element";
  mymap['c']=mymap['b'];

  std::cout << "mymap['a'] is " << mymap['a'] << '\n';
  std::cout << "mymap['b'] is " << mymap['b'] << '\n';
  std::cout << "mymap['c'] is " << mymap['c'] << '\n';
  std::cout << "mymap['d'] is " << mymap['d'] << '\n';

  std::cout << "mymap now contains " << mymap.size() << " elements.\n";

  return 0;
}

8 构造遍历及增删查使⽤样例--值得一看

#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;
}

9 map的迭代器和[]功能样例---可以看看

#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
 // 利⽤find和iterator修改功能,统计⽔果出现的次数
string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠",
"苹果", "⾹蕉", "苹果", "⾹蕉" };
map<string, int> countMap;
for (const auto& str : arr)
{
// 先查找⽔果在不在map中
// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 1}
// 2、在,则查找到的节点中⽔果对应的次数++
auto ret = countMap.find(str);
if (ret == countMap.end())
{
countMap.insert({ str, 1 });
}
else
{
ret->second++;
}
}
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()
{
// 利⽤[]插⼊+修改功能,巧妙实现统计⽔果出现的次数
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()
{
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;
}

10 multimap和map的差异

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

11 leetcode练习题

嘿嘿,,锻炼锻炼铁子们

https://leetcode.cn/problems/copy-list-with-random-pointer/description/

https://leetcode.cn/problems/top-k-frequent-words/

本期收录于博主的专栏——C++专栏,适用于编程初学者,感兴趣的朋友们可以订阅,其中本篇里面提及的范围for和列表初始化会在后面的文章里介绍,看不懂的铁子们可以先记住怎么使用即可,不懂的地方也可以评论区留言。

感谢每一个观看本篇文章的朋友,更多精彩敬请期待:小董 不懂  *( ̄▽ ̄)°★*

更多推荐