C++ 学习杂记05:std::map

概述

std::map是 C++ 标准模板库(STL)中的一个关联容器,它存储键值对(key-value pairs),并根据键(key)自动排序。std::map通常基于红黑树(一种自平衡二叉搜索树)实现,因此能保证对数时间复杂度的查找、插入和删除操作。

基本特性

  • 头文件<map>

  • 定义template <class Key, class T, class Compare = std::less<Key>, class Allocator = std::allocator<std::pair<const Key, T>>> class map;

  • 键唯一性 :每个键在 map中只能出现一次

  • 排序 :元素始终按键的升序排列(默认使用 std::less<Key>

  • 时间复杂度

    • 查找:O(log n)

    • 插入:O(log n)

    • 删除:O(log n)

基本用法

1. 创建和初始化

复制代码
复制代码
复制代码
#include <iostream>
#include <map>
#include <string>

int main() {
    // 空map
    std::map<int, std::string> map1;
    
    // 使用初始化列表
    std::map<int, std::string> map2 = {
        {1, "Alice"},
        {2, "Bob"},
        {3, "Charlie"}
    };
    
    // 复制构造
    std::map<int, std::string> map3(map2);
    
    // 范围构造
    std::map<int, std::string> map4(map2.begin(), map2.end());
    
    return 0;
}

2. 插入元素

复制代码
复制代码
复制代码
std::map<int, std::string> myMap;

// 使用insert
myMap.insert({1, "Apple"});
myMap.insert(std::make_pair(2, "Banana"));
myMap.insert(std::pair<const int, std::string>(3, "Cherry"));

// 使用下标运算符(如果键不存在则插入)
myMap[4] = "Date";  // 插入键4,值为"Date"
myMap[5] = "Elderberry";

// 使用emplace(C++11及以上)
myMap.emplace(6, "Fig");

3. 访问元素

复制代码
复制代码
复制代码
std::map<int, std::string> myMap = {{1, "One"}, {2, "Two"}, {3, "Three"}};

// 使用下标运算符(注意:如果键不存在,会创建新元素)
std::cout << myMap[1] << std::endl;  // 输出: One

// 使用at(如果键不存在,抛出std::out_of_range异常)
try {
    std::cout << myMap.at(2) << std::endl;  // 输出: Two
} catch (const std::out_of_range& e) {
    std::cout << "Key not found!" << std::endl;
}

// 使用find(安全的方式)
auto it = myMap.find(3);
if (it != myMap.end()) {
    std::cout << it->second << std::endl;  // 输出: Three
}

4. 删除元素

复制代码
复制代码
复制代码
std::map<int, std::string> myMap = {
    {1, "A"}, {2, "B"}, {3, "C"}, {4, "D"}, {5, "E"}
};

// 通过迭代器删除
auto it = myMap.find(2);
if (it != myMap.end()) {
    myMap.erase(it);
}

// 通过值删除
myMap.erase(3);

// 删除一个范围
myMap.erase(myMap.find(4), myMap.end());

// 删除所有元素
myMap.clear();

5. 遍历元素

复制代码
复制代码
复制代码
std::map<int, std::string> myMap = {{1, "Red"}, {2, "Green"}, {3, "Blue"}};

// 使用迭代器
for (auto it = myMap.begin(); it != myMap.end(); ++it) {
    std::cout << it->first << ": " << it->second << std::endl;
}

// 使用范围for循环(C++11及以上)
for (const auto& pair : myMap) {
    std::cout << pair.first << ": " << pair.second << std::endl;
}

// 使用结构化绑定(C++17及以上)
for (const auto& [key, value] : myMap) {
    std::cout << key << ": " << value << std::endl;
}

容量查询

复制代码
复制代码
复制代码
std::map<int, std::string> myMap = {{1, "A"}, {2, "B"}};

// 获取元素数量
std::cout << "Size: " << myMap.size() << std::endl;  // 输出: 2

// 检查是否为空
std::cout << "Empty: " << std::boolalpha << myMap.empty() << std::endl;  // 输出: false

// 获取最大可能的大小
std::cout << "Max size: " << myMap.max_size() << std::endl;

查找操作

复制代码
复制代码
复制代码
std::map<int, std::string> myMap = {{1, "A"}, {2, "B"}, {3, "C"}};

// find - 查找键,返回迭代器
auto it = myMap.find(2);
if (it != myMap.end()) {
    std::cout << "Found: " << it->second << std::endl;
}

// count - 检查键是否存在(对于map,返回0或1)
if (myMap.count(3) > 0) {
    std::cout << "Key 3 exists" << std::endl;
}

// lower_bound - 返回第一个不小于key的元素的迭代器
auto lb = myMap.lower_bound(2);
if (lb != myMap.end()) {
    std::cout << "Lower bound: " << lb->first << " -> " << lb->second << std::endl;
}

// upper_bound - 返回第一个大于key的元素的迭代器
auto ub = myMap.upper_bound(2);
if (ub != myMap.end()) {
    std::cout << "Upper bound: " << ub->first << " -> " << ub->second << std::endl;
}

// equal_range - 返回匹配键的范围
auto range = myMap.equal_range(2);
for (auto i = range.first; i != range.second; ++i) {
    std::cout << i->first << " -> " << i->second << std::endl;
}

自定义比较函数

复制代码
复制代码
复制代码
#include <iostream>
#include <map>
#include <string>

// 自定义比较函数 - 按字符串长度排序
struct StringLengthCompare {
    bool operator()(const std::string& a, const std::string& b) const {
        return a.length() < b.length();
    }
};

int main() {
    // 使用自定义比较函数
    std::map<std::string, int, StringLengthCompare> lengthMap;
    
    lengthMap.insert({"apple", 5});
    lengthMap.insert({"kiwi", 4});
    lengthMap.insert({"banana", 6});
    
    for (const auto& pair : lengthMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    
    return 0;
}

性能注意事项

  1. 时间复杂度

    • 查找、插入、删除:O(log n)

    • 遍历:O(n)

  2. 空间复杂度:O(n),每个元素需要额外的指针空间(通常每个节点需要3个指针)

  3. std::unordered_map对比

    • std::map:有序,基于红黑树,O(log n)操作

    • std::unordered_map:无序,基于哈希表,平均O(1)操作,最坏O(n)

  4. 使用场景

    • 需要有序遍历

    • 元素数量相对稳定

    • 键的比较操作开销不大

    • 内存使用不是主要限制

线程安全性

  • 多个线程可以同时读取同一个std::map对象

  • 如果有任何线程修改std::map,必须确保没有其他线程同时访问该对象

  • 对于多线程环境,考虑使用互斥锁或并发容器(如Intel TBB的concurrent_hash_map

最佳实践

  1. 避免频繁的插入删除 :如果频繁操作,考虑std::unordered_map

  2. 使用emplace替代insert:避免不必要的临时对象创建

  3. 使用find而不是operator[]检查存在性operator[]会插入不存在的键

  4. 考虑使用std::unordered_map:当不需要有序遍历时

  5. 使用auto简化迭代器声明:提高代码可读性

示例应用

复制代码
复制代码
复制代码
#include <iostream>
#include <map>
#include <string>

class StudentDatabase {
private:
    std::map<int, std::string> students;
    
public:
    void addStudent(int id, const std::string& name) {
        auto result = students.emplace(id, name);
        if (!result.second) {
            std::cout << "Student with ID " << id << " already exists!" << std::endl;
        }
    }
    
    bool removeStudent(int id) {
        return students.erase(id) > 0;
    }
    
    std::string getStudentName(int id) const {
        auto it = students.find(id);
        if (it != students.end()) {
            return it->second;
        }
        return "Student not found";
    }
    
    void listAllStudents() const {
        if (students.empty()) {
            std::cout << "No students in database." << std::endl;
            return;
        }
        
        std::cout << "Student List (sorted by ID):" << std::endl;
        for (const auto& [id, name] : students) {
            std::cout << "ID: " << id << ", Name: " << name << std::endl;
        }
    }
    
    size_t getStudentCount() const {
        return students.size();
    }
};

int main() {
    StudentDatabase db;
    
    db.addStudent(1001, "Alice Johnson");
    db.addStudent(1003, "Bob Smith");
    db.addStudent(1002, "Charlie Brown");
    
    db.listAllStudents();
    
    std::cout << "\nStudent 1002: " << db.getStudentName(1002) << std::endl;
    
    db.removeStudent(1002);
    std::cout << "\nAfter removing student 1002:" << std::endl;
    db.listAllStudents();
    
    return 0;
}

总结

std::map是C++中一个功能强大且灵活的关联容器,提供了高效的键值对存储和检索能力。它的自动排序特性使得有序遍历变得简单高效。在选择使用std::map时,应根据具体需求权衡其有序性和对数时间复杂度的特性与其他容器(如std::unordered_map)的差异。

相关推荐
Magic@2 小时前
Redis学习[1] ——基本概念和数据类型
linux·开发语言·数据库·c++·redis·学习
黑不溜秋的2 小时前
C++ STL reduce 用法
开发语言·c++
云泽8082 小时前
二叉树高阶笔试算法题精讲(一):序列化、层序遍历、LCA 与 BST 转换
数据结构·c++·算法
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 98. 验证二叉搜索树 | C++ 指针边界法
c++·算法·leetcode
t***5443 小时前
如何在 Dev-C++ 中配置 Clang 编译器
开发语言·c++
白夜11173 小时前
静态库vs动态库
c++·笔记
小辉同志3 小时前
45. 跳跃游戏 II
c++·leetcode·游戏·贪心算法
海参崴-3 小时前
C++ 进阶篇 STL 模拟实现二叉搜索树
开发语言·c++
cpp_25013 小时前
P2871 [USACO07DEC] Charm Bracelet S
数据结构·c++·算法·动态规划·题解·洛谷·背包dp