std::map是STL中有序的关联容器,它内部存储的元素都是键值对的形式的,其中key是唯一,不可重复,但是value却没有限制,同时std::map中的元素都会按照key值自动排序,因此它是有序容器。
同样对于map的介绍可以参考以下这两个网站:
实现原理
std::map底层使用红黑树作为底层的数据结构。红黑树是一种自动平衡的二叉树,它确保插入、删除和查找操作的时间复杂度都是O(log n)。
因为std::map需要排序,因此它是有一定的性能损耗的,如果你仅仅需要一个关联容器,并不关心排序问题,那么建议优先使用unordered_map
。 相对于std::map的话std::unordered_map是基于哈希表实现的,具有常数时间复杂度的平均性能。
因为std::map被称为有序容器,所以对插入进去的key有排序的要求。一般需要为类实现<比较方法,例如以下代码就无法通过编译:
arduino
using namespace std;
class A{
public:
A(){
}
~A(){
}
private:
int b;
};
int main() {
std::map<A,int> m; //默认无参构造
A a{};
m[a] = 20;
return 0;
}
因为上面的代码使用自定义的类A的对象作为map的key,但是类A却没有重载操作符<
,此时我们可以可以通过重载函数的方式让其通过编译:
arduino
bool operator< (const A& va) const{
return b < va.b;
}
插入删除
对于std::map的访问,我们可以通过下标访问符[]
或者函数at
进行访问。
c
int main() {
std::map<int,std::string> m;
m[1] = "hello";
m[2] = "world";
// 输出 hello
std::cout << m[1] << std::endl;
// 输出 world
std::cout << m.at(2) << std::endl;
return 0;
}
但是需要注意的一点是下标访问符[]
或者函数at
并不是完全等价的,下标访问符[]
如果没有相关key也不会跑出异常, 而函数at
需要确保一定包含相关的key,否则会抛出异常,这可能是大多数开发者不喜欢用at
的原因吧。
对于std::map插入元素,我们可以使用下标访问符[]
、函数at
、函数insert
进行添加:
c
int main() {
std::map<int,std::string> m;
// 使用下标访问符添加
m[1] = "hello";
// 使用at
// m.at(2) = "world"; // error除非有这个key
m.at(1) = "world";
// 使用insert
m.insert(std::pair<int, string>(3, "gogo"));
m.insert(make_pair(4,"666"));
// 这个insert真不喜欢啊
m.insert(map<int,string>::value_type (5,"5555"));
for (int i = 0; i < 6; ++i) {
std::cout << m[i] << std::endl;
}
return 0;
}
注意:当使用下标访问符时如果map中不存在某个key时,对map使用map[key]操作会在map中增加一个键值对,键名为key,值是传入的value类型的默认值。
在这里提以下std::pair 是 C++ 标准库中定义的一个模板类,用于将两个值组合为一个类型。它提供了简单的方式来存储和操作两个不同类型的值。 我们可以通过first
和second
成员变量,分别访问第一个和第二个值。
对于std::map的元素删除我们使用函数erase
即可,如果需要清空则使用clear
函数。
c
int main() {
std::map<int,std::string> m;
// 输出0
std::cout << "key的个数:" << m.count(5) << std::endl;
m[5];
// 输出1
std::cout << "key的个数:" << m.count(5) << std::endl;
// 删除key为5的
m.erase(5);
// 输出0
std::cout << "key的个数:" << m.count(5) << std::endl;
// 清空
m.clear();
return 0;
}
查找
在std::map中我们可以通过函数find
或者函数count
找到对应的key是否存在。
需要注意的是这里这里的函数find
是指map的内部函数find
,并不是指std::find
,它们之间的性能是有区别的。内部函数find
性能更佳。
c
int main() {
std::map<int,std::string> m;
// 输出0
m[1] = "hello";
std::cout << "key count:" << m.count(1) << std::endl;
if(m.find(1) != m.end()){
std::cout << "找到key" << std::endl;
} else{
std::cout << "没有找到key" << std::endl;
}
return 0;
}
那么同样能实现查找功能,函数find
和函数count
应该用那个呢?无论是实际情况还是从字面意思理解都是用函数find
更佳。函数count
虽然也能实现查找功能,但是它的更多是实现key的个数统计功能。
元素遍历
对于std::map的遍历一般有两种方式,一种是迭代器法,一种是auto循环。
c
int main() {
std::map<int,std::string> m;
m[1] = "hello";
m[2] = "world";
// 迭代器法遍历
for(map<int, string>::iterator it = m.begin(); it != m.end(); ++ it){
cout << "key:" << it->first << "---value:" << it->second << std::endl;
}
cout << "+++++++++华丽的分割线+++++++++"<< std::endl;
// auto遍历
for(const auto &pa : m){
cout << "key:" << pa.first << "---value:" << pa.second << std::endl;
}
return 0;
}
排序规则
map容器排序是按照键的自动排序进行存储。默认情况下,std::map使用键的升序进行排序,也就是key从小到大。 但如果你希望按照其他方式进行排序,可以通过自定义比较函数来实现:一般借用仿函数即可,需要在构造map时传入我们放函数自定义的类或结构体。
c
class MyCompare
{
public:
// 这个值的类型和你map的key的类型对应
bool operator()(int v1, int v2) const{
return v1 > v2;
}
};
int main() {
std::map<int,std::string> m;
m[1] = "hello";
m[2] = "world";
// auto遍历
for(const auto &pa : m){
cout << "key:" << pa.first << "---value:" << pa.second << std::endl;
}
cout << "+++++++++华丽的分割线+++++++++"<< std::endl;
std::map<int,std::string,MyCompare> m2;
m2.insert(m.begin(),m.end());
for(const auto &pa : m2){
cout << "m2的健值对key:" << pa.first << "---value:" << pa.second << std::endl;
}
return 0;
}
unordered_map
和map一样unordered_map也是STL中关联容器,但是unordered_map的key是没有排序的,unordered_map底层实现是哈希表,元素存储时没有特定的顺序,不保证元素以任何特定的顺序排列。
就性能而言因为map的底层是基于红黑树实现的,因此查找操作的平均时间复杂度为O(log n)。而unordered_map底层是哈希表,查找操作的平均时间复杂度为O(1),但最坏情况下为O(n)。
unordered_map的使用方式和map差不多,在实际的开发过程中选择使用map还是unordered_map取决于具体的应用场景和对容器操作的需求, 如果需要有序的键值对,则可以选择map;如果不需要有序的健值对则可以选择unordered_map,因为它的性能更佳。
关注我,后期不定期更新...