跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

C++ STL 核心容器模拟:unordered_set 与 unordered_map 实现

综述由AI生成基于哈希表的无序容器模拟实现涉及底层数据结构设计与泛型编程技巧。文章详细解析了 unordered_set 与 unordered_map 的模拟过程,涵盖哈希表节点结构、链地址法冲突处理、动态扩容策略及迭代器遍历逻辑。重点阐述了如何通过 KeyOfT 仿函数适配不同存储类型,解决键值对提取问题。同时说明了单向迭代器的实现细节,包括跨桶遍历机制及 begin/end 边界处理。最后展示了 operator[] 重载的实现原理,确保接口符合标准库规范。

苹果系统发布于 2026/3/24更新于 2026/5/44 浏览
C++ STL 核心容器模拟:unordered_set 与 unordered_map 实现

C++ STL 核心容器模拟:unordered_set 与 unordered_map 实现

在 C++ STL 系列中,无序关联容器(unordered_set/map)是性能优化的关键组件。本文深入探讨其底层哈希表结构,并通过模拟实现解析核心机制。

标准库中的实现原理

SGI STL30 版本属于 C++11 之前的版本,当时不存在标准的 unordered_set 和 unordered_map。这两个容器是在 C++11 标准中正式引入的。在此之前,SGI STL 实现了非标准容器 hash_set 和 hash_map,其核心依赖 stl_hashtable.h。

从源码结构可以看出,hash_set 和 hash_map 复用了同一个 hashtable 来实现关键结构:

  • hash_set:传递给 hashtable 的是单纯的 key。
  • hash_map:传递给 hashtable 的是 pair<const key, value> 这种键值对形式。

核心源码框架

stl_hashtable.h
/*-------------------------- stl_hashtable.h --------------------------*/
template<class Value, class Key, class HashFcn, class ExtractKey, class EqualKey, class Alloc>
class hashtable {
public:
    typedef Key key_type;
    typedef Value value_type;
    typedef HashFcn hasher;
    typedef EqualKey key_equal;
private:
    hasher hash;
    key_equal equals;
    ExtractKey get_key;
    typedef __hashtable_node<Value> node;
    vector<node*, Alloc> buckets;
    size_type num_elements;
public:
    typedef __hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> iterator;
    pair<iterator, > ;
    ;
};

< >
  {
    __hashtable_node* next;
    Value val;
};
bool
insert_unique
(const value_type& obj)
const_iterator find(const key_type& key) const
template
class
Value
struct
__hashtable_node
stl_hash_set
/*-------------------------- stl_hash_set --------------------------*/
template<class Value, class HashFcn = hash<Value>, class EqualKey = equal_to<Value>, class Alloc = alloc>
class hash_set {
private:
    typedef hashtable<Value, Value, HashFcn, identity<Value>, EqualKey, Alloc> ht;
    ht rep;
public:
    typedef typename ht::key_type key_type;
    typedef typename ht::value_type value_type;
    typedef typename ht::hasher hasher;
    typedef typename ht::key_equal key_equal;
    typedef typename ht::const_iterator iterator;
    typedef typename ht::const_iterator const_iterator;
    hasher hash_funct() const { return rep.hash_funct(); }
    key_equal key_eq() const { return rep.key_eq(); }
};
stl_hash_map
/*-------------------------- stl_hash_map --------------------------*/
template<class Key, class T, class HashFcn = hash<Key>, class EqualKey = equal_to<Key>, class Alloc = alloc>
class hash_map {
private:
    typedef hashtable<pair<const Key, T>, Key, HashFcn, select1st<pair<const Key, T>>, EqualKey, Alloc> ht;
    ht rep;
public:
    typedef typename ht::key_type key_type;
    typedef T data_type;
    typedef T mapped_type;
    typedef typename ht::value_type value_type;
    typedef typename ht::hasher hasher;
    typedef typename ht::key_equal key_equal;
    typedef typename ht::iterator iterator;
    typedef typename ht::const_iterator const_iterator;
};

代码实现

容器结构设计

数据结构示意图

头文件实现

HashTable.h

这是底层哈希表的核心实现,包含节点定义、迭代器逻辑及扩容策略。

#pragma once
#include <iostream>
#include <vector>
using namespace std;

/*------------------ 任务:定义哈希表函数的'通用类模板'------------------*/
template<class K>
struct HashFunc {
    // 重载 () 运算符 ---> 作用:将 K 类型转化为 size_t 类型,用于计算哈希值
    size_t operator()(const K& key) {
        return (size_t)key; // 默认为直接转换,适用于 int、long 等整数类型
    }
};

/*------------------ 任务:定义哈希函数的'模板特化'------------------*/
template<>
struct HashFunc<string> {
    // 实现:'() 运算符的重载' ---> 作用:将 string 类型的变量转化为哈希值
    size_t operator()(const string& s) {
        // 定义 size_t 类型变量记录 string 类型的变量计算的哈希值
        size_t hash = 0;
        // 使用范围 for 循环遍历字符串并用 BKDR 算法计算其哈希值
        for (auto it : s) {
            // 先将字符的 ASCII 值累加到哈希值中
            hash += it;
            // 再让哈希值乘以质数 131(BKDR 哈希算法认为:131 可有效减少冲突)
            hash *= 131;
        }
        // 返回最终计算的哈希值
        return hash;
    }
};

/*------------------ 任务:实现'获取下一个 >=n 的质数的函数'---> '用于哈希表扩容'------------------*/
inline unsigned long _stl_next_prime(unsigned long n) {
    // 指定素数表的大小
    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
    };
    // 使用二分查找找到第一个 >=n 的素数
    const unsigned long* first = _stl_prime_list;
    const unsigned long* last = _stl_prime_list + _stl_num_primes;
    const unsigned long* pos = lower_bound(first, last, n);
    // 适合作为哈希表容量的质数
    return pos == last ? *(last - 1) : *pos;
}

/*------------------ 任务:使用'链地址法'实现哈希表------------------*/
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 {
        HashNode<T>* _node;
        const HashTable<K, T, KeyOfT, Hash>* _ht;

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

        HTIterator(Node* node, const HT* ht) : _node(node), _ht(ht) {}

        Ref operator*() { return _node->_data; }
        Ptr operator->() { return &_node->_data; }
        bool operator!=(const Self& ht) { return _node != ht._node; }
        bool operator==(const Self& ht) { return _node == ht._node; }

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

    /*------------------ 任务:定义'哈希表的类模板'------------------*/
    template<class K, class T, class KeyOfT, class Hash>
    class HashTable {
    private:
        vector<HashNode<T>*> _tables;
        size_t _n;
        typedef HashNode<T> Node;

        friend struct HTIterator<K, T, T&, T*, KeyOfT, Hash>;
        friend struct HTIterator<K, T, const T&, const T*, KeyOfT, Hash>;

    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++) {
                Node* current = _tables[i];
                if (current) return Iterator(current, this);
            }
        }

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

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

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

        HashTable() : _tables(_stl_next_prime(0)), _n(0) {}

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

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

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

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

            if (_n == _tables.size()) {
                vector<Node*> newVector(_tables.size() * 2);
                for (size_t i = 0; i < _tables.size(); i++) {
                    Node* current = _tables[i];
                    while (current) {
                        Node* next = current->_next;
                        size_t hash_i = hashFunc(kot(current->_data)) % newVector.size();
                        current->_next = newVector[hash_i];
                        newVector[hash_i] = current;
                        current = next;
                    }
                    _tables[i] = nullptr;
                }
                _tables.swap(newVector);
            }

            Node* newNode = new Node(data);
            size_t hash_i = hashFunc(kot(data)) % _tables.size();
            newNode->_next = _tables[hash_i];
            _tables[hash_i] = newNode;
            ++_n;
            return { Iterator(newNode, this), true };
        }
    };
}
Myunordered_set.h

封装无序集合,通过仿函数适配底层哈希表。

#pragma once
#include "HashTable.h"
namespace Myunordered_set {
    enum State { EXIST, EMPTY, DELETE };

    template<class K, class V>
    struct HashData {
        pair<K, V> _kv;
        State _state = EMPTY;
    };

    template<class K, class Hash = HashFunc<K>>
    class unordered_set {
    private:
        struct SetKeyOfT {
            const K& operator()(const K& key) { return key; }
        };
        hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
    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(); }
        iterator find(const K& key) { return _ht.Find(key); }
        bool erase(const K& key) { return _ht.Erase(key); }
        pair<iterator, bool> insert(const K& key) { return _ht.Insert(key); }
    };
}
Myunordered_map.h

封装无序映射,支持 [] 运算符重载。

#pragma once
#include "HashTable.h"
namespace Myunordered_map {
    template<class K, class V, class Hash = HashFunc<K>>
    class unordered_map {
    private:
        struct MapKeyOfT {
            const K& operator()(const pair<K, V>& kv) { return kv.first; }
        };
        hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
    public:
        typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;
        typedef typename hash_bucket::HashTable<K, 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(); }
        iterator find(const K& key) { return _ht.Find(key); }
        bool erase(const K& key) { return _ht.Erase(key); }
        pair<iterator, bool> insert(const pair<K, V>& kv) { return _ht.Insert(kv); }
        V& operator[](const K& key) {
            pair<iterator, bool> ret = insert({ key, V() });
            return ret.first->second;
        }
    };
}

测试文件:Test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
#include "Myunordered_set.h"
#include "Myunordered_map.h"
using namespace std;

void test01() {
    cout << "=== 测试 unordered_set 基本功能 ===" << endl;
    Myunordered_set::unordered_set<int> s;
    s.insert(3); s.insert(1); s.insert(4); s.insert(1);
    s.insert(2); s.insert(5);
    cout << "遍历元素:";
    for (auto it = s.begin(); it != s.end(); ++it) cout << *it << " ";
    cout << endl;
    int key = 3;
    auto find_ret = s.find(key);
    if (find_ret != s.end()) cout << "找到元素:" << *find_ret << endl;
    key = 4;
    bool erase_ret = s.erase(key);
    if (erase_ret) cout << "删除元素 " << key << " 成功" << endl;
    cout << "删除后遍历:";
    for (auto val : s) cout << val << " ";
    cout << endl << endl;
}

void test02() {
    cout << "=== 测试 unordered_set 字符串类型 ===" << endl;
    Myunordered_set::unordered_set<string> str_set;
    str_set.insert("apple"); str_set.insert("banana"); str_set.insert("cherry");
    cout << "字符串集合元素:";
    for (const auto& str : str_set) cout << str << " ";
    cout << endl;
    string target = "banana";
    auto it = str_set.find(target);
    if (it != str_set.end()) cout << "找到字符串:" << *it << endl;
    str_set.erase("cherry");
    cout << "删除 cherry 后:";
    for (const auto& str : str_set) cout << str << " ";
    cout << endl << endl;
}

void test03() {
    cout << "=== 测试 unordered_map 基本功能 ===" << endl;
    Myunordered_map::unordered_map<string, int> m;
    m.insert({"apple", 10}); m.insert({"banana", 20}); m.insert({"cherry", 30});
    m["date"] = 40; m["banana"] = 25;
    cout << "遍历键值对:" << endl;
    for (auto it = m.begin(); it != m.end(); ++it) cout << it->first << ": " << it->second << endl;
    string key = "cherry";
    auto it = m.find(key);
    if (it != m.end()) cout << "找到 " << key << ": " << it->second << endl;
    key = "banana";
    bool erase_ret = m.erase(key);
    if (erase_ret) cout << "删除 " << key << " 成功" << endl;
    cout << "删除后 " << key << " 是否存在:" << (m.find(key) != m.end() ? "是" : "否") << endl << endl;
}

void test04() {
    cout << "=== 测试 unordered_map [] 运算符 ===" << endl;
    Myunordered_map::unordered_map<int, string> m;
    m[1]; m[2] = "two"; m[2] = "second"; m[3] = "three";
    for (auto& kv : m) cout << kv.first << ": " << kv.second << endl;
    cout << endl;
}

void test05() {
    cout << "=== 测试边界情况 ===" << endl;
    Myunordered_set::unordered_set<int> s_empty;
    cout << "空 set 的 begin() == end(): " << (s_empty.begin() == s_empty.end() ? "true" : "false") << endl;
    cout << "删除空 set 中的元素:" << (s_empty.erase(100) ? "成功" : "失败") << endl << endl;
    Myunordered_map::unordered_map<int, int> mp_empty;
    cout << "空 map 的 begin() == end(): " << (mp_empty.begin() == mp_empty.end() ? "true" : "false") << endl;
    cout << "访问空 map 的 []: " << mp_empty[100] << endl;
}

int main() {
    test01(); test02(); test03(); test04(); test05();
    return 0;
}

运行结果

运行结果截图

核心设计解析

仿函数适配

由于 HashTable 是泛型实现,无法确定模板参数 T 具体是 K(如 unordered_set 的元素类型),还是 pair<K, V>(如 unordered_map 的键值对类型)。在插入操作时,需要用 K 对象转换为整数取模以及进行相等性比较。但 pair 的 value 本就不参与取模计算,且默认比较逻辑是对 key 和 value 一起比较,而我们实际只需要比较 K 对象。

因此,在 unordered_set 和 unordered_map 这一层,我们分别实现 SetKeyOfT 和 MapKeyOfT 这两个仿函数,传递给 HashTable 的 KeyOfT 参数。这样 HashTable 就能通过 KeyOfT 仿函数,从 T 类型对象里提取出 K 对象,再完成转换为整数取模以及 K 相等性比较的操作。

迭代器实现

unordered_set 和 unordered_map 的 iterator 实现框架与 list 类似:用一个类型封装'结点指针',再通过重载运算符,让迭代器能像指针一样完成访问操作。需要注意的是,哈希表的迭代器是单向迭代器(只能向后遍历,无法反向)。

关于可修改性:

  • unordered_map 的迭代器不允许修改 key,但允许修改 value。只需把 unordered_map 的键值对中'第一个参数(key)'设为 const K 即可。
  • unordered_set 的迭代器不支持修改 key,只需把 unordered_set 的第二个模板参数改为 const K 即可。

前置 ++ 运算符逻辑

迭代器内部维护一个'指向结点的指针',operator++ 实现时,要分两种情况:

  1. 如果当前桶还有后续结点,直接让指针指向下一个结点即可。
  2. 如果当前桶已经遍历完毕,就需要'寻找下一个桶'。

这里的关键设计是:迭代器中除了存'结点指针',还会存'哈希表对象的指针'。当当前桶遍历完时,可通过哈希表对象,结合 key 值计算当前桶位置,然后往后找'第一个不为空的桶',就能拿到下一批结点。

begin() 和 end() 的设计

  • begin():返回'第一个非空桶的第一个结点指针'构造的迭代器。
  • end():返回一个代表'遍历结束'的空迭代器(可用空指针等方式表示)。

[] 运算符重载

要让 unordered_map 支持 [] 运算符,关键在于调整 insert 相关的返回值逻辑。需要修改 HashTable 中的 Insert 函数,使其返回值类型变为 pair<Iterator, bool>。这样 unordered_map 的 operator[] 可以调用 insert,如果键存在则返回引用,如果不存在则插入新键值对(值为默认构造)并返回引用。

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

目录

  1. C++ STL 核心容器模拟:unorderedset 与 unorderedmap 实现
  2. 标准库中的实现原理
  3. 核心源码框架
  4. stl_hashtable.h
  5. stlhashset
  6. stlhashmap
  7. 代码实现
  8. 容器结构设计
  9. 头文件实现
  10. HashTable.h
  11. Myunordered_set.h
  12. Myunordered_map.h
  13. 测试文件:Test.cpp
  14. 运行结果
  15. 核心设计解析
  16. 仿函数适配
  17. 迭代器实现
  18. 前置 ++ 运算符逻辑
  19. begin() 和 end() 的设计
  20. [] 运算符重载
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Microsoft 365 Copilot Chat 与 Microsoft 365 Copilot 详细对比
  • 清华大学发布仿生多模态触觉传感器 SuperTac,结合大模型实现类人感知
  • Obsidian 同步新方案:坚果云官方插件免 WebDAV 配置及冲突合并
  • 数据结构:栈和队列的定义与实现
  • 学生成绩管理系统设计与实现:AI 辅助开发实战
  • 基于 AnalyticDB 与通义千问搭建 AI 智能客服
  • OpenClaw 开源桌面 Agent 部署与飞书钉钉集成实战指南
  • 2026 AI 编码工具对比:Claude、Cursor、Copilot 选型指南
  • DBeaver 社区版 AI 助手配置指南
  • AgentScope Java 智能体开发指南
  • 大模型训练数据白皮书发布:大模型是数据要素价值释放的最短路径
  • Ubuntu 24.04 离线安装 Ollama 及导入模型教程
  • 医疗场景下多智能体协同路径规划技术演进与建模
  • IntelliJ IDEA 接入 AI 编程助手:Copilot、DeepSeek 及 GPT-4o Mini
  • 璞致 PZ-VU9P/VU13P FPGA 开发板与 Xilinx UltraScale Plus 架构解析
  • Open WebUI MCPo 项目:将 MCP 工具无缝集成到 OpenAPI
  • C++ 设计模式实战:装饰者与适配器模式深度解析
  • ROS 2 机器人运行与 ros2 run 命令详解
  • 前端面试核心知识点汇总:JavaScript、CSS、HTML、框架及工程化
  • Git cherry-pick 命令详解

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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