【LeetCode-C-Container】unorered_map & unorered_set & map & set

operator[]=

C++ STL 的 unordered_map 支持 []= 操作符。 本质为(operator[] 【unordered_set和set无此操作符】与元素的 operator=叠加所得) 该操作符具有插入和修改元素的双重功能,具体行为如下:

1. []= 操作符的核心功能

  • 键存在时:通过 map[key] = value 可以直接修改该键对应的值。
  • 键不存在时:会自动插入一个以该键为索引的新元素,并将值初始化为默认类型(如 int 会初始化为 0,类对象调用默认构造函数),随后再赋值为 value

示例代码:

std::unordered_map<std::string, int> umap;
umap["apple"] = 10;  // 插入键 "apple",值为 10
umap["apple"] = 20;  // 修改键 "apple" 的值为 20

insert() 的区别

  • []= 的强制覆盖性:无论键是否存在,都会执行赋值操作。而 insert() 仅在键不存在时插入新元素,若键已存在则保留原值
  • 性能差异[]= 在键不存在时会先构造默认值再赋值,可能涉及额外开销insert()emplace() 更适合直接构造键值对。

示例:

umap.insert({"banana", 5});  // 仅当键不存在时插入

底层实现与注意事项

  • 哈希表机制[]= 操作依赖哈希函数计算键的存储位置,若哈希冲突较多(例如键分布不均匀),可能导致性能下降。
  • 默认值风险:若键不存在,直接读取 map[key] 会插入默认值,可能引发意外行为。建议使用 find()count() 检查键是否存在后再操作。

适用场景

  • 动态更新值:例如统计词频时,可以直接通过 map[word]++ 快速更新计数。
  • 快速初始化:适用于需要灵活插入或修改键值对的场景,但需注意默认值的初始化逻辑。

总结

unordered_map[]= 操作符提供了便捷的键值操作方式,但其隐式的插入行为需要谨慎处理。在需要高性能或严格避免无效键插入时,建议结合 find()emplace() 使用。

遍历

在C++中,std::mapstd::unordered_map的遍历方法高度相似,但由于底层实现不同(红黑树 vs 哈希表),需要注意顺序问题。以下是具体方法及适用场景:


通用遍历方法(适用于所有版本)

  1. 范围for循环(C++11+)
    最简洁的现代语法,直接遍历键值对:

    for (const auto& pair : container) {
        std::cout << "Key: " << pair.first << ", Value: " << pair.second << "\n";
    }
    

    适用场景:只需顺序访问元素,无需修改键值。

  2. 迭代器遍历
    传统方法,支持正向和反向遍历(反向仅限std::map):

    // 正向遍历
    for (auto it = container.begin(); it != container.end(); ++it) {
        std::cout << "Key: " << it->first << ", Value: " << it->second << "\n";
    }
    // 反向遍历(仅map)
    for (auto rit = container.rbegin(); rit != container.rend(); ++rit) {
        // 访问方式同上
    }
    

    适用场景:需要灵活控制遍历过程(如条件删除元素)。

  3. std::for_each算法
    结合Lambda表达式实现函数式编程:

    #include <algorithm>
    std::for_each(container.begin(), container.end(), [](const auto& pair) {
        std::cout << "Key: " << pair.first << ", Value: " << pair.second << "\n";
    });
    

    适用场景:需要对元素进行统一处理(如批量修改值)。


进阶方法(C++17+)

  1. 结构化绑定结构化绑定
    解构键值对,提升代码可读性:

    for (const auto& [key, value] : container) {
        std::cout << "Key: " << key << ", Value: " << value << "\n";
    }
    

    适用场景:需要分离键和值的操作(如仅处理键或值)。

  2. 键/值视图(C++20+)
    通过std::views::keysstd::views::values遍历单一部分:

    for (const auto& key : container | std::views::keys) {
        std::cout << "Key: " << key << "\n";
    }
    

    适用场景:仅需遍历键或值。


注意事项

  1. 顺序差异

    • std::map按键升序排列,支持反向迭代。
    • std::unordered_map无固定顺序,反向迭代不可用。
  2. 安全性

    • 遍历时插入或删除元素可能导致迭代器失效,需谨慎操作。
  3. 性能

    • std::unordered_map的遍历速度通常更快,但受哈希冲突影响。

方法对比

方法适用容器C++版本特点
范围for循环全部C++11+简洁直观
迭代器全部C++98+灵活控制
结构化绑定全部C++17+解构键值对
键/值视图全部C++20+仅遍历键或值

根据需求选择:若需顺序访问且代码简洁,优先用范围for循环;若需条件操作元素,用迭代器或std::for_each;若仅处理键或值,可用C++20的视图功能。

Pair

mapunordered_map的键值对存储方式

在C++中,std::mapstd::unordered_map的底层确实使用std::pair来存储键值对。每个元素的类型为std::pair<const Key, T>,其中Key是键的类型,T是值的类型。

  • std::map:基于红黑树实现,元素按键的升序排列,键不可修改。
  • std::unordered_map:基于哈希表实现,元素无序存储,键通过哈希函数映射到桶中。

两者在插入、查找等操作中均通过pair对象管理键值对。例如:

std::map<int, std::string> m;
m.insert(std::make_pair(1, "apple"));  // 使用pair插入键值对

std::pair的创建方法

std::pair是C++标准库中的模板类,用于将两个值组合成单一实体。以下是其常见的创建方法:

1. 直接构造

通过构造函数初始化键值对:

std::pair<std::string, int> p1("apple", 3);  // 显式指定键值对类型
std::pair p2("banana", 5);                   // C++17起支持自动类型推导(CTAD)

2. 使用make_pair函数

无需显式指定类型,自动推导类型:

auto p3 = std::make_pair("orange", 7);  // 类型为std::pair<const char*, int>

3. 拷贝构造与赋值

通过已有pair对象初始化新对象:

std::pair p4(p3);       // 拷贝构造
std::pair p5 = p3;      // 赋值操作

4. 通过emplaceinsert隐式构造

在容器中直接构造pair(无需显式创建对象):

std::unordered_map<std::string, int> umap;
umap.emplace("pear", 2);          // 在容器内部构造pair
umap.insert({"grape", 4});        // 使用初始化列表隐式转换为pair

5. 结构化绑定(C++17+)

从容器遍历时解构pair

for (const auto& [key, value] : umap) {  // 将pair解构为key和value
    std::cout << key << ": " << value << "\n";
}

pair的扩展用法

  1. 自定义比较规则
    若需自定义排序或哈希函数(如unordered_map中键为自定义类型),需为pair提供哈希和比较方法。
  2. 通过tie解构返回值
    当函数返回pair时,可用std::tie直接解构接收值:
    std::string name; int age;
    std::tie(name, age) = getPerson();  // 假设getPerson返回pair<string, int>
    

方法对比

方法适用场景特点
直接构造类型明确时代码直观,需显式指定类型
make_pair类型自动推导简化代码,适用于模板编程
拷贝构造/赋值基于已有对象创建新对象依赖原对象的类型和值
容器隐式构造直接插入到mapunordered_map高效,避免临时对象拷贝

Licensed under CC BY-NC-SA 4.0
Last updated on Mar 07, 2025 00:00 UTC
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy