前言
在C++标准模板库(STL)中,vector
是最常用且功能强大的容器之一。它提供了动态数组的功能,能够自动管理内存,支持随机访问,并在尾部高效地插入和删除元素。本文将深入探讨如何手动实现一个简化版的vector
容器,并分析其中的关键技术和设计思路。
Vector的基本结构
cpp
template <class T> class vector { public: typedef T* iterator; typedef const T* const_iterator; private: iterator _start = nullptr; // 指向数据起始位置 iterator _finish = nullptr; // 指向最后一个元素的下一个位置 iterator _end_of_storage = nullptr; // 分配内存的末尾 };
Vector使用三个指针来管理动态数组:
-
_start
: 指向数组的起始位置 -
_finish
: 指向最后一个元素的下一个位置(即当前元素数量) -
_end_of_storage
: 指向分配内存的末尾
构造函数与析构函数
默认构造函数
cpp
vector() = default; // C++11特性,使用编译器生成的默认构造函数
迭代器范围构造函数
cpp
template<class InputIterator> vector(InputIterator first, InputIterator end) { while (first != end) { push_back(*first); first++; } }
这个模板构造函数允许从任何迭代器范围初始化vector,提供了极大的灵活性。
特定数量元素构造函数
cpp
vector(size_t n, const T& val = T()) { reserve(n); for (size_t i = 0; i < n; i++) { push_back(val); } }
为了避免与迭代器构造函数冲突,需要为不同整数类型提供重载版本(int、long、long long等)。
拷贝构造函数与赋值运算符
cpp
vector(const vector<T>& v) { reserve(v.size()); for (auto& e : v) { push_back(e); } } vector<T>& operator=(const vector<T>& v) { if (this != &v) // 防止自赋值 { clear(); reserve(v.size()); for (auto& e : v) { push_back(e); } } return *this; }
析构函数
cpp
~vector() { delete[] _start; _start = _finish = _end_of_storage = nullptr; }
核心功能实现
内存管理:reserve()
cpp
void reserve(size_t n) { if (n > capacity()) { T* temp = new T[n]; const size_t old_size = size(); // 深拷贝元素 for (size_t i = 0; i < old_size; i++) { temp[i] = _start[i]; } delete[] _start; _start = temp; _finish = temp + old_size; _end_of_storage = temp + n; } }
关键点:不能使用memcpy
进行浅拷贝,必须对每个元素进行深拷贝,特别是对于自定义类型(如string)。
元素访问
cpp
T& operator[](size_t n) { assert(n < size()); return _start[n]; } const T& operator[](size_t n) const { assert(n < size()); return _start[n]; }
迭代器
cpp
iterator begin() { return _start; } iterator end() { return _finish; } const_iterator begin() const { return _start; } const_iterator end() const { return _finish; }
插入操作
cpp
iterator insert(iterator pos, const T& x) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _end_of_storage) { size_t len = pos - _start; // 保存相对位置 reserve(capacity() == 0 ? 4 : 2 * capacity()); pos = _start + len; // 更新pos位置 } // 向后移动元素 iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; end--; } *pos = x; _finish++; return pos; } void push_back(const T& v) { if (_finish == _end_of_storage) { reserve(capacity() == 0 ? 4 : 2 * capacity()); } *_finish = v; _finish++; }
删除操作
cpp
void erase(iterator pos) { assert(pos < _finish); assert(pos >= _start); iterator it = pos + 1; while (it != end()) { *(it - 1) = *it; it++; } _finish--; } void pop_back() { assert(!empty()); _finish--; }
大小调整:resize()
cpp
void resize(size_t n, const T& t = T()) { if (n < size()) { _finish = _start + n; // 截断 } else { reserve(n); while (_finish != _start + n) { *(_finish++) = t; // 填充新元素 } } }
关键技术与注意事项
-
模板实现:模板类必须在头文件中完整实现,不能将声明与定义分离,否则会导致链接错误。
-
深拷贝问题:在
reserve()
中必须使用元素级别的拷贝而非memcpy
,确保自定义类型正确拷贝。 -
迭代器失效:插入或删除操作可能导致迭代器失效,需要在实现中特别注意。
-
异常安全:在实际生产代码中需要考虑异常安全性,确保资源不会泄漏。
-
类型萃取:标准库实现中使用类型萃取技术优化拷贝操作,对POD类型使用memcpy。
测试示例
cpp
void test1() { vector<string> v1; v1.push_back("1"); v1.push_back("2"); v1.push_back("3"); v1.push_back("4"); v1.push_back("5"); Print_Vector(v1); }
总结
通过手动实现vector容器,我们深入理解了动态数组的内部工作机制,包括:
-
内存管理与自动扩容
-
迭代器设计与实现
-
元素访问与修改
-
插入删除操作的时间复杂度分析
-
深拷贝与浅拷贝的区别
这种实现虽然简化,但涵盖了vector的核心功能,对于理解STL容器的设计思想和内部机制非常有帮助。在实际开发中,建议直接使用标准库的vector,它经过充分优化和测试,具有更好的性能和稳定性。
理解底层实现的价值在于能够更有效地使用标准库,并在需要时能够扩展或定制容器行为。