从零手写C++哈希表容器:深入理解unordered_map/unordered_set底层原理

CentOS7安装openGauss5.0.2LTS版本 该文档要根据自己的实际情况调整ip地址。要根据自己的实际网卡调整。 阅读详情

1. 项目概述与核心价值

最近在带新人做项目,发现很多同学对C++标准库里的 unordered_map 和 unordered_set 用得很溜,但一问到它们底层是怎么工作的,就有点含糊其辞了。这让我想起自己刚入行那会儿,也是只会调用 insert 和 find ,直到后来自己动手封装了一个简易版的哈希表容器,才真正搞懂了哈希冲突、负载因子这些核心概念。今天,我就带大家从零开始,手把手封装我们自己的 myunordered_map 和 myunordered_set 。这不仅仅是一个练习,更是深入理解C++容器设计、模板编程和数据结构底层原理的绝佳机会。无论你是正在准备面试,想搞懂那些关于哈希表的“八股文”,还是想提升自己的C++工程能力,这个项目都能让你收获满满。我们会从最基础的哈希桶开始,一步步实现迭代器、模板特化,最终得到一个功能完整、性能可靠的简易哈希容器。相信我,走完这一趟,你再去看STL的源码,会有一种“原来如此”的通透感。

2. 哈希表容器设计思路拆解

2.1 为什么选择封装哈希表?

在C++标准库中,我们有两套关联容器:基于红黑树的有序容器( map , set )和基于哈希表的无序容器( unordered_map , unordered_set )。后者在平均情况下能提供O(1)时间复杂度的查找、插入和删除操作,性能优势明显。但是,直接使用黑盒库,我们很难理解其性能边界和内部机制。比如,为什么当元素数量过多时性能会急剧下降? rehash 操作到底在背后做了什么?自己动手实现一遍,是回答这些问题最直接的方式。我们的目标不是造一个比STL更优秀的轮子,而是通过造轮子的过程,吃透它的设计精髓和实现细节。

2.2 核心架构设计:复用与解耦

一个高效的实现策略是代码复用。观察 unordered_map 和 unordered_set ,你会发现它们底层共享同一套哈希表机制,区别仅在于存储的数据类型: map 存储键值对 pair<const Key, T> ,而 set 只存储键 Key 。因此,我们可以设计一个通用的哈希表模板类作为底层引擎,然后通过封装和模板特化,派生出针对 map 和 set 的不同接口。这样做的好处是,哈希表的核心逻辑(如哈希函数、冲突解决、扩容)只需要维护一份,大大减少了代码冗余和潜在的bug。我们的设计将遵循STL的惯例,提供迭代器、容量操作和修改操作等接口。

2.3 关键技术选型与考量

首先需要确定哈希表解决冲突的方法。常见的有开放定址法和链地址法。开放定址法(如线性探测)在表项密集时容易产生“聚集”现象,影响性能。链地址法(又称拉链法)是将哈希到同一位置的元素放入一个链表中,实现简单,且能稳定地处理冲突,也是大多数标准库实现的选择。我们将采用链地址法,每个哈希桶(bucket)是一个单向链表的头节点。

其次,关于哈希函数,我们将提供一个默认的仿函数,用于计算内置类型(如整型、字符串)的哈希值,同时允许用户自定义哈希函数,以支持更复杂的键类型。这与STL的设计保持一致。

最后,迭代器的设计是一大难点。哈希表的迭代器需要能够在不同桶之间正确跳转。当遍历完当前桶的链表后,需要找到下一个非空桶的链表头。这要求迭代器内部持有哈希表本身的指针或引用,以访问桶数组信息。

3. 底层哈希表模板类实现详解

3.1 哈希节点与桶结构定义

一切从最基础的节点开始。对于链地址法,我们需要一个链表节点。考虑到 map 和 set 存储的数据类型不同,我们设计一个模板节点结构体。

template <class T>
struct HashNode {
    T _data; // 存储的数据,对于map是pair<const K, V>,对于set是K
    HashNode<T>* _next; // 指向下一个节点的指针

    HashNode(const T& data)
        : _data(data)
        , _next(nullptr) {}
};

接下来是哈希表本体 HashTable 的框架。它是一个模板类,接受三个参数:键类型 K ,值类型 T (对于 set , T=K ;对于 map , T=pair<const K, V> ),以及一个从键 K 提取出用于比较的“关键码”的仿函数 KeyOfT 。这个仿函数是解耦的关键。

template <class K, class T, class KeyOfT>
class HashTable {
public:
    typedef HashNode<T> Node;
    // ... 后续会添加迭代器等类型定义

private:
    std::vector<Node*> _tables; // 哈希桶数组,每个元素是一个链表头指针
    size_t _n = 0; // 存储的有效节点个数,用于计算负载因子
};

这里使用 std::vector 来管理桶数组,因为它能方便地动态扩容。 _n 记录表中存储的节点总数。负载因子 load_factor = _n / _tables.size() ,是触发扩容的重要指标。

3.2 哈希函数与仿函数设计

哈希函数负责将任意类型的键映射到一个大小在 [0, 桶数量-1] 范围内的整数索引。我们首先实现一个默认的哈希仿函数。对于整型,直接取模即可(需注意处理负数)。对于字符串,采用经典的BKDR哈希算法。

template <class K>
struct DefaultHash {
    size_t operator()(const K& key) {
        return (size_t)key;
    }
};

// 模板特化处理string类型
template <>
struct DefaultHash<std::string> {
    size_t operator()(const std::string& str) {
        size_t hash = 0;
        for (auto ch : str) {
            hash = hash * 131 + ch; // BKDR哈希乘子
        }
        return hash;
    }
};

在 HashTable 类中,我们增加一个模板参数 Hash ,默认使用 DefaultHash<K> 。这样用户可以为自定义类型提供特化版本。

KeyOfT 仿函数用于从存储的数据 T 中提取出键 K 。对于 set ,直接返回数据本身;对于 map ,则返回 pair 中的 first 成员。这个设计使得哈希表内部可以用统一的方式访问键值,而不必关心 T 的具体构成。

3.3 核心操作:插入、查找与删除

插入操作 ( Insert ) 是哈希表最复杂的操作之一,因为它可能引发扩容。步骤如下:

  1. 检查键是否已存在(调用 Find ),避免重复插入( unordered_set 不允许重复键, unordered_map 的 insert 也不覆盖)。
  2. 检查负载因子。通常当负载因子超过0.7或1.0时(可根据场景调整),需要进行扩容。我们选择当 _n == _tables.size() 时扩容,即负载因子达到1.0,这是一个比较激进的策略,能保证较好的查找性能。
  3. 扩容操作 ( _rehash ):创建一个新的、容量更大的桶数组(通常是原大小的两倍左右的质数)。然后遍历旧表的所有节点,根据新的桶数量重新计算每个节点的哈希索引,并将其插入到新桶对应的链表中。 这里有一个关键点:是移动节点,而不是创建新节点拷贝数据再删除旧节点 。这样可以避免不必要的拷贝开销,直接改变节点的 _next 指针指向即可。
  4. 计算待插入数据的哈希索引,采用头插法将新节点插入对应桶的链表。
std::pair<iterator, bool> Insert(const T& data) {
    KeyOfT kot;
    Hash hash;
    // 1. 查重
    iterator it = Find(kot(data));
    if (it != end()) {
        return std::make_pair(it, false); // 已存在,插入失败
    }
    // 2. 检查扩容
    if (_n == _tables.size()) {
        size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
        _rehash(newSize);
    }
    // 3. 插入新节点
    size_t hashi = hash(kot(data)) % _tables.size();
    Node* newnode = new Node(data);
    // 头插
    newnode->_next = _tables[hashi];
    _tables[hashi] = newnode;
    ++_n;
    return std::make_pair(iterator(newnode, this), true);
}

注意: 在 _rehash 过程中,重新哈希时一定要使用 新的桶数量 进行计算,即 hash(kot(node->_data)) % newTables.size() 。如果错误地使用了旧的桶数量,会导致所有节点被错误地放置,引发严重bug。

查找操作 ( Find ) 相对直接:计算键的哈希索引,遍历对应桶的链表,使用 KeyOfT 提取节点数据的键进行比较。

删除操作 ( Erase ) 需要找到待删除节点的前驱节点,因为我们是单链表。所以需要遍历链表,同时记录前一个节点。找到后调整指针并 delete 节点。这里需要注意处理删除的是链表头节点的特殊情况。

3.4 迭代器设计与实现挑战

哈希表的迭代器必须是“前向迭代器”,它支持 ++ 和 * 操作。迭代器内部需要包含两个成员:指向当前节点的指针 _node ,和指向所属哈希表对象的指针 _pht (用于访问桶数组以寻找下一个桶)。

operator++ 的实现是精髓所在:

  1. 如果当前节点的 _next 不为空,则直接走到下一个节点。
  2. 如果 _next 为空,说明当前桶已遍历完,需要寻找下一个非空桶。通过当前节点的哈希值计算出当前桶索引 hashi ,然后从 hashi+1 开始遍历桶数组 _pht->_tables ,找到第一个非空桶,将其头节点赋值给 _node 。如果找不到,则将 _node 置为 nullptr ,表示结束。
template <class K, class T, class KeyOfT, class Hash>
struct __HashIterator {
    typedef HashNode<T> Node;
    typedef HashTable<K, T, KeyOfT, Hash> HT;
    typedef __HashIterator<K, T, KeyOfT, Hash> Self;

    Node* _node;
    HT* _pht;

    __HashIterator(Node* node, HT* pht)
        : _node(node)
        , _pht(pht) {}

    T& operator*() { return _node->_data; }
    T* operator->() { return &_node->_data; }

    Self& operator++() {
        if (_node->_next) {
            // 同一个桶内下一个节点
            _node = _node->_next;
        } else {
            // 当前桶已空,找下一个非空桶
            KeyOfT kot;
            Hash hash;
            size_t hashi = hash(kot(_node->_data)) % _pht->_tables.size();
            ++hashi; // 从下一个桶开始找
            while (hashi < _pht->_tables.size()) {
                if (_pht->_tables[hashi]) {
                    _node = _pht->_tables[hashi];
                    return *this;
                }
                ++hashi;
            }
            // 后面没有非空桶了
            _node = nullptr;
        }
        return *this;
    }
    // ... 相等与不等判断运算符
};

这里有一个 关键细节 :迭代器的 operator++ 需要计算当前节点的哈希值以定位当前桶。这要求迭代器能够访问哈希表类的私有成员 _tables 。因此,我们需要在 HashTable 类中将迭代器模板类声明为友元。

template <class K, class T, class KeyOfT, class Hash>
class HashTable {
    template <class K, class T, class KeyOfT, class Hash>
    friend struct __HashIterator; // 友元声明
    // ...
};

4. 封装myunordered_set与myunordered_set

4.1 myunordered_set的封装

有了强大的 HashTable 底座,封装 myunordered_set 就变得非常简单。它本质上是对 HashTable 的一个薄包装,主要工作是定义好模板参数,并暴露 set 应有的接口。

template <class K, class Hash = DefaultHash<K>>
class myunordered_set {
public:
    // 从存储的数据类型T(即K)中提取出键,就是K本身
    struct SetKeyOfT {
        const K& operator()(const K& key) { return key; }
    };
private:
    HashTable<K, K, SetKeyOfT, Hash> _ht; // 底层哈希表引擎
public:
    // 类型定义,将底层迭代器暴露出来
    typedef typename HashTable<K, K, SetKeyOfT, Hash>::iterator iterator;

    iterator begin() { return _ht.begin(); }
    iterator end() { return _ht.end(); }

    std::pair<iterator, bool> insert(const K& key) {
        return _ht.Insert(key);
    }

    iterator find(const K& key) {
        return _ht.Find(key);
    }

    bool erase(const K& key) {
        return _ht.Erase(key);
    }
    // ... 其他接口如size(), empty(), clear()等,直接转发给_ht
};

可以看到, myunordered_set 的 KeyOfT 仿函数(这里叫 SetKeyOfT )非常简单,直接返回传入的键。它告诉底层的 HashTable :“我存储的数据 T 就是键 K 本身”。所有对 set 的操作,都被转发给内部的 _ht 对象去执行。

4.2 myunordered_map的封装与pair处理

myunordered_map 的封装逻辑类似,但多了一层对键值对 pair<const K, V> 的处理。这里的关键在于, map 的键是 const 的,这意味着一旦插入,键就不能被修改,这保证了哈希表基于键的组织结构不会被破坏。

template <class K, class V, class Hash = DefaultHash<K>>
class myunordered_map {
public:
    // 存储的数据类型是pair,键是const的
    typedef std::pair<const K, V> value_type;
    // 从pair中提取键(即first成员)
    struct MapKeyOfT {
        const K& operator()(const value_type& kv) { return kv.first; }
    };
private:
    HashTable<K, value_type, MapKeyOfT, Hash> _ht;
public:
    typedef typename HashTable<K, value_type, MapKeyOfT, Hash>::iterator iterator;

    iterator begin() { return _ht.begin(); }
    iterator end() { return _ht.end(); }

    std::pair<iterator, bool> insert(const value_type& kv) {
        return _ht.Insert(kv);
    }

    V& operator[](const K& key) {
        std::pair<iterator, bool> ret = insert({key, V()}); // 尝试插入,V()是值类型的默认构造
        return ret.first->second; // 返回对应值的引用
    }

    iterator find(const K& key) {
        return _ht.Find(key);
    }
    // ...
};

myunordered_map 的精华在于 operator[] 的实现,它完美模拟了STL中 map 的下标访问语义:如果键存在,返回其对应值的引用;如果键不存在,则插入一个以该键为键、以值类型默认值初始化的键值对,并返回这个新值的引用。这为像 map[key]++ 这样的操作提供了极大便利。其内部就是调用了 insert 函数,并利用其返回值。

4.3 接口一致性测试与验证

实现完成后,必须进行严格的测试,确保我们的容器行为与STL标准库一致。测试应覆盖基本功能、边界条件和性能。

  1. 基本功能测试 :插入一批数据(包括重复键),测试 find 、 erase 、迭代器遍历是否正常工作。对于 map ,重点测试 operator[] 的插入和修改功能。
  2. 扩容测试 :插入大量数据,观察在负载因子触发扩容前后,迭代器的有效性是否保持(我们的实现中,扩容后旧迭代器会失效,这与STL行为一致)。可以通过在扩容前后打印桶的数量来验证。
  3. 自定义类型测试 :定义一个结构体作为键,并为其特化哈希函数和相等比较( == 运算符),测试容器是否能正确工作。
  4. 性能对比 :与 std::unordered_map/set 进行简单插入和查找的性能对比(使用相同的数据量和哈希函数)。我们的简易实现可能在极端情况下性能有差距,但基本操作的平均复杂度应该接近。
// 示例:简单测试myunordered_map
void TestMyUnorderedMap() {
    myunordered_map<std::string, int> m;
    m.insert({"apple", 1});
    m.insert({"banana", 2});
    m["orange"] = 3; // 使用operator[]插入
    m["apple"] = 10; // 使用operator[]修改

    auto it = m.find("banana");
    if (it != m.end()) {
        std::cout << it->first << ":" << it->second << std::endl;
    }

    for (auto& kv : m) { // 范围for循环,依赖begin()和end()
        std::cout << kv.first << " -> " << kv.second << std::endl;
    }
}

5. 深度优化、常见陷阱与进阶思考

5.1 性能优化关键点

  1. 质数桶容量 :哈希表桶数组的大小最好是一个质数。这能使哈希值对桶数取模的结果分布更均匀,减少冲突。可以在扩容时,从一个预定义的质数表中选取下一个比目标值大的质数作为新容量,而不是简单地倍增。
  2. 更优的冲突解决策略 :当单个桶的链表过长时,查找会退化为O(n)。可以考虑当链表长度超过某个阈值(如8)时,将其转换为红黑树(如Java HashMap所做),以保证最坏情况下的性能。但这会大大增加实现复杂度。
  3. 高效的内存管理 :频繁的 new 和 delete (尤其在插入删除多的场景)会影响性能。可以实现一个简单的内存池,预先分配一批节点,减少向系统申请内存的次数。
  4. 移动语义 :为 Insert 函数添加右值引用版本( Insert(T&& data) ),在C++11及以上环境中,这可以在插入临时对象时避免一次拷贝,提升效率。

5.2 实际开发中踩过的坑

  1. 迭代器失效问题 :这是最易出错的地方。在我们的实现中,任何可能导致扩容的操作(如 Insert )都会使所有迭代器失效,因为桶数组被重新分配了,节点被移动到了新的内存地址。这与STL规范一致。 务必在文档或注释中明确说明迭代器失效的时机 ,使用者应避免在扩容后使用旧的迭代器。
  2. 哈希函数的品质 :糟糕的哈希函数会导致大量冲突,使哈希表退化为链表。对于自定义类型,务必设计一个分布均匀的哈希函数。一个常见的技巧是使用标准库 std::hash 作为基础,然后组合多个成员变量: size_t h1 = std::hash<string>{}(obj.name); size_t h2 = std::hash<int>{}(obj.id); return h1 ^ (h2 << 1); 。
  3. const 迭代器与 const 正确性 :我们目前只实现了普通迭代器。一个完整的STL风格容器还需要 const_iterator ,它指向常量数据,用于 const 对象。这需要再实现一个 __HashConstIterator 类,并仔细处理 begin() const 和 end() const 的返回类型。
  4. 桶的本地迭代器 :STL的 unordered_map 提供了 begin(bucket_index) 和 end(bucket_index) 接口,用于遍历特定桶。这在某些需要探查哈希表分布情况的调试场景中很有用。实现它需要在哈希表类中增加返回桶内链表头尾迭代器的方法。

5.3 从项目延伸到面试与工程

完成这个项目后,你对以下面试高频问题了如指掌:

  • 哈希表解决冲突的方法有哪些?优缺点? 你能从实现复杂度、空间开销、缓存友好性等方面对比链地址法和开放定址法。
  • 哈希表扩容(rehash)的过程是怎样的? 你能详细描述创建新数组、重新计算哈希、移动节点、更新迭代器关系的全过程。
  • 哈希表的迭代器如何实现? ++ 操作的时间复杂度是多少? 你能解释跨桶跳转的逻辑,并分析平均O(1)和最坏O(n)的复杂度。
  • unordered_map 的 operator[] 如何实现? 你能写出其基于 insert 返回值的经典实现。

在工程实践中,你现在能更有底气地选择和使用哈希容器。你会明白,为什么在知道元素大致数量时,使用 reserve 预分配空间可以避免多次扩容,提升性能。你也理解了为什么自定义类型作为键时,必须同时提供哈希函数和相等比较运算符。

这个手动封装的过程,就像一次深入内核的探险。它剥开了标准库神秘的外衣,让你看到了数据结构的筋骨与算法的脉搏。下次当你再写下 unordered_map<string, int> 时,你脑海中浮现的将不再是一个黑盒,而是一个由桶数组、链表、哈希函数和迭代器精密协作的生动图景。这才是学习C++容器最扎实的方式。

摩擦纳米发电机测试系统全解析:从硬件配置到软件控制 本文全面解析摩擦纳米发电机(TENG)测试系统,涵盖硬件配置、软件控制及系统集成。详细介绍NI USB-6003等采集卡的选择与使用,以及一体化控制软件的功能与优势,帮助研究人员高效评估纳米材料发电性能。系统支持自动测试与数据分析,显著提升测试效率与准确性。 阅读详情

相关推荐

利用eds editor生成CANOPEN 设备eds文件

通过以上步骤,您可以根据实际设备的功能特性,自动生成一份符合CANopen协议标准的电子数据表(EDS文件),该文件可以被上位机软件或其他CANopen主站设备用于识别、配置和控制您的CANopen从站设备。使用CANopen EDS Editor生成CANOPEN设备的EDS文件是一个系统化的过程,它涉及将设备的具体技术参数和功能映射到CANopen规范定义的对象字典中。

weixin_38604759的博客 6459

Spring注入的三种方式

1

weixin_45830664的博客 4570

VScode+MPLAB AI助手:Microchip MCU开发全流程实战解析

本文详细解析了如何使用VScode搭配MPLAB AI助手进行Microchip MCU开发的完整流程。从环境配置、工程创建到代码生成与调试,全面介绍了AI助手在提升开发效率、优化代码和故障排除方面的实际应用,为嵌入式开发者提供了一套高效的开发解决方案。

hh234的博客 1131

【Spring】关于Spring的三种注入方式的分析

关于Spring的三种注入方式简单分析

qq_53070263的博客 2922

centos7搭建LNMP环境-编译安装&yum安装-超详细

文章目录1. 首先了解编译安装和yum安装的区别。2. yum安装准备工作---切换阿里云yum源3. 编译安装lnmp4.yum安装lnmp5. lnmp一键安装6. 一些报错7. 笔记8. 参考文章 1. 首先了解编译安装和yum安装的区别。 windows下安装centos虚拟机,之前写过。传送门~~ 编译安装: 可以按需安装,想安在哪个目录下就安在哪个目录下。 可以设定参数 可安装自己想要的版本 yum安装: 安装的方便快捷 不需要考虑包的依赖 可以直接使用service nginx st

最美不过下雨天 9349

【Web实战】微信小程序逆向

2E 70 6E 67”为带存放路径的文件名称,“00 00 11 7C”对应文件在小程序包中的具体偏移位置,“00 01 01 F7“对应文件在小程序包中的数据长度。但这并不是最初的原项目文件结构,原因是微信服务器会将小程序源码中所有的“js”文件压入“app-service.js”文件中,将所有的“json”文件压入“app-config.json”中,将所有的“wxml”文件压入“page-frame.html”文件中,“wxss”则在处理之后以“html”文件的形式存留在对应页面目录之下。

若有战,召必回 1946

Spring常用的三种注入方式

Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:构造方法注入(实体类),setter注入,基于注解的注入。

qq_41273137的博客 4892

在Jetson orin NX上安装部署Yolov8

1、环境准备与依赖安装2、安装PyTorch和Torchvision:从NVIDIA官方论坛或开发者下载中心获取与你的JetPack版本兼容的PyTorch wheel文件。例如,对于JetPack 5.1.2,可以安装PyTorch v2.1.0 8。:需要编译安装与PyTorch版本匹配的Torchvision。这里自己编译没有成功,下载了一个现成的可以用torchvision-0.16.2+c6f3977-cp38-cp38-linux_aarch64.whl。

xxxcccvvvbbb的博客 776

Spring 注入方式

Spring 注入方式有三中 1.setter注入 2.构造器注入 3.基于注解的注入 1.setter注入 在InjectionServiceImple 中有 变量 injectionDao 并有其的set方法 还有一点需要注意:如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。 2.构造器注入 注意:配置文件中的nam.

Imzhipo的博客 8702

高级电机控制场景实例:基于Simulink的永磁同步电机基于Luenberger观测器的弱磁控制仿真

本文基于Simulink实现表贴式永磁同步电机(SPMSM)的Luenberger观测器弱磁控制仿真,通过构建“观测-弱磁-控制”协同逻辑,突破传统矢量控制的基速限制。利用线性化模型设计观测器实时估计电流与磁链,结合电压极限约束动态注入负d轴电流,实现转速扩展至12000rpm(4倍基速),弱磁区电流THD低于3%,动态响应时间小于5ms,验证了高速运行下系统稳定性与鲁棒性。附完整建模步骤、MATLAB函数代码及性能分析,适用于电动汽车、机床等高速驱动场景,为高级电机控制提供可复现的技术范式。

amy_mhd的博客 32

Spring AI Alibaba 项目源码学习(九)-其他继承BaseAgent

本文分析了Spring AI Alibaba Agent Framework中的A2aRemoteAgent实现,主要特点包括: 实现了Agent间的远程调用能力,支持分布式部署 核心组件包括AgentCard封装元数据、A2aNodeActionWithConfig处理调用逻辑、AgentCardProvider提供卡片管理 支持流式调用和状态共享机制 采用Builder模式构建Agent实例 通过指令传递实现Agent间通信 关键流程包括状态提取、请求构建、远程调用和状态更新四个步骤,形成Agent

820

【Spring系列】- 深入理解Spring IoC

IoC全称Inversion of Control控制反转,别名为Dependency Injection依赖注入。如果我们依赖于某个类或服务,最简单而有效的方式就是直接在类的构造函数中新建相应的依赖类。可是我们自己每次用到什么依赖对象都要主动地去获取,这是否真的必要?我们最终所要做的,其实就是直接调用依赖对象所提供的某项服务而已。只要用到这个依赖对象的时候,它能够准备就绪就行。如果有人能够在我们需要时将某个依赖对象送过来,为什么还要自己去折腾?

m0_68383079的博客 1646

关于DEJA_VU3D - Cesium功能集专栏说明

专栏主要目的是对工作中基于Cesium实现过的功能进行整合,暂时打算一周2-3更的样子来更新,每一篇都会奉上完整源代码(尽可能把代码简洁一些)。功能实现大多数是自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有差不多实现小130个左右的功能,后续也会不断的更新。..........................................

总得学点什么的博客 2万+

踏入 C++ 的深邃世界:实现 unordered_set 与 unordered_map 的优雅之旅

在 C++ 标准库中,unordered_set 和 unordered_map 是常用的哈希容器,分别用于存储唯一元素集合和键值对关联表。它们的实现基于哈希表,因此能在平均O1O(1)O1时间内完成插入、查找和删除操作。理解它们的实现机制有助于我们更深入地掌握哈希表的核心原理和高效数据结构的设计。本篇文章将详细讲解如何使用 C++ 模板实现 HashTable 类,并基于该类构建 unordered_set 和 unordered_map,同时深入分析每个成员函数及其实现细节。

17的博客 1万+

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

【C++】使用哈希表封装unordered_set和unordered_map!

哈希表封装unordered_map和unordered_set 摘要 本文详细讲解了如何使用哈希表作为底层容器封装实现unordered_map和unordered_set。主要包括以下几个关键点: 框架分析:通过源码分析,说明hash_map和hash_set复用同一个hashtable实现key和key/value结构。 封装步骤: 实现哈希表(链地址法) 封装unordered_map/set框架,解决KeyOfT问题 实现迭代器(iterator和const_iterator) 保证Key不可修改

2403_86779341的博客 1310

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

深入理解哈希表与C++中的unordered_map

哈希表是一种用于存储 键值对 的基本数据结构。在C++中,哈希表使用 哈希函数 来计算数组的索引,进而存取数组中对应索引位置的值。计算索引的过程称为哈希。哈希表也叫散列表,散列表是数组结构。可以根据一个key值来直接访问数据,因此查找速度快,在基本的数据结构中,数组的查询效率是最高的。

m0_74999866的博客 1909

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

从零开始的C++学习生活 15:哈希表的使用和封装unordered_map/set

本文介绍了基于哈希表实现的unordered_map和unordered_set容器。首先对比了哈希表与红黑树实现的map/set在性能要求、迭代器特性等方面的差异,重点讲解了哈希函数设计、冲突处理策略(开放定址法和链地址法)等底层原理。文章详细分析了如何通过模板特化和素数表优化哈希表实现,并提供了完整的封装方案,包括迭代器实现和关键方法如insert/find/erase的操作细节。最后总结了哈希表在字符串处理、大数据分析等场景的性能优势,为开发者提供了容器选型建议和工程实践指导。

qq_49648783的博客 1383

【探寻C++之旅】第十六章:unordered系列的认识与模拟实现

今天,我们继续踏入追寻C++的冒险历程。上一章我们讲解了数据结构哈希表,那么本章让我们用哈希表来模拟实现一下C++STL中的unordered_map和unordered_set。下面让我们一起来进入本章的学习。

2302_80243065的博客 5367

【C++篇】从底层到封装:用哈希表实现unordered_map和unordered_set

本文深入解析了如何基于哈希表实现STL风格的unordered_map和unordered_set容器。通过分析STL源码,揭示了底层哈希表的设计思想,包括适配器模式、泛型设计和职责分离原则。文章详细介绍了模板参数设计、KeyOfT仿函数实现等关键技术点,展示了unordered_set和unordered_map的基本框架实现方法。从底层哈希表到上层容器的封装过程,体现了STL容器的核心设计理念和实现技巧。

半截诗的博客~ 1204

穿越数据迷宫:C++哈希表的奇幻旅程

在C++的世界中,哈希表是一种高效、独特的数据结构,被广泛应用于需要快速查找、插入、删除的场景。通过哈希函数将数据映射到表中,它不仅提高了操作效率,还在解决冲突时展现了精巧的设计。哈希表在算法的应用中尤为重要,无论是缓存、字典处理还是集合操作,都离不开它的身影。本篇博客将带你一步步深入理解哈希表的实现原理及其应用,让你在编码之路上更上一层楼。unordered:一个不包含重复元素的集合,存储的是唯一的键。:与相似,但允许包含重复元素。:一个键值对的容器,每个键只能出现一次,键是唯一的。

17的博客 8110

从零实现C++哈希表:深入理解unordered_map/unordered_set底层原理

哈希表是一种基于键值对存储的高效数据结构,其核心原理是通过哈希函数将键映射到数组的特定索引位置,从而实现平均O(1)时间复杂度的查找、插入和删除操作。这种设计使其在处理大规模数据时具有显著性能优势,广泛应用于数据库索引、缓存系统和编译器符号表等场景。在C++标准库中,`std::unordered_map`和`std::unordered_set`正是基于链地址法实现的哈希表容器,它们通过模板元编程支持泛型,并利用负载因子和动态扩容机制平衡性能与内存使用。本文将探讨如何从底层实现一套完整的哈希表容器,涵盖*

572

【C++闯关笔记】哈希表模拟实现unordered_map与unordered_set

本文详细探讨了C++中unordered_map、unordered_set与map、set的区别与实现原理。首先分析了两类容器的底层结构差异(哈希表vs红黑树)及其特性对比,包括时间复杂度、元素顺序等关键特性。重点剖析了基于哈希表的unordered系列容器的实现:1)通过HashNode模板统一存储结构;2)使用KeyOfT仿函数解决不同类型元素的键提取问题;3)完整实现了哈希表的核心框架,包括插入、查找、删除等操作。特别深入讲解了迭代器的实现难点,包括跨桶遍历的处理机制。

Kaisir777的博客 648

从零实现C++哈希表容器:深入理解unordered_map/unordered_set底层原理

哈希表是一种基于键值对存储的高效数据结构,其核心原理是通过哈希函数将键映射到固定范围的索引,实现平均O(1)时间复杂度的查找、插入和删除操作。在C++中,STL提供的unordered_map和unordered_set正是基于哈希表实现的关联容器,广泛应用于需要快速查找和去重的场景。本文聚焦于哈希表的底层实现细节,通过从零封装自定义哈希表容器,深入探讨链地址法解决哈希冲突、动态扩容策略、迭代器设计等关键技术点。项目实践不仅帮助开发者掌握模板编程、RAII资源管理等C++核心特性,更能深化对STL容器内部机

weixin_34274029的博客 395

从零实现C++哈希表:深入理解unordered_map与unordered_set底层原理

哈希表作为计算机科学中一种基础且高效的数据结构,其核心原理是通过哈希函数将键映射到固定大小的数组索引,从而实现平均O(1)时间复杂度的查找、插入和删除操作。这一特性使其成为实现关联容器(如字典、集合)的理想底层支撑。在工程实践中,哈希表的技术价值体现在对大规模数据快速访问的支持,广泛应用于数据库索引、缓存系统和编译器符号表等场景。要真正掌握哈希表,关键在于理解其核心组件:哈希函数的设计、冲突解决策略(如链地址法)、动态扩容机制以及迭代器的实现。本文聚焦于C++中unordered_map与unordered

weixin_34128237的博客 547

【C++高阶】深度剖析:从零开始模拟实现 unordered 的奥秘

在C++标准库中,unordered_map和unordered_set作为高效的无序容器,以其基于哈希表的实现方式,为数据的快速查找、插入和删除提供了强有力的支持。这些容器通过哈希函数将元素映射到数组的索引上,从而实现了接近O(1)的平均时间复杂度操作,极大地提升了程序性能。然而,尽管它们的使用极为便捷,了解这些容器背后的工作原理和模拟实现过程,对于深入理解数据结构、算法设计以及优化程序性能都至关重要

EterNity_TiMe_的计算机学习之路 2188

深入理解哈希表:实现 unordered_set 与 unordered_map

好好学习,天天向上!

好好学习,天天向上! 610
上一篇: 模板驱动型文档自动化:让标准化报告一键生成
com_xpp
博客等级 码龄16年 23粉丝 160原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值