跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 哈希表封装实战:模拟实现 unordered_map 与 unordered_set

C++ 哈希表封装实战,基于底层 HashTable 模拟实现 unordered_map 和 unordered_set。重点解析 KeyOfT 仿函数提取键值、迭代器单向遍历逻辑及扩容机制。通过完整代码示例展示插入、查找、删除操作,深入理解标准库容器内部原理。

随缘发布于 2026/3/15更新于 2026/6/2623 浏览
C++ 哈希表封装实战:模拟实现 unordered_map 与 unordered_set

C++ 哈希表封装实战:模拟实现 unordered_map 与 unordered_set

在深入理解 C++ 标准库之前,亲手实现一遍 unordered_map 和 unordered_set 是非常好的学习路径。这不仅有助于掌握哈希表的底层机制,还能熟悉 STL 容器的设计模式。

1. 源码背景与框架

1.1 历史沿革

SGI-STL 30 版本(C++11 之前)中并没有标准的 unordered_map 和 unordered_set,当时使用的是非标准的 hash_map 和 hash_set。这两个容器是 C++11 标准引入的,但 SGI-STL 早已实现了类似的哈希表结构。

1.2 核心框架

查看 stl_hash_map、stl_hash_set 以及 stl_hashtable.h 的源码,可以发现它们复用了同一个 hashtable 模板类。

  • hash_set:传给 hashtable 的是两个 key(Value 和 Key),实际上 Value 就是 Key。
  • hash_map:传给 hashtable 的是 pair<const Key, T>。

这种复用机制非常巧妙,通过不同的 KeyOfT 仿函数来提取键值,从而适配不同的业务场景。不过源码中的命名风格比较随意,比如 hash_set 用 Value 而 hash_map 用 Key 和 T,我们在实现时会统一规范。

2. 模拟实现思路

2.1 复用哈希表框架

我们基于之前实现的 HashTable 进行封装。为了区分 unordered_map 和 unordered_set,我们需要在插入时处理不同的数据类型:

  • unordered_set:存储的是 Key,内部 HashTable 的类型参数为 <K, K, SetKeyOfT, Hash>。
  • unordered_map:存储的是 pair<K, V>,内部 HashTable 的类型参数为 <K, pair<K, V>, MapKeyOfT, Hash>。

关键点在于 KeyOfT 仿函数的实现。因为 HashTable 不知道传入的数据是单纯的 Key 还是包含 Value 的 Pair,所以需要在外部定义一个仿函数,告诉 HashTable 如何从数据中提取出用于哈希计算的 Key。

// SetKeyOfT 示例
struct SetKeyOfT {
    const K& operator()(const K& key) { return key; }
};

// MapKeyOfT 示例
struct MapKeyOfT {
    const K& operator()(const std::pair<K, V>& kv) { return kv.first; }
};

2.2 迭代器实现难点

哈希表的迭代器是单向迭代器。其核心逻辑在于 operator++ 的实现:

  1. 如果当前桶下还有节点,直接指向下一个节点。
  2. 如果当前桶遍历完了,需要找到下一个不为空的桶。

这需要迭代器持有哈希表对象的指针,以便计算当前桶位置并线性探测下一个有效桶。同时要注意 begin() 返回第一个非空桶的第一个节点,end() 通常用空指针表示。

对于 unordered_set,迭代器不支持修改元素;对于 unordered_map,key 不可修改,value 可以修改。这通过模板参数的 const 限定来控制。

2.3 支持 [] 操作符

unordered_map 的 [] 操作符依赖于 insert 的返回值。我们需要修改 HashTable 的 Insert 方法,使其返回 pair<Iterator, bool>,这样 operator[] 就能方便地判断是否插入成功并返回引用。

3. 完整代码实现

以下是核心组件的实现,包括 HashTable、unordered_set 和 unordered_map。

HashTable.h

这是最底层的哈希表实现,负责管理桶数组、节点链表以及扩容逻辑。

#pragma once
#include <vector>
#include <algorithm>

static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] = {
    53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,
    49157, 98317, 196613, 393241, 786433, 1572869, 3145739,
    6291469, 12582917, 25165843, 50331653, 100663319, 201326611,
    402653189, 805306457, 1610612741, 3221225473, 4294967291
};

inline unsigned long __stl_next_prime(unsigned long n) {
    const unsigned long* first = __stl_prime_list;
    const unsigned long* last = __stl_prime_list + __stl_num_primes;
    const unsigned long* pos = std::lower_bound(first, last, n);
    return pos == last ? *(last - 1) : *pos;
}

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

template<>
struct HashFunc<std::string> {
    size_t operator()(const std::string& key) {
        size_t hash = 0;
        for (auto ch : key) {
            hash += ch;
            hash *= 131;
        }
        return hash;
    }
};

namespace Hash_bucket {
    template<class T>
    struct HashNode {
        T _data;
        HashNode<T>* _next;
        HashNode(const T& data) :_data(data), _next(nullptr) {}
    };

    template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
    struct HTIterator {
        typedef HashNode<T> Node;
        typedef HashTable<K, T, KeyOfT, Hash> HT;
        typedef HTIterator Self;

        Node* _node;
        const HT* _pht;

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

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

        Self& operator++() {
            if (_node->_next) {
                _node = _node->_next;
            } else {
                KeyOfT kot;
                Hash hs;
                size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();
                ++hashi;
                while (hashi < _pht->_tables.size()) {
                    if (_pht->_tables[hashi]) {
                        _node = _pht->_tables[hashi];
                        break;
                    } else {
                        ++hashi;
                    }
                }
                if (hashi == _pht->_tables.size()) {
                    _node = nullptr;
                }
            }
            return *this;
        }

        bool operator!=(const Self & s) const { return _node != s._node; }
        bool operator==(const Self & s) const { return _node == s._node; }
    };

    template<class K, class T, class KeyOfT, class Hash>
    class HashTable {
        template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
        friend struct HTIterator;

        typedef HashNode<T> Node;
    public:
        typedef HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
        typedef HTIterator<K, T, const T&, const T*, KeyOfT, Hash> ConstIterator;

        Iterator Begin() {
            if (_n == 0) return End();
            for (size_t i = 0; i < _tables.size(); i++) {
                if (_tables[i]) return Iterator(_tables[i], this);
            }
            return End();
        }

        Iterator End() { return Iterator(nullptr, this); }

        ConstIterator Begin() const {
            if (_n == 0) return End();
            for (size_t i = 0; i < _tables.size(); i++) {
                if (_tables[i]) return ConstIterator(_tables[i], this);
            }
            return End();
        }

        ConstIterator End() const { return ConstIterator(nullptr, this); }

        HashTable() :_tables(__stl_next_prime(1), nullptr), _n(0) {}

        ~HashTable() {
            for (size_t i = 0; i < _tables.size(); i++) {
                Node* cur = _tables[i];
                while (cur) {
                    Node* next = cur->_next;
                    delete cur;
                    cur = next;
                }
                _tables[i] = nullptr;
            }
            _n = 0;
        }

        pair<Iterator, bool> Insert(const T& data) {
            KeyOfT kot;
            if (auto it = Find(kot(data)); it != End()) return { it, false };

            Hash hs;
            // 负载因子接近 1 时扩容
            if (_n >= _tables.size()) {
                std::vector<Node*> newtables(__stl_next_prime(_tables.size() + 1), nullptr);
                for (size_t i = 0; i < _tables.size(); i++) {
                    Node* cur = _tables[i];
                    while (cur) {
                        Node* next = cur->_next;
                        size_t hashi = hs(kot(cur->_data)) % newtables.size();
                        cur->_next = newtables[hashi];
                        newtables[hashi] = cur;
                        cur = next;
                    }
                    _tables[i] = nullptr;
                }
                _tables.swap(newtables);
            }

            size_t hashi = hs(kot(data)) % _tables.size();
            Node* newnode = new Node(data);
            newnode->_next = _tables[hashi];
            _tables[hashi] = newnode;
            ++_n;
            return { Iterator(newnode, this), true };
        }

        Iterator Find(const K& key) {
            KeyOfT kot;
            Hash hs;
            size_t hashi = hs(key) % _tables.size();
            Node* cur = _tables[hashi];
            while (cur) {
                if (kot(cur->_data) == key) return { cur, this };
                cur = cur->_next;
            }
            return { nullptr, this };
        }

        bool Erase(const K& key) {
            KeyOfT kot;
            Hash hs;
            size_t hashi = hs(key) % _tables.size();
            Node* prev = nullptr;
            Node* cur = _tables[hashi];
            while (cur) {
                if (kot(cur->_data) == key) {
                    if (prev == nullptr) _tables[hashi] = cur->_next;
                    else prev->_next = cur->_next;
                    --_n;
                    delete cur;
                    return true;
                }
                prev = cur;
                cur = cur->_next;
            }
            return false;
        }

    private:
        std::vector<Node*> _tables;
        size_t _n;
    };
}

unordered_set.h

封装 HashTable,限制 key 为唯一且不可变。

#pragma once
#include "HashTable.h"

namespace jqj {
    template<class K, class Hash = HashFunc<K>>
    class unordered_set {
        struct SetKeyOfT {
            const K& operator()(const K& key) { return key; }
        };
    public:
        typedef typename Hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;
        typedef typename Hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator const_iterator;

        iterator begin() { return _ht.Begin(); }
        iterator end() { return _ht.End(); }
        const_iterator begin() const { return _ht.Begin(); }
        const_iterator end() const { return _ht.End(); }

        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); }

    private:
        Hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
    };
}

unordered_map.h

封装 HashTable,支持 key-value 映射及 [] 操作符。

#pragma once
#include "HashTable.h"

namespace jqj {
    template<class K, class V, class Hash = HashFunc<K>>
    class unordered_map {
        struct MapKeyOfT {
            const K& operator()(const std::pair<K, V>& kv) { return kv.first; }
        };
    public:
        typedef typename Hash_bucket::HashTable<K, std::pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;
        typedef typename Hash_bucket::HashTable<K, std::pair<const K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator;

        iterator begin() { return _ht.Begin(); }
        iterator end() { return _ht.End(); }
        const_iterator begin() const { return _ht.Begin(); }
        const_iterator end() const { return _ht.End(); }

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

        V& operator[](const K& key) {
            pair<iterator, bool> ret = _ht.Insert(std::make_pair(key, V()));
            return ret.first->second;
        }

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

    private:
        Hash_bucket::HashTable<K, std::pair<const K, V>, MapKeyOfT, Hash> _ht;
    };
}

Test.cpp

测试用例展示基本功能。

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;

#include "unordered_map.h"
#include "unordered_set.h"

void Print(const jqj::unordered_set<int>& s) {
    jqj::unordered_set<int>::const_iterator it = s.begin();
    while (it != s.end()) {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

int main() {
    // 测试 unordered_set
    jqj::unordered_set<int> us;
    us.insert(3);
    us.insert(1000);
    us.insert(2);
    us.insert(102);
    us.insert(2111);
    us.insert(22);

    auto it = us.begin();
    while (it != us.end()) {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    Print(us);

    // 测试 unordered_map
    jqj::unordered_map<string, string> dict;
    dict.insert({ "string", "" });
    dict.insert({ "left", "左边" });
    dict.insert({ "right", "右边" });

    dict["map"] = "地图";
    dict["left"] = "左边修改";

    for (auto& [k, v] : dict) {
        cout << k << ":" << v << endl;
    }
    return 0;
}

运行结果会显示插入的元素顺序(取决于哈希分布)以及 map 的键值对输出。通过这段代码,你可以直观地看到自定义容器与标准库行为的一致性。

目录

  1. C++ 哈希表封装实战:模拟实现 unorderedmap 与 unorderedset
  2. 1. 源码背景与框架
  3. 1.1 历史沿革
  4. 1.2 核心框架
  5. 2. 模拟实现思路
  6. 2.1 复用哈希表框架
  7. 2.2 迭代器实现难点
  8. 2.3 支持 [] 操作符
  9. 3. 完整代码实现
  10. HashTable.h
  11. unordered_set.h
  12. unordered_map.h
  13. Test.cpp
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++ 哈希表封装:模拟实现 unordered_map 和 unordered_set
  • C++ 基于哈希表封装 unordered_map 和 unordered_set
  • C++关联式容器详解:map、set与unordered_map的原理与应用
  • C++ 构造函数与初始化列表核心解析
  • C++高性能事件循环库libev封装实战
  • C++26 契约编程:三种实现方式与最佳实践
  • C++ 高并发内存池实战:ThreadCache 设计与实现
  • C++ 类型转换避坑指南:从基础到四种核心强制转换方式
  • C++ 复习核心知识点
  • C++ 红黑树封装实战:从零实现 Map 与 Set
  • C++ 分布式语音识别服务实践
  • C++ 分布式任务调度核心算法与负载均衡实践
  • 单调栈专题:接雨水与柱状图中最大矩形
  • 面向 C++ 开发的 Web 自动化测试入门:从概念到 Selenium 实战
  • C++ 仿函数详解:让对象像函数一样调用
  • C++ 仿函数核心解析:状态、性能与 STL 实践
  • C++ 仿函数深度解析:状态管理与 STL 实践
  • C++ 二叉搜索树详解:概念、操作与实现
  • C++ 二叉搜索树(BST)原理及核心操作实现
  • C++ 二叉搜索树:从基础概念到代码实现

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online