自学内容网 自学内容网

Vector的模拟实现与迭代器失效问题

一.Vector的介绍与使用

vector的英文解释是向量的意思,向量是序列容器,表示大小可以变化的数组。就像数组一样是连续存储的可以通过指针偏移来访问元素。但与数组不同的是它的大小可以动态变化,其存储由容器自动处理。在存储数据时容器可能会分配一些额外的空间来适应可能的增长,因此实际的容器可能大于包含其元素严格需要的存储空间。

二.Vector的实现

接下来我会通过vector的基本功能用法,迭代器的模拟,以及构造析构方面进行模拟实现。

首先搭好架子,创建一个类模板T,将T的指针定义为iterator后续用来模拟实现迭代器,_start表示指向首位的指针,_finish表示指向有效元素末尾的指针,_end_of_storage表示容器最大存储量。

namespace bicup
{
template<class T>//类型模板
class vector     //类型
{
public:
typedef T* iterator;//类型指针

iterator begin()
{
return _start;
}

iterator end()
{
return _finish;
}

private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}

1.构造析构函数 

 1.构造与析构

下面书写一下构造函数和析构函数,当然这里的构造函数我们先写一部分,后面根据需要来进行补充修改。

namespace bicup
{
template<class T>
class vector
{
public:
typedef T* iterator;
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}

~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}

需要注意这里的构造在 :下要用()而不能用 = 直接初始化 ,析构时需要判断vector内是否有数据。

2.拷贝函数

  下面是拷贝构造

namespace bicup
{
template<class T>//类型模板
class vector     //类型
{
public:
typedef T* iterator;//类型指针
typedef const T* const_iterator;

vector(const vector<T>& v)
{
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}

/*void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}

vector<T>& operator=(const vector<T>& v)
{
swap(v);
return *this;
}*/

vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}
return *this;
}

private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
}

这里有两种表示方法,v2(v1)或者v2 = v1,此处没有太多的难点。 

2.功能函数模拟 

 下面来实现尾插,首先对vector进行判断,是否有空间插入数据,若没有则需要进行扩容。在扩容这部分操作我们需要用到reserve以及vector空间大小capacity和实际大小size,我们在下面依次实现。为了节省空间以及更加清楚的展示,每个板块只放上核心代码部分,当然在最后我会将所有代码一次性放上。

1.reverse与push_back

#pragma once

namespace bicup
{
template<class T>
class vector
{
public:

size_t size() const
{
return _finish - _start;
}

size_t capacity() const
{
return _end_of_storage - _start;
}

void reserve(size_t n)
{
if (n > capacity())
{
size_t oldsize = size();
T* tmp = new T[n];
memcpy(tmp, _start, sizeof(T) * oldsize);
delete[] _start;
_start = tmp;
_finish = _start + oldsize;
_end_of_storage = _start + n;
}
}

void push_back(const T&x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
*_finish = x;
_finish++;
}

private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}

reverse部分中,需要判断扩容是否大于capacity, 若小于capacity则不会改变容量,若大于capacity我们不能在原有的空间上进行添加数据。我们需要重新的开辟一块新的空间,new出n个单位的空间,通过memcpy将原有的数据转移到新开辟的空间上,回收原有的内存空间。将tmp首元素地址给_start,更新_finish和_end_of_storage。

当然这块并不是最终的代码,下面我们再来看一个例子

int main()
{
bicup::vector<string> v1;
v1.push_back("1111111111111111");
v1.push_back("111111111111111111");
v1.push_back("1111111111111111111");
v1.push_back("111111111111111111");
v1.push_back("11111111111111111111");

for (const auto& e : v1)
{
cout << e << " ";
}
cout << endl;
return 0;
}

当我们运行这串代码后发现

 这是什么原因导致的呢?

这里使用了一个memcpy,它属于浅拷贝,它通过首元素的地址对整个string进行拷贝,所以当vector扩容之后tmp会与_start指向同一个位置,最后会被delete[ ] 析构掉。所以看到的就是乱码。

那我们该怎么调整呢?

很简单,不使用memcpy就可以了

namespace bicup
{
template<class T>//类型模板
class vector     //类型
{
public:
typedef T* iterator;//类型指针
typedef const T* const_iterator;

void reserve(size_t n)
{
if (n > capacity())
{
size_t oldsize = size();
T* tmp = new T[n];
//memcpy(tmp, _start, sizeof(T) * oldsize);
for (size_t i = 0; i < oldsize; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
_finish = _start + oldsize;
_end_of_storage = _start + n;
}
}

private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
}

 2. [ ] 运算符重载

接下来测试一下push_back,我们希望像数组一样用 [ ] 的方式来表示下标元素,这就需要用到运算符重载。

#pragma once
#include <string>
#include <assert.h>

namespace bicup
{
template<class T>//类型模板
class vector     //类型
{
public:
typedef T* iterator;//类型指针

    T& operator[](size_t i)
{
assert(i < size());
return _start[i];//等于*(_start + i)
}

private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}

这里的本质就是以指针_start为起始点+i个单位之后解引用得到的数据。 可以用_start [ i ] 来简便书写。

测试代码:

int main()
{
bicup::vector<int> vv;
vv.push_back(2);
vv.push_back(3);
vv.push_back(5);

for (const auto &e : vv)
{
cout << e << " ";
}
cout << endl;

for (size_t i = 0; i < vv.size(); i++)
{
cout << vv[i] << " ";
}
cout << endl;
return 0;
}

3.迭代器模拟

当我们需要测试const类型的vector时,原先的下标以及迭代器就用不了了,需要重新进行函数重载。

例如下面的测试代码:

void func(const bicup::vector<int>& v)
{
for (size_t i = 1; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
bicup::vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
}

当然上述的代码是跑不起来的,原因是const进行了函数的权限缩小,但是我们还没有进行函数重载。这里需要注意,我们不能直接在 bicup::vector<int>::iterator it = v.begin() 前面加const,因为这里加的const就是修饰迭代器本身不能达到我们的目的,我们需要const来修饰的是vector里的数据。所以请看

namespace bicup
{
template<class T>//类型模板
class vector     //类型
{
public:
typedef T* iterator;//类型指针
typedef const T* const_iterator;

iterator begin()
{
return _start;
}

const_iterator begin() const
{
return _start;
}

iterator end()
{
return _finish;
}

const_iterator end() const
{
return _finish;
}

T& operator[](size_t i)
{
assert(i < size());
return _start[i];//等于*(_start + i)
}

const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];//等于*(_start + i)
}


private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}

 我们需要在typedef处添加const修饰,随之而来的我们需要重载一份const的函数begin() end() 以及 [ ] 。

下面是更正后的写法:

void func(const bicup::vector<int>& v)
{
for (size_t i = 1; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
bicup::vector<int>::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
}

 三.迭代器失效问题

1.野指针

接下来是本篇的重点部分,关于迭代器失效的问题。我们先来看一串代码。

namespace bicup
{
template<class T>//类型模板
class vector     //类型
{
public:
typedef T* iterator;//类型指针
typedef const T* const_iterator;

void insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos < _finish);
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
iterator it = _finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
it--;
}
*pos = x;
_finish++;
}

private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}

 我们来看insert这部分逻辑,空间不够扩容,移动数据,插入数据,更新_finish 好像没有什么问题。事实也是如此,当我们用5个数据测试时并没有找出问题,但当我们用4个数据测试时,程序就崩溃了。原因是vector内只有4个数据时要插入数据,那么就会涉及到扩容,扩容的时候需要将数据移动到另一块空间上。但是此时vector内的 _start 仍然指向原空间首位,所以需要我们对其进行更新。

正确写法:

#pragma once
#include <string>
#include <assert.h>

namespace bicup
{
template<class T>//类型模板
class vector     //类型
{
public:
typedef T* iterator;//类型指针
typedef const T* const_iterator;

void 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());
_start = len;
}
iterator it = _finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
it--;
}
*pos = x;
_finish++;
}


private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}

2.编译器强制检查

 当然迭代器失效的情况不止这一种,下面我们来继续探讨。

int main()
{
bicup::vector<int> vv;
vv.push_back(2);
vv.push_back(3);
vv.push_back(5);
vv.push_back(4);

int i;
cin >> i;
auto it = vv.begin() + i;
vv.insert(it, 2);
cout << *it << " ";

return 0;
}

当我们创建一个变量 it  来传参时,函数的形参会改变但是实参并没有进行更新,所以 it 无法找到扩容之后的pos位置。有的同学可能会疑惑,那不能在函数参数上添加 & 吗?当然这也是不行的,虽然添加之后 it 会进行更新,但参数一旦添加常量之后(it + 1)就无法运行通过了。所以,规定了只要使用了insert函数,迭代器就会失效(不使用)。

 接下来我们看下一个经典的迭代器失效。

int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);

//删除偶数
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
v.erase(it);
}
it++;
}
}

这个操作是对偶数项进行删除,但是当我们在vs下编译时确没有结果显示。 

这是由于编译器的设定 ,在vs下当编译器当程序erase之后,认为迭代器失效会将其标记,一旦访问就会报错。

总结,迭代器的失效主要是由两种原因导致,一个是野指针另一个是强制检查标记。虽然不同编译器对于erase和insert的迭代器处理不同,但我们默认使用了erase和insert后迭代器失效。

四.完整代码

#pragma once
#include <string>
#include <assert.h>

namespace bicup
{
template<class T>//类型模板
class vector     //类型
{
public:
typedef T* iterator;//类型指针
typedef const T* const_iterator;

iterator begin()
{
return _start;
}

const_iterator begin() const
{
return _start;
}

iterator end()
{
return _finish;
}

const_iterator end() const
{
return _finish;
}

T& operator[](size_t i)
{
assert(i < size());
return _start[i];//等于*(_start + i)
}

const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];//等于*(_start + i)
}

vector()
{}

size_t size() const
{
return _finish - _start;
}

size_t capacity() const
{
return _end_of_storage - _start;
}

void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
_finish++;
}
}
}

void reserve(size_t n)
{
if (n > capacity())
{
size_t oldsize = size();
T* tmp = new T[n];
//memcpy(tmp, _start, sizeof(T) * oldsize);
for (size_t i = 0; i < oldsize; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
_finish = _start + oldsize;
_end_of_storage = _start + n;
}
}

void push_back(const T&x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
*_finish = x;
_finish++;
}

void pop_back()
{
assert(_finish > _start);
_finish--;
}

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());
_start = len;
}
iterator it = _finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
it--;
}
*pos = x;
_finish++;
return pos;
}

iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it < _finish)
{
*(it - 1) = *it;
it++;
}
_finish--;
return pos;
}

vector(const vector<T>& v)
{
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}

/*void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}

vector<T>& operator=(const vector<T>& v)
{
swap(v);
return *this;
}*/

vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}
return *this;
}

~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
}

五.结语 

创作不易,希望大家多多支持!!! 


原文地址:https://blog.csdn.net/2401_86449430/article/details/145179297

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!