【C++基础】std::map详解

std::map是C++标准模板库(STL)中的关联容器,它提供了一种将键和值一一对应关联起来的数据结构。
其中的元素按照键的顺序进行排序,默认情况下是按照键的升序排序。
每个键只能出现一次,如果要插入具有相同键的新元素,则会覆盖原有键对应的值。

一、底层实现

std::map的底层实现通常基于红黑树(Red-Black Tree)。红黑树是一种自平衡的二叉搜索树,它满足以下性质:

1、 每个节点要么是红色,要么是黑色。
2、 根节点是黑色的。
3、 每个叶子节点(NIL节点,空节点)是黑色的。
4、 如果一个节点是红色的,则它的两个子节点都是黑色的。
5、 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,黑色节点的数量相同。

红黑树的这些性质保证了树的平衡性,使得查找、插入和删除操作的时间复杂度都能保持在O(log n)级别。

std::map中,每个元素都存储为一个键值对,键和值之间是一一对应的关系。在红黑树中,每个节点包含一个键值对和指向左右子节点的指针。元素按照键的大小顺序排列,因此红黑树的节点是按照键的大小顺序排列的。

当执行插入、删除或查找操作时,红黑树会根据节点的键进行自平衡操作,以保持树的平衡性。这些自平衡操作包括颜色翻转、左旋和右旋等,通过这些操作,红黑树可以保持平衡,同时保持较高的性能。

二、成员函数

1、 构造函数:
map():默认构造函数,创建一个空的std::map
map(InputIterator first, InputIterator last):根据指定范围的元素构造map。
map(const map& other):复制构造函数,创建一个新的map,包含另一个map的所有元素。

std::map<std::string,int> map1;
map1.insert({"A",1});
map1.insert({"B",2});
map1.insert({"C",3});
map1.insert({"D",4});
std::map<std::string,int> map2(map1); // 4
std::cout<<map2.size()<<std::endl;
std::map<std::string,int> map3(std::move(map2));
std::cout<<map2.size()<<std::endl; // 0
std::cout<<map3.size()<<std::endl; // 4

2、 容量查询:
empty():检查map是否为空。
size():返回map中元素的个数。
max_size():返回map支持的最大元素数量。

std::map<std::string,int> map1;
map1.insert({"A",1});
map1.insert({"B",2});
map1.insert({"C",3});
map1.insert({"D",4});
std::cout<<map1.size()<<std::endl; // 4
std::cout<<map1.empty()<<std::endl; // 0
std::cout<<map1.max_size()<<std::endl; // 128102389400760775

3、 插入和删除:
insert(const value_type& value):插入一个键值对。
emplace(Args&&、、、 args):在map中原位构造一个新元素。
erase(const key_type& key):删除指定键对应的元素。
clear():清空map中的所有元素。
Demo:insert函数演示

#include 
#include 

int main() {
    // 创建一个空的std::map
    std::map<int, std::string> myMap;

    // 单个元素插入
    auto result1 = myMap.insert(std::make_pair(1, "one"));
    if (result1.second) {
        std::cout << "Inserted: " << result1.first->first << " -> " << result1.first->second << std::endl;
    } else {
        std::cout << "Element already exists: " << result1.first->first << " -> " << result1.first->second << std::endl;
    }

    // 通过移动元素插入
    auto result2 = myMap.insert(std::make_pair(2, "two"));
    if (result2.second) {
        std::cout << "Inserted: " << result2.first->first << " -> " << result2.first->second << std::endl;
    } else {
        std::cout << "Element already exists: " << result2.first->first << " -> " << result2.first->second << std::endl;
    }

    // 范围插入
    std::map<int, std::string> anotherMap = {{3, "three"}, {4, "four"}};
    myMap.insert(anotherMap.begin(), anotherMap.end());

    // 通过位置插入
    std::map<int, std::string>::iterator it = myMap.find(2); // 找到键为2的位置
    if (it != myMap.end()) {
        myMap.insert(it, std::make_pair(5, "five")); // 在键为2的位置之前插入
    }

    // 通过列表初始化插入
    auto result5 = myMap.insert(std::make_pair(6, "six"));
    if (result5.second) {
        std::cout << "Inserted: " << result5.first->first << " -> " << result5.first->second << std::endl;
    } else {
        std::cout << "Element already exists: " << result5.first->first << " -> " << result5.first->second << std::endl;
    }

    // 输出map中的所有元素
    std::cout << "Map contents:" << std::endl;
    for (const auto& pair : myMap) {
        std::cout << pair.first << " -> " << pair.second << std::endl;
    }

    return 0;
}

Demo:erase函数演示

#include 
#include 

int main() {
    // 创建一个 std::map 对象
    std::map<int, std::string> myMap;

    // 添加一些键值对
    myMap[1] = "One";
    myMap[2] = "Two";
    myMap[3] = "Three";
    myMap[4] = "Four";
    myMap[5] = "Five";

    // 显示原始的 std::map
    std::cout << "Original Map:" << std::endl;
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    // 删除键值为2的元素
    myMap.erase(2);

    // 显示删除后的 std::map
    std::cout << "\nMap after erasing key 2:" << std::endl;
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    // 删除迭代器位置为1的元素
    auto it = myMap.find(4);
    if (it != myMap.end()) {
        myMap.erase(it);
    }

    // 显示删除后的 std::map
    std::cout << "\nMap after erasing iterator position 1:" << std::endl;
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    // 删除指定范围内的元素
    auto first = myMap.lower_bound(1);
    auto last = myMap.upper_bound(3);
    myMap.erase(first, last);

    // 显示删除后的 std::map
    std::cout << "\nMap after erasing elements in range [1, 3):" << std::endl;
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

4、 查找和访问:
find(const key_type& key):查找指定键的元素,返回指向该元素的迭代器。
count(const key_type& key):返回指定键在map中出现的次数(0或1)。
at(const key_type& key):返回指定键对应的值。
operator[]:重载了下标访问运算符,返回给定键对应的值的引用。

std::map<std::string,int> map1;
map1.insert({"A",1});
map1.insert({"B",2});
map1.insert({"C",3});
map1.insert({"D",4});
auto it = map1.find("3");
for(auto num : map1){
    std::cout<<num.first<<" "<<num.second<<std::endl; // A
}
// A 1
// B 2
// C 3
// D 4
std::cout<<map1.count("A")<<std::endl; // 1
std::cout<<map1.at("A")<<std::endl; // 1
std::cout<<map1["A"]<<std::endl; // 1

5、 迭代器相关:
begin():返回指向第一个元素的迭代器。
end():返回指向尾后元素的迭代器。
rbegin():返回指向最后一个元素的逆向迭代器。
rend():返回指向首个元素前一个位置的逆向迭代器。

6、 赋值和交换:
operator=:赋值运算符,将一个map的内容复制给另一个map。
swap(map& other):交换两个map的内容。

std::map<std::string,int> map1;
map1.insert({"A",1});
map1.insert({"B",2});
map1.insert({"C",3});
map1.insert({"D",4});
std::map<std::string,int> map2;
map1.swap(map2);
std::cout<<map1.size()<<std::endl; // 0
std::cout<<map2.size()<<std::endl; // 4

三、自定义对象

当使用自定义对象作为std::map的键时,需要确保自定义对象可以进行比较。这通常涉及到重载比较运算符(例如operator<)以确保元素可以按照特定的顺序进行排序。

Demp定义一个自定义对象的std::map

#include 
#include 

// 自定义对象
class MyObject {
public:
    int id;
    std::string name;

    MyObject(int id, const std::string& name) : id(id), name(name) {}

    // 重载比较运算符,以确保对象可以按照特定顺序进行排序
    bool operator<(const MyObject& other) const {
        return id < other.id;
    }
};

int main() {
    // 创建一个存储自定义对象的 std::map
    std::map<MyObject, std::string> myMap;

    // 添加自定义对象到 map
    myMap.emplace(MyObject(1, "Object1"), "Value1");
    myMap.emplace(MyObject(3, "Object3"), "Value3");
    myMap.emplace(MyObject(2, "Object2"), "Value2");

    // 遍历 map,并输出每个键值对
    for (const auto& pair : myMap) {
        std::cout << "Key: " << pair.first.id << ", Value: " << pair.second << std::endl;
    }

    return 0;
}

四、性能问题

std::map的性能受到多种因素的影响,包括但不限于以下几点:

1、 底层数据结构std::map通常基于红黑树实现,这确保了在插入、查找和删除操作上有较好的平衡性能。

2、 插入和删除操作:由于std::map是有序的关联容器,插入和删除操作可能需要重新平衡底层的红黑树,因此插入和删除元素的性能可能略低于无序容器,例如std::unordered_map

3、 查找操作:由于底层使用了红黑树,std::map的查找操作的时间复杂度为O(log n),其中n是容器中元素的数量。这使得在大型数据集上查找效率较高。

4、 内存占用:红黑树作为底层数据结构,每个节点需要存储额外的指针和颜色信息,因此std::map相比于一些无序容器可能会消耗更多的内存。

5、 迭代器稳定性std::map的迭代器在插入和删除操作后仍然有效,这意味着不会因为插入或删除元素而使现有的迭代器失效。

综上所述,std::map在大多数情况下提供了较好的性能和稳定性,特别适用于需要有序存储和高效查找的场景。然而,在某些特定情况下,例如需要快速的插入和删除操作,并且不需要元素的顺序关系时,可能会有更合适的选择。

你可能感兴趣的:(C++,c++,java,算法,开发语言,数据结构)