终于搞明白了map和flat_map的区别

C++23中引入了两个新的关联容器--std::flat_map 和 std::flat_set ,其目的是提供更高的性能与更好的缓存局部性(cache locality)

看到这俩容器,很多人第一个疑问是什么是flat容器?

flat,顾名思义 扁平的意思。

正如我们前面所说的,传统的map和set是基于平衡搜索二叉树(通常为红黑树)实现,通过指针连接的树结构来维护有序性。而flat_map和flat_set则基于顺序容器(通常是 std::vector)实现的扁平化容器(flat container)。

引入它们的主要原因是缓存友好性。在实际性能中,缓存局部性通常比理论上的渐近复杂度(O(N) 与 O(logN))更重要,尤其对于元素数量较少或中等的容器而言。

正是因为其顺序容器的特性,那么其优缺点就很明显了:

核心优势:

  • • 内存连续 → 更好的 CPU 缓存命中率

  • • 小规模数据时,遍历和查找更快

  • • 空间开销更小,没有节点指针等结构

缺点:

  • • 插入、删除需要移动元素 → O(N) 开销

  • • 迭代器在插入删除后会失效

  • • 不适合频繁修改的场景

下面我们通过一个简单的例子来加深对这俩容器的理解:

flat_set_demo:

#include <flat_set>
#include <iostream>
#include <vector>
#include <algorithm>

int main(){
    // 构造:自动去重并排序
    std::flat_set<int> nums{7, 3, 9, 3, 5};
    // => {3, 5, 7, 9}

    // 插入新元素(返回 pair<iterator, bool>)
    auto [it, ok] = nums.insert(4);
    if (ok) std::cout << "插入元素 4 成功\n";

    // 使用 emplace 构造元素
    nums.emplace(8);

    // 一次性插入多个元素(C++23 新增 insert_range)
    std::vector<int> extra{1, 2, 3};
    nums.insert_range(extra); // 自动排序去重

    // 查找与访问
    if (nums.contains(5))
        std::cout << "找到元素 5\n";

    auto lb = nums.lower_bound(4);
    std::cout << "lower_bound(4): " << *lb << "\n";

    // 随机访问(flat 容器支持随机访问迭代器)
    std::cout << "所有元素:";
    for (size_t i = 0; i < nums.size(); ++i)
        std::cout << nums.begin()[i] << " ";
    std::cout << "\n";

    // 删除元素
    nums.erase(3);
    nums.erase(nums.find(9));

    std::cout << "删除后:";
    for (auto v : nums) std::cout << v << " ";
    std::cout << "\n";

    // 提取底层容器(C++23)
    auto backing = std::move(nums).extract();
    std::cout << "底层容器大小: " << backing.size()
              << ", nums 是否为空: " << std::boolalpha << nums.empty() << "\n";
}

在上述代码中,可以看出,可以通过直接用 [] 索引或通过 begin()+n 访问对应元素。

输出如下:

插入元素 4 成功
找到元素 5
lower_bound(4): 4
所有元素:1 2 3 4 5 7 8 9 
删除后:1 2 4 5 7 8 
底层容器大小: 6, nums 是否为空: true

std::flat_map 是基于 std::vector<std::pair<K,V>> 的有序映射容器。

flat_map_demo:

// 编译:g++ -std=c++23 flat_map_demo.cpp && ./a.out
#include <flat_map>
#include <iostream>
#include <string>
#include <cassert>

int main(){
    // 初始化
    std::flat_map<int, std::string> dict{
        {1, "one"}, {3, "three"}, {2, "two"}
    };
    // 自动排序为 {1:"one", 2:"two", 3:"three"}

    // 插入与更新
    dict.emplace(4, "four");
    dict.insert_or_assign(2, "TWO"); // 更新已存在键

    // 访问
    std::cout << "dict[3] = " << dict[3] << "\n";
    std::cout << "dict.at(2) = " << dict.at(2) << "\n";

    // 遍历
    std::cout << "\n所有键值对:\n";
    for (const auto& [k, v] : dict)
        std::cout << k << " -> " << v << "\n";

    // C++23 新增:keys() / values()
    const auto& keys = dict.keys();
    const auto& vals = dict.values();

    std::cout << "\n通过 keys() / values() 访问:\n";
    for (size_t i = 0; i < keys.size(); ++i)
        std::cout << keys[i] << " => " << vals[i] << "\n";

    assert(std::is_sorted(keys.begin(), keys.end()));
}

输出如下:

dict[3] = three
dict.at(2) = TWO

所有键值对:
1 -> one
2 -> TWO
3 -> three
4 -> four

通过 keys() / values() 访问:
1 => one
2 => TWO
3 => three
4 => four





评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值