前言
vector 本质上类似数组,也可以理解为一种泛型的 string。string 只能存储 char 类型,但是 vector 支持各种内置类型和自定义类型。本次将围绕模拟实现 vector 中遇到的问题进行分析。
文章目录
- 前言
- 一、确定思路
- 二、实现过程
- 2.1 查阅文档
- 2.2 验证正确性
- 三、易错问题
- 3.1 insert 导致迭代器失效
- 3.2 erase 导致迭代器失效
- 3.3 浅拷贝问题
- 四、完整代码
一、确定思路
同 string 一样,在进行模拟之前我们要确定 vector 的结构是怎么样的,以及有哪些函数可用。
由图中我们可以知道 vector 所需要的函数相对于 string 较少,这主要是因为 vector 更支持泛型,因此类似于 string 的字典序比较等都不需要实现,毕竟不是每一个类型都是字典序比较。
下面给出一个 vector 的实现框架。
namespace hty{
template<class T>
class vector {
private:
typedef T* iterator;
typedef const T* const_iterator;
iterator _start; // 指向数据块的开始
iterator _finish; // 指向有效数据的尾
iterator _endOfStorage; // 指向存储容量的尾
public:
// iterator
iterator begin() {
}
iterator end() {
}
const_iterator cbegin() const {
}
const_iterator cend() const {
}
// construct and destroy
vector() {}
vector(int n, const T& value = T()) {
}
vector(size_t n, const T& value = T()) {
}
template<class InputIterator>
vector(InputIterator first, InputIterator last) {
}
vector(const vector<T>& v) {
}
vector<T>& operator= (vector<T> v) {
}
~vector() {
}
// capacity
size_t size() const {
}
size_t capacity() const {
}
bool empty() {
}
void clear() {
}
void reserve(size_t n) {
}
void resize(size_t n, const T& value = T()) {
}
//access
T& operator[](size_t pos) {
}
const T& operator[](size_t pos)const {
}
// modify
void push_back(const T& x) {
}
void pop_back() {
}
void swap(vector<T>& v) {
}
iterator insert(iterator pos, const T& x) {
}
iterator erase(iterator pos) {
}
};
}
二、实现过程
2.1 查阅文档
和模拟 string 时一样,模拟最重要的一步还是查阅文档,了解函数参数和功能。例如之前模拟实现 string 时,模拟 insert 函数传参是插入位置的下标,但是在 vector 中我们则传入的是迭代器。诸此之类的细节都需要在查阅文档后了解。
2.2 验证正确性
比起 string,vector 更需要对正确性的验证。虽然看似两者功能相近,但是由于 vector 更多时候面向的是自定义类型,因此更容易出现错误,而此类错误往往是连锁性的。
三、易错问题
在模拟实现过程中有两个常见的迭代器失效问题,这两处错误非常容易出现,分别由 insert 和 erase 引起。
3.1 insert 导致迭代器失效
insert 导致迭代器失效的原因在扩容。当我们对一个迭代器所指向位置插入数据时,其余数据向后移动。在原本剩余空间充足的情况下迭代器正常,但是如果剩余空间不够,需要扩容时,很多人容易忽略原 pos 迭代器会失效,具体失效原理如下:
3.2 erase 导致迭代器失效
erase 不存在扩容等改变原数组空间分配的情况,乍一看似乎不容易出现错误,但是在 string 中我们提到要注意特殊情况的正确性验证。如果我们删除的是最后一个位置的元素,那么之后 pos 迭代器指向的就是一块没有意义的地址。对于 vs 等迭代器非原生指针实现的情况,如果检查严格,就会中断报错。具体失效原理如下:
3.3 浅拷贝问题
不同于 string,vector 更多面向自定义类型。类似于重载 = 运算符,string 可以直接调用 memcpy 进行实现,但是对于 vector 则不行,vector 内容中也可能包含指针,需要自行开辟空间。最经典的例子就是 vector<vector>,因此在实现过程中,许多原本在 string 中 memcpy 可以解决的问题,我们需要用循环 + new 进行实现。这更体现了 C++ new 用于实现类型初始化的特性。
四、完整代码
namespace hty {
template<class T>
class vector {
private:
typedef T* iterator;
typedef const T* const_iterator;
iterator _start; // 指向数据块的开始
iterator _finish; // 指向有效数据的尾
iterator _endOfStorage; // 指向存储容量的尾
public:
// iterator
iterator begin() {
return _start;
}
iterator end() {
return _finish;
}
const_iterator cbegin() const {
return _start;
}
const_iterator cend() const {
return _finish;
}
// construct and destroy
vector() : _start(nullptr), _finish(nullptr), _endOfStorage(nullptr) {}
vector(int n, const T& value = T()) : _start(nullptr), _finish(nullptr), _endOfStorage(nullptr) {
resize(n, value);
}
vector(size_t n, const T& value = T()) : _start(nullptr), _finish(nullptr), _endOfStorage(nullptr) {
resize(n, value);
}
template<class InputIterator>
vector(InputIterator first, InputIterator last) : _start(nullptr), _finish(nullptr), _endOfStorage(nullptr) {
while (first != last) {
push_back(*first);
first++;
}
}
vector(const vector<T>& v) {
vector<T> tmp(v.begin(), v.cend());
swap(v);
}
vector<T>& operator= (vector<T> v) {
swap(v);
return *this;
}
~vector() {
if (_start) {
delete[] _start;
}
_start = _finish = _endOfStorage = nullptr;
}
// capacity
size_t size() const {
return _finish - _start;
}
size_t capacity() const {
return _endOfStorage - _start;
}
bool empty() {
return _start == _finish;
}
void clear() {
_finish = _start;
}
void reserve(size_t n) {
if (n > capacity()) {
size_t old_size = size();
T* tmp = new T[n];
if (_start) {
for (int i = 0; i < old_size; i++)
tmp[i] = _start[i];
delete[] _start;
}
_start = tmp;
_finish = _start + old_size;
_endOfStorage = _start + n;
}
}
void resize(size_t n, const T& value = T()) {
if (n > capacity())
reserve(n);
if (n > size()) {
for (int i = size(); i < n; i++)
_start[i] = value;
}
_finish = _start + n;
}
//access
T& operator[](size_t pos) {
return _start + pos;
}
const T& operator[](size_t pos)const {
return _start + pos;
}
// modify
void push_back(const T& x) {
if (size() == capacity()) {
size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(new_capacity);
}
*_finish = x;
_finish++;
}
void pop_back() {
assert(_finish > _start);
--_finish;
}
void swap(vector<T>& v) {
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endOfStorage, v._endOfStorage);
}
iterator insert(iterator pos, const T& x) {
assert(pos >= _start && pos <= _finish);
if (size() == capacity()) {
size_t len = pos - _start;
size_t new_capacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(new_capacity);
pos = _star + len;
}
for (auto i = _finish; i > pos; i--)
*i = *(i - 1);
*pos = x;
_finish++;
return pos;
}
iterator erase(iterator pos) {
assert(pos >= _start && pos < _finish);
for (auto x = pos; x < _finish - 1; x++)
*x = *(x + 1);
--_finish;
return pos;
}
};
}