vector介绍:
STL(Standard Template Library)中的vector是一种动态数组容器,可以根据需要自动调整大小。vector可以存储任意类型的数据,并提供了一系列操作函数和方法来访问、插入和删除元素。
vector的模拟实现:
类内成员变量:
在vector中成员变量的类型不像string中的那样,而是泛形类型。
typedef T* iterator;//迭代器
typedef const T* const_iterator;//const类型迭代器
在vector中迭代器的使用是更加频繁的·。迭代器是一个行为类似于指针对象。
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
构造函数:
官方实现构造函数的形式有很多种,本文主要实现了三种。
一、
vector()
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
二、
将数组内部初始化为相同的内容。在进行实现时选择复用了一些已经写好的函数例如:reverse,push_back。这些函数在本文会一一实现。
vector(size_t n,const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
for(size_t i = 0;0<n;i++)
{
push_back(val);
}
}
vector(int n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
三、
利用迭代器进行初始化。我们先来看看官方时如何实现的。
官方在这里用到InputIterator理论上我们可以传任何类型的数据。有时我们想要用第二种方法去初始化时,编译器会使用第三种。
void test4()
{
vector<int> v1(5, 0);
vector<int>::iterator it = v1.begin();
while(it != v1.end())
{
cout << *it;
it++;
}
cout << endl;
}
当我们尝试运行test4时会进行报错。
我的本意是想要通过方法二去对vector进行构造,但是编译器调用的是方法三,因为我们传的是int类型需要进行隐式转换类型编译器会去寻找最适合的。在这里我是直接再加了一个构造函数,新加的构造函数与方法二中的函数的不同点是修改了函数参数,将size_t换成了int。
析构函数
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
释放空间且将指针置空即可。
拷贝构造
为了防止有vector<vector<int>>等类型的拷贝,我们要在拷贝的时候全部交换,为了防止交换不了的情况我们需要自己写一个交换函数,这个交换函数调用库中的swap函数即可。同时重载=也是要有的,不进行重载的话赋值会有问题。
vector(const vector<T>& val)
{
vector<T> tmp(val.begin(), val.end());
swap(tmp);
}
void swap(vector<T>& val)
{
std::swap(_start, val._start);
std::swap(_finish, val._finish);
std::swap(_end_of_storage, val._end_of_storage);
}
vector<T>& operator =(vector<T> val)
{
swap(val);
return *this;
}
begin
返回_start。
iterator begin()
{
return _start;
}
const iterator begin()const
{
return _start;
}
end
返回_finish。
iterator end()
{
return _finish;
}
const iterator end()const
{
return _finish;
}
size
返回空间内可存放元素的空间。
size_t size() const
{
return _finish - _start;
}
capacity
size_t capacity() const
{
return _end_of_storage - _start;
}
reserve
扩容函数,如果原先的数组内有内容,我们需要去拷贝。并需要更新_finish,_end_of_storage
void reserve(size_t n)
{
if(n>capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
memcpy(tmp, _start, sizeof(T) * size());
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start+n;
}
}
resize
当x小于空间大小时删除,大于时扩容并且追加val。
void resize(size_t n, T val = T())
{
if (n < size())
{
// 删除数据
_finish = _start + n;
}
else
{
if (n > capacity())
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
empty
判断是否为空,判断start和finish是否相等即可。
bool empty()
{
return _start == _finish;
}
pop_back
尾删,先检查是否为空,若不为空--finish。
void pop_back()
{
assert(!empty());
_finish--;
}
push_back
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
重载[]
为了方便访问数组内的位置,所以我们需要对[]进行重载,首先检查传入的pos是否大于数组本身的大小,然后返回位置即可。
T& operator [](size_t pos)
{
assert(pos < size());
return *(_start + pos);
}
const T& operator[](size_t pos)const
{
assert(pos < size());
return *(_start + pos);
}
insert
insert的实现逻辑并不复杂,但是在进行插入时要注意迭代器失效的问题。尤其是在开辟了新空间之后,将数据复制后要更新迭代器。另外在外部不要调用迭代器。
iterator insert(iterator pos,const T& val)//认为pos失效尽量不要再次调用
{
if(_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4:capacity()*2);
//更新pos防止迭代器失效
pos = _start + len;
}
iterator end = _finish;
while(end>=pos)
{
*(end + 1) = *end;
end--;
}
*pos = val;
_finish++;
return pos;
}
erase
在删除元素后,不要再使用之前的迭代器,需要重新获取迭代器来指向正确的位置。
iterator erase(iterator pos)//认为pos会失效,不应去访问,原因:行为未定义。指在vs和在g++环境下同样的代码所输出的结果是不同的。
{
assert(pos >= _start);
assert(pos < _finish);
iterator start = pos + 1;
while(start != _finish)
{
*(start - 1) = *start;
start++;
}
_finish--;
return pos;
}