从零手写迷你STL:用C++11实现简化版vector/map(附完整代码)
从零手写迷你STL用C11实现简化版vector/map附完整代码在C开发者的成长路径中理解STLStandard Template Library的内部实现机制是一个重要的里程碑。很多开发者能够熟练使用vector、map等容器却对其底层原理知之甚少。本文将带你从零开始用现代C11实现简化版的vector和map容器揭开STL设计的神秘面纱。1. 准备工作与环境配置在开始编码前我们需要明确几个核心目标第一实现一个支持动态扩容的vector类模板第二实现一个基于红黑树的map容器第三保证代码的异常安全性第四提供完整的迭代器支持。推荐使用支持C11及以上标准的编译器如GCC 7或Clang 5。创建一个新的头文件mini_stl.h作为我们的实现文件。为了测试方便可以同时创建一个test.cpp文件用于验证容器功能。// mini_stl.h 基础框架 #pragma once #include memory // for std::allocator #include utility // for std::pair #include initializer_list namespace mini { // 后续实现将放在这个命名空间内 }2. vector容器的实现2.1 基础结构与内存管理vector的核心是一个动态数组需要处理内存分配、元素构造和析构。我们使用std::allocator作为默认的内存分配器。namespace mini { template typename T, typename Allocator std::allocatorT class vector { public: using value_type T; using allocator_type Allocator; using size_type size_t; using reference T; using const_reference const T; using pointer T*; using const_pointer const T*; private: pointer m_data nullptr; size_type m_size 0; size_type m_capacity 0; allocator_type m_alloc; public: // 构造函数与析构函数 explicit vector(const Allocator alloc Allocator()) : m_alloc(alloc) {} ~vector() { clear(); m_alloc.deallocate(m_data, m_capacity); } }; }2.2 关键操作实现vector的核心操作包括push_back、pop_back、reserve和resize等。下面我们实现这些关键方法// 在vector类中添加以下方法 void push_back(const T value) { if (m_size m_capacity) { reserve(m_capacity 0 ? 1 : m_capacity * 2); } m_alloc.construct(m_data m_size, value); m_size; } void pop_back() { if (m_size 0) { --m_size; m_alloc.destroy(m_data m_size); } } void reserve(size_type new_capacity) { if (new_capacity m_capacity) return; pointer new_data m_alloc.allocate(new_capacity); for (size_type i 0; i m_size; i) { m_alloc.construct(new_data i, std::move(m_data[i])); m_alloc.destroy(m_data i); } m_alloc.deallocate(m_data, m_capacity); m_data new_data; m_capacity new_capacity; } void resize(size_type new_size, const T value T()) { if (new_size m_size) { reserve(new_size); for (; m_size new_size; m_size) { m_alloc.construct(m_data m_size, value); } } else if (new_size m_size) { for (; m_size new_size; --m_size) { m_alloc.destroy(m_data m_size - 1); } } }2.3 迭代器实现为了使我们的vector能够与标准算法兼容需要实现迭代器。下面是简化版的迭代器实现// 在vector类中添加以下定义 public: class iterator { public: using iterator_category std::random_access_iterator_tag; using value_type T; using difference_type ptrdiff_t; using pointer T*; using reference T; explicit iterator(pointer ptr) : m_ptr(ptr) {} reference operator*() const { return *m_ptr; } pointer operator-() const { return m_ptr; } iterator operator() { m_ptr; return *this; } iterator operator(int) { iterator tmp *this; m_ptr; return tmp; } // 其他必要操作符重载... private: pointer m_ptr; }; iterator begin() { return iterator(m_data); } iterator end() { return iterator(m_data m_size); }3. map容器的实现3.1 红黑树基础结构map的底层通常使用红黑树实现。我们先定义红黑树的节点结构和基本操作namespace mini { template typename Key, typename Value class rb_tree { private: enum class color { RED, BLACK }; struct node { std::pairconst Key, Value data; node* left nullptr; node* right nullptr; node* parent nullptr; color col color::RED; node(const Key k, const Value v, node* p nullptr) : data(k, v), parent(p) {} }; node* m_root nullptr; size_t m_size 0; public: ~rb_tree() { clear(m_root); } private: void clear(node* n) { if (n) { clear(n-left); clear(n-right); delete n; } } }; }3.2 红黑树插入操作红黑树的插入操作较为复杂需要处理多种情况以保持平衡// 在rb_tree类中添加以下方法 public: std::pairnode*, bool insert(const Key k, const Value v) { if (!m_root) { m_root new node(k, v); m_root-col color::BLACK; m_size; return {m_root, true}; } node* current m_root; node* parent nullptr; while (current) { parent current; if (k current-data.first) { current current-left; } else if (current-data.first k) { current current-right; } else { return {current, false}; // 键已存在 } } node* new_node new node(k, v, parent); if (k parent-data.first) { parent-left new_node; } else { parent-right new_node; } m_size; fix_insert(new_node); return {new_node, true}; } private: void fix_insert(node* n) { while (n ! m_root n-parent-col color::RED) { if (n-parent n-parent-parent-left) { node* uncle n-parent-parent-right; if (uncle uncle-col color::RED) { // 情况1叔叔节点是红色 n-parent-col color::BLACK; uncle-col color::BLACK; n-parent-parent-col color::RED; n n-parent-parent; } else { // 情况2和3叔叔节点是黑色 if (n n-parent-right) { n n-parent; rotate_left(n); } n-parent-col color::BLACK; n-parent-parent-col color::RED; rotate_right(n-parent-parent); } } else { // 对称情况... } } m_root-col color::BLACK; } void rotate_left(node* n) { node* right_child n-right; n-right right_child-left; if (right_child-left) { right_child-left-parent n; } right_child-parent n-parent; if (!n-parent) { m_root right_child; } else if (n n-parent-left) { n-parent-left right_child; } else { n-parent-right right_child; } right_child-left n; n-parent right_child; } void rotate_right(node* n) { // 对称实现... }3.3 map接口封装基于红黑树实现map的公共接口namespace mini { template typename Key, typename Value, typename Compare std::lessKey, typename Allocator std::allocatorstd::pairconst Key, Value class map { private: rb_treeKey, Value m_tree; public: using key_type Key; using mapped_type Value; using value_type std::pairconst Key, Value; using size_type size_t; Value operator[](const Key key) { auto result m_tree.insert(key, Value()); return result.first-data.second; } size_type size() const { return m_tree.size(); } bool empty() const { return size() 0; } // 其他接口方法... }; }4. 性能测试与优化4.1 vector性能测试实现完成后我们需要测试vector的性能特别是扩容策略的影响void test_vector_performance() { mini::vectorint v; const int N 1000000; auto start std::chrono::high_resolution_clock::now(); for (int i 0; i N; i) { v.push_back(i); } auto end std::chrono::high_resolution_clock::now(); std::cout mini::vector push_back time: std::chrono::duration_caststd::chrono::milliseconds(end - start).count() ms\n; std::vectorint std_v; start std::chrono::high_resolution_clock::now(); for (int i 0; i N; i) { std_v.push_back(i); } end std::chrono::high_resolution_clock::now(); std::cout std::vector push_back time: std::chrono::duration_caststd::chrono::milliseconds(end - start).count() ms\n; }4.2 map性能测试同样地我们需要测试map的插入和查找性能void test_map_performance() { mini::mapint, int m; const int N 100000; auto start std::chrono::high_resolution_clock::now(); for (int i 0; i N; i) { m[i] i * 2; } auto end std::chrono::high_resolution_clock::now(); std::cout mini::map insertion time: std::chrono::duration_caststd::chrono::milliseconds(end - start).count() ms\n; std::mapint, int std_m; start std::chrono::high_resolution_clock::now(); for (int i 0; i N; i) { std_m[i] i * 2; } end std::chrono::high_resolution_clock::now(); std::cout std::map insertion time: std::chrono::duration_caststd::chrono::milliseconds(end - start).count() ms\n; }4.3 优化建议根据测试结果可以考虑以下优化方向vector的扩容策略当前实现采用简单的2倍扩容可以考虑更精细的策略红黑树的节点分配使用内存池技术减少频繁的内存分配移动语义支持为容器添加移动构造函数和移动赋值操作符异常安全性确保在异常发生时资源能够正确释放5. 完整代码示例与使用指南将上述实现整合到一个头文件中并提供简单的使用示例// 示例使用mini::vector mini::vectorstd::string names; names.push_back(Alice); names.push_back(Bob); names.push_back(Charlie); for (const auto name : names) { std::cout name \n; } // 示例使用mini::map mini::mapint, std::string id_to_name; id_to_name[1] Alice; id_to_name[2] Bob; std::cout ID 2: id_to_name[2] \n;在实际项目中可以将这些容器作为学习工具或者在对标准库有特殊需求时使用。但请注意生产环境通常建议使用标准库实现因为它们经过了更充分的测试和优化。