【C/C++】map和unordered_map容器的使用总结

1,849 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情


map 容器

map 是STL的一个关联容器,可以在 map 中存储一对一的映射,一般通过键 key(也叫关键字),寻找对应的值 value(也叫关键字的值),也就是键值对(键 key - 值 value,代码为:map[key] = value)。

特点

  • 对应的数据结构是 红黑树 。红黑树是一种近似于平衡的 二叉搜索树(二叉查找树),里面的数据是有序的。在红黑树上做查找、插入、删除操作的 时间复杂度 为:O(log2N)O(log_2N)
  • 有序性map 中元素的有序性在使用上会简化很多操作。

使用方法

  • 头文件:#include<map> ,使用 map 需要包含该头文件。

  • 声明方式:map<int, int> mp; ,表示关键字为 int 类型,关键字所对应的值为 int 类型,对象变量名为 mp。其中关键字和值的类型还可以是其他类型,如:double、string、... 等。变量名为用户自定义取名,必须符合变量名要求。

  • 遍历 map 的迭代器声明:map<int, string>::iterator iter;auto iter,表示声明了对应关键字和值相同类型的 map 迭代器 iter,用于遍历 map 容器中的所有元素。

    1. 使用 map 迭代器声明方式声明 map<int, string>::iterator iter;
    //正向迭代器
    map<int, string>::iterator iter; 
    for(iter = mp.begin(); iter != mp.end(); iter++) {
        cout << iter->first << " " << iter->second << endl;
    }
    //反向迭代器(即正向迭代器的反向输出)
    map<int, int>::reverse_iterator iter;
    for(iter = mp.rbegin(); iter != mp.rend(); iter++){
        printf("%d  %d\n", iter->first, iter->second);
    }
    

    需要注意的是 map 是双向迭代器,只能进行 ++iteriter++--iteriter--*iter 操作,并且迭代器之间只能使用 == 或者 != 运算符进行比较。

    1. 使用 auto 方式声明(注 autoC++11 的新特性)
    //auto会自动识别mp.begin()的类型值(map<int, string>::iterator)
    for(auto iter = mp.begin(); iter != mp.end(); iter++) {
        cout << iter->first << "  " << iter->second << endl;
    }
    
    for(auto iter = mp.rbegin(); iter != mp.rend(); iter++) {
        printf("%d  %d\n", iter->first, iter->second);
    }
    
    

    auto 会根据后面值的类型,来确定前面变量的类型,简化了变量的声明。auto 声明的变量必须初始化,否则报错。

  • 通过迭代器访问容器中的元素:

    1. iter->first :访问当前迭代器指向元素的关键字 key
    2. iter->second :访问当前迭代器指向元素的值 value
  • map 的常用函数方法:

函数方法功能用法
clear()初始化清空 map 容器 mp.clear()
begin()返回指向 map 容器中(按照关键字排好序的)第一个元素的双向迭代器iter = mp.begin()
end()返回指向 map 容器中(按照关键字排好序的)最后一个元素的双向迭代器iter != mp.end()
rbegin()返回指向 map 容器中(按照关键字排好序的)最后一个元素的反向迭代器iter = mp.rbegin()
rend()返回指向 map 容器中(按照关键字排好序的)第一个元素的反向迭代器iter != mp.rend()
find(key)map 容器中查找关键字为 key 的键值对,找到返回指向该键值对的双向迭代器,否则返回 end()mp.find(key) != mp.end()
count(key)返回 map 容器中 key 出现的次数(由于 map 中关键字唯一的特性,所以只会返回 01 )表示是否存在 keymp.count(key)
erase(key)删除 map 容器中关键字为 key 的键值对mp.erase(key)
size()返回 map 容器中键值对的个数mp.size()
empty()判断 map 容器是否为空,如果为空则返回 truemp.empty()

unordered_map容器

unordered_map 容器也是存储一对一的键值对映射,unordered_mapmap 容器在功能上都是大致差不多的,只是在存储的数据结构上有所差异,所以这也导致了他们在内存和执行速度方面有所差异。在命名上 unordered 也表示无序的意思。

特点

  • unordered_map内部实现了一个哈希表 (也叫散列表),通过把关键字映射到 Hash 表中一个位置来访问记录,查找、插入、删除操作的 时间复杂度 可达到带常数级 O(1)O(1)。因此,其元素的排列顺序都是无序的。
  • unordered_map 容器中的元素是 无序的

使用方法

  • 头文件:#include<unordered_map> 在使用方面将 map 换成 unordered_map 即可,如声明方式:unordered_map<int, int> mp;

需要注意的是 unordered_map 没有反向迭代器和对应的函数方法。

解决 unordered_map 编译错误问题

GVim 上使用 g++ 编译 C++ 程序时会出现报错,如下:

image.png

通过报错信息我们得知主要是因为这句话:This file requires compiler and library support for the ISO C++ 2011 standard. This support is currently experimental, and must be enabled with the -std=c++11 or -std=gnu++11 compiler options.

翻译过来就是:该文件需要编译器和库支持ISO c++ 2011标准。这种支持目前是实验性的,必须使用-std=c++11或-std=gnu++11编译器选项来启用。

这时我们去查看GVim的配置文件 _vimrc 中的编译操作:nmap<F9> : !g++ % -o %< <CR>

我们在g++指令后添加 -std=c++11 即可,一定要注意前后的空格,因为空格敏感问题:

nmap<F9> : !g++ -std=c++11 % -o %< <CR>

保存配置文件 _vimrc 后重启 GVim 再次编译即可: image.png

解决 pair 类型作为 unordered_map 的键时编译报错

如果使用 pair 类型作为 unordered_map 的键时,在我们编译的时候会出现报错,如:

unordered_map<pair<int, int>, bool> mp;//编译错误

image.png

问题分析: 由于 C++ 中没有 pair 类型的 Hash 函数,而 unordered_map 中又正好用 std::hash 来计算 key ,所以当 pair 作为 unordered_mapkey 时会出现编译错误。

问题解决: 我们可以将 unordered_map 换成 map ,因为 map 是通过操作符 < 来比较 key 的大小,而 pair 是可以比较大小的。所以,当我们需要使用 pair 类型作为 key 时,可以使用 map

map<pair<int, int>, bool> mp;//编译通过

总结

通常我们遇到查找问题首先考虑使用的是 unordered_map 而不是 map,这是因为 unordered_map 内部实现了哈希表,所以 unordered_map的查找速度( 带常数级的O(1)O(1) )是比 mapO(log2N)O(log_2N) )要快很多的。一般还要根据需求衡量内存和执行速度以及是否需要键值对有序等因素来选择使用哪个容器。


结束语

理想再远大,也需要点滴的努力;口号再响亮,也需要实际的行动。不论你有多么宏伟的规划、多么昂扬的斗志,不去行动,一切终究是空中楼阁。千里之行,始于足下,不如就从此刻开始行动,向着目标进发。