概述这是一个完整的模板类yyq::vector的实现模仿 C 标准库中的std::vector。作为STL最重要的容器之一vector的动态数组实现展示了C模板编程、内存管理和迭代器设计的核心技术。本文将全面分析该实现不遗漏任何细节。目录概述一、整体架构与设计1.1 命名空间与头文件保护1.2 类成员变量设计二、迭代器与类型定义2.1 迭代器类型定义2.2 迭代器访问函数三、构造函数与析构函数3.1 默认构造函数3.2 拷贝构造函数3.3 指定大小和初始值的构造函数3.4 析构函数四、赋值操作符与交换函数4.1 交换函数4.2 赋值操作符现代实现4.3 被注释的传统实现五、容量管理5.1 size和capacity5.2 empty函数5.3 reserve函数重要5.4 resize函数六、元素访问6.1 operator[]重载七、修改操作7.1 push_back函数7.2 pop_back函数7.3 insert函数7.4 erase函数7.5 被注释的clear函数八、辅助函数8.1 print_vector函数8.2 print_container函数通用版本九、代码中的特殊字符问题十、设计亮点与改进建议10.1 设计亮点10.2 存在的问题与改进建议10.3 改进的reserve实现示例十一、完整性与学习价值十二、总结一、整体架构与设计1.1 命名空间与头文件保护cppnamespace yyq { templateclass T class vector { ... }; }使用自定义命名空间yyq避免与标准库冲突模板类设计支持任意类型的元素#pragma once防止头文件重复包含1.2 类成员变量设计cppprivate: iterator _start nullptr; // 指向数组起始位置 iterator _finish nullptr; // 指向最后一个元素的下一个位置 iterator _end_of_storage nullptr; // 指向分配内存的末尾采用三指针设计这是vector的经典实现方式_start数组起始位置_finish有效元素末尾的下一个位置size _finish - _start_end_of_storage分配内存的末尾capacity _end_of_storage - _start二、迭代器与类型定义2.1 迭代器类型定义cpptypedef T* iterator; typedef const T* const_iterator;简单地将指针作为迭代器因为vector在内存中是连续的提供const和非const版本支持常对象2.2 迭代器访问函数cppiterator begin() { return _start; } iterator end() { return _finish; } const_iterator begin()const { return _start; } const_iterator end()const { return _finish; }提供完整的迭代器支持使vector可以与STL算法配合使用支持基于范围的for循环三、构造函数与析构函数3.1 默认构造函数cpp// C11 默认生成默认构造函数 vector() default;使用C11的 default语法成员变量已初始化为nullptr确保安全3.2 拷贝构造函数cppvector(const vectorT v) { reserve(v.size()); for (auto e : v) { push_back(e); } }先分配与源vector相同大小的内存使用范围for循环遍历并复制每个元素注意这里使用了push_back会调用元素的赋值操作3.3 指定大小和初始值的构造函数cppvector(size_t n, const T val T()) { reserve(n); for (size_t i 0; i n; i) { push_back(val); } } vector(int n, const T val T()) { reserve(n); for (int i 0; i n; i) { push_back(val); } }重要细节两个重载版本size_t和int参数这是为了解决模板推导时的歧义问题使用T()作为默认初始值调用类型的默认构造函数例如vectorint v(10)创建包含10个0的vector例如vectorint v(10, 5)创建包含10个5的vector3.4 析构函数cpp~vector() { if (_start) { delete[] _start; _start _finish _end_of_storage; } }检查_start是否为nullptr再删除删除后将所有指针置为nullptr避免悬空指针注意对于自定义类型会调用每个元素的析构函数四、赋值操作符与交换函数4.1 交换函数cppvoid swap(vectorT v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_end_of_storage, v._end_of_storage); }使用std::swap交换三个指针效率高仅交换指针不复制元素是拷贝交换技法的核心4.2 赋值操作符现代实现cppvectorT operator(vectorT temp) { swap(temp); return *this; }这是经典的拷贝并交换技法参数temp是值传递会自动调用拷贝构造函数交换当前对象和temp的资源temp离开作用域时自动析构释放原资源异常安全且自动处理自赋值4.3 被注释的传统实现cpp// 传统赋值操作符实现 //vectorT operator(const vectorT v) //{ // if (this ! v) // 检查自赋值 // { // clear(); // 清空当前内容 // reserve(v.size()); // 分配足够空间 // for (auto e : v) // 复制元素 // { // push_back(e); // } // } // return *this; //}显示检查自赋值先清空再重新分配和复制现代实现更简洁优雅五、容量管理5.1 size和capacitycppsize_t size()const { return _finish - _start; // 有效元素个数 } size_t capacity()const { return _end_of_storage - _start; // 分配的内存容量 }使用指针算术计算大小和容量都是const成员函数5.2 empty函数cppbool empty() { return _start _finish; // 起始等于结束表示空 }检查vector是否为空注意这里应该是const成员函数5.3 reserve函数重要cppvoid reserve(size_t n) { if (n capacity()) { size_t old_size size(); T* temp new T[n]; // 重要不能使用memcpy因为T可能是自定义类型 for (size_t i 0; i size(); i) { temp[i] _start[i]; // 使用赋值操作符 } delete[] _start; _start temp; _finish _start old_size; _end_of_storage _start n; } }关键细节为什么不用memcpycpp// 被注释的错误实现 // memcpy(temp, _start, size() * sizeof(T));memcpy是浅拷贝对于包含指针或资源的自定义类型会出问题使用循环赋值可以调用元素的赋值操作符正确处理深拷贝扩容策略只扩容不缩容先分配新内存再复制元素最后释放旧内存保存旧的大小因为size()在释放后可能失效5.4 resize函数cppvoid resize(size_t n, T val T()) { if (n size()) // 缩小 { _finish _start n; // 简单截断 } else // 扩大 { reserve(n); // 确保有足够容量 while (_finish _start n) { *_finish val; // 用val填充新元素 _finish; } } }如果新大小小于当前大小简单截断如果新大小大于当前大小用指定值填充新位置使用T()作为默认填充值六、元素访问6.1 operator[]重载cppT operator[](size_t i) { assert(i size()); return _start[i]; // 返回引用可修改 } const T operator[](size_t i)const { assert(i size()); return _start[i]; // 返回const引用只读 }提供非常量和常量两个版本使用assert进行边界检查支持随机访问时间复杂度O(1)七、修改操作7.1 push_back函数cppvoid push_back(const T x) { if (_finish _end_of_storage) // 需要扩容 { reserve(capacity() 0 ? 4 : 2 * capacity()); // 2倍扩容 } *_finish x; // 在末尾添加元素 _finish; // 更新_finish指针 }扩容策略初始容量为0时扩容到4后续按2倍扩容vector的经典策略使用引用参数避免不必要的复制7.2 pop_back函数cppvoid pop_back() { assert(!empty()); // 确保不为空 --_finish; // 简单递减_finish }仅减少大小不释放内存元素会被保留在内存中但不再可访问对于自定义类型需要显式调用析构函数当前实现有缺陷7.3 insert函数cppiterator insert(iterator pos, const T x) { // 检查是否需要扩容 if (_finish _end_of_storage) { size_t len pos - _start; // 保存偏移量 reserve(capacity() 0 ? 4 : 2 * capacity()); pos _start len; // 重新计算pos因为_start可能改变 } // 向后移动元素 iterator end _finish - 1; while (end pos) { *(end 1) *end; --end; } *pos x; // 插入新元素 _finish; // 更新大小 return pos; // 返回插入位置的迭代器 }重要细节迭代器失效问题扩容后原来的pos会失效需要重新计算保存偏移量len pos - _start扩容后pos _start len从后向前移动避免覆盖未处理的元素返回值返回指向新插入元素的迭代器符合STL规范7.4 erase函数cppiterator erase(iterator pos) { assert(pos _start); // 确保pos有效 assert(pos _finish); iterator it pos 1; while (it ! end()) // 向前移动元素 { *(it - 1) *it; it; } --_finish; // 更新大小 return pos; // 返回被删除元素的下一个位置 }检查pos的合法性从前向后移动元素覆盖要删除的位置返回被删除元素的下一个位置符合STL规范注意对于自定义类型应该显式调用析构函数7.5 被注释的clear函数cpp//void clear() //{ // _finish _start; // 简单重置_finish //}简单实现仅重置_finish指针问题对于自定义类型没有调用析构函数更好的实现应该调用每个元素的析构函数八、辅助函数8.1 print_vector函数cpptemplateclass T void print_vector(const vectorT v) { // 方法1使用迭代器 typename vectorT::const_iterator it v.begin(); while (it ! v.end()) { cout *it ; it; } cout endl; // 方法2使用范围for循环 for (auto e : v) { cout e ; } cout endl; }关键细节typename关键字告诉编译器vectorT::const_iterator是一个类型两种遍历方式传统迭代器和范围for循环模板函数可以打印任意类型的vector8.2 print_container函数通用版本cpptemplateclass Container void print_container(const Container v) { // 与方法1相同但更通用 typename Container::const_iterator it v.begin(); while (it ! v.end()) { cout *it ; it; } cout endl; for (auto e : v) { cout e ; } cout endl; }更通用的版本可以打印任何支持迭代器的容器使用了模板模板参数展示了泛型编程的思想十、设计亮点与改进建议10.1 设计亮点现代C风格使用 default、拷贝交换技法完整的迭代器支持与STL完全兼容异常安全赋值操作符的现代实现是异常安全的模板设计支持任意类型合理的扩容策略2倍扩容平衡性能和内存使用10.2 存在的问题与改进建议析构问题pop_back、erase、clear没有调用元素的析构函数对于自定义类型可能造成资源泄漏异常安全reserve中如果元素赋值抛出异常会内存泄漏应该先复制到临时内存成功后再交换缺少的功能cpp// 应该添加以下功能 T front(); // 访问第一个元素 T back(); // 访问最后一个元素 void shrink_to_fit(); // 缩减内存性能优化可以使用移动语义C11push_back可以添加右值引用版本可以添加emplace_back直接构造const正确性cppbool empty() const // 应该是const成员函数迭代器失效需要更完善的迭代器失效处理文档说明哪些操作会使迭代器失效10.3 改进的reserve实现示例cppvoid reserve(size_t n) { if (n capacity()) { T* temp new T[n]; size_t i 0; try { for (; i size(); i) { temp[i] _start[i]; // 可能抛出异常 } } catch (...) { // 异常时清理已构造的元素 for (size_t j 0; j i; j) { temp[j].~T(); // 显式调用析构 } delete[] temp; throw; // 重新抛出异常 } // 成功则替换 for (size_t j 0; j size(); j) { _start[j].~T(); // 析构旧元素 } delete[] _start; _finish temp i; _start temp; _end_of_storage _start n; } }十一、完整性与学习价值这个yyq::vector实现涵盖了动态数组的核心功能✅ 模板类设计✅ 动态内存管理✅ 迭代器支持✅ 基本操作增删改查✅ 容量管理✅ 拷贝控制✅ 运算符重载十二、总结这个vector实现展示了C模板编程和容器设计的多个重要方面模板元编程通用的容器设计内存管理动态数组的分配与释放迭代器设计连续容器的迭代器实现异常安全拷贝交换技法的应用性能考虑2倍扩容策略虽然与标准库的std::vector相比还有差距如缺少移动语义、异常安全不够完善但它作为一个教学实现完美展示了vector的核心原理是学习C容器设计和模板编程的优秀范例。通过分析这个实现我们可以深入理解vector的内部存储机制动态扩容的原理和代价迭代器失效的原因和应对模板类设计的基本模式现代C编程的最佳实践