vector是我们日常中经常使用的一种数据结构,在c语言中一般被称为顺序表,其在空间中的分布顺序是连续的。为了更好的理解vector的功能与使用,我将通过模拟实现vector来逐步讲述其功能与需要注意的点。
框架定义
首先我们需要知道,vector需要存储不同类型的数据,可以是存一堆整形也可以是存一串串的字符串,甚至可以存vector,因此,我们需要用模版来实现vector。并且,由于模版类声明和定义不可分离,因此我们大部分代码都要写在.h文件中
在STL原装库中,vector是通过3个迭代器来进行定义的,分别是_start,_finish与_endof_Storage,名字可能略有不同,但是具体功能都在下文表示出来了。
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start; // 指向数据块的开始
iterator _finish; // 指向有效数据的尾
iterator _endof_Storage; // 指向存储容量的尾
};
构造/析构函数
vector的构造方式有很多种,如下文代码所示,第一种为显式默认构造,直接令vector()=default,这是在告诉编译器我不是忘记写了,而是我就只需要默认构造,那么当你不传任何参数的时候,编译器就默认帮你生成一个实例。
第二个是开辟n个空间,并在每个空间里面放一个T类型的value数据,如果第二个参数没有写的话,就会调用T的默认构造。在该构造中,首先初始化所有变量,接着先开辟n个数据的空间。reserve函数的功能就是开辟空间,后续会提到。接着把空间所有数据都初始化为value。
第三个是根据其他容器来构造vector实例,直接定义一个it为传进来的first迭代器,接着通过循环把数据都push_back到实例内,push_back的功能就是尾插,在后续会提到。
第四个是通过其他vector<T>实例来初始化该实例,此时需要先把空间开好,接着将数据一个一个往内挪,再把_finish给改了,这样就能实现相关功能了。
第五个是赋值重载函数,因为这是传值传参,因此当构造函数结束,参数就会被销毁,先通过swap函数将变量给进行交换,此时v中的数据就是原实例的数据,函数结束既销毁,因为c++原先是可以连续赋值的,于是可以返回一个*this用于连续赋值。
第六个是析构函数,只要delete[]空间,把三个变量全部改成nullptr即可。
vector() = default;//默认构造
vector(int n, const T& value = T())//大量连续构造
{
_start = _finish = _endof_Storage = nullptr;
reserve(n);
for (int i = 0; i < n; i++)
{
_start[i] = value;
}
}
template<class InputIterator>//利用其他容器来构造
vector(InputIterator first, InputIterator last)
{
_start = _finish = _endof_Storage = nullptr;
auto it = first;
size_t i = 0;
while (it != last)
{
push_back(*it);
it++;
}
}
vector(const vector<T>& v)
{
reserve(v.size());
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
}
vector<T>& operator= (vector<T> v)//赋值构造
{
swap(v);
return *this;
}
~vector()//析构
{
delete[] _start;
_start = _finish = _endof_Storage= nullptr;
}
空间类函数
空间类函数中包括2个数值返回型函数和2两个空间变动型函数。size即返回空间中已有数据个数,capacity即返回空间中已经开辟的空间个数。再往后就是reseve函数,若申请的空间小于已有空间,不进行缩容,若申请的空间大于已有的空间,则进行扩容,首先定义一个old_size用于记录当前数据个数,接着new一个n个大小的T*数据用于存储数据,如果_start已经初始化,将数据挪过去,并删除原先的_start,并让_finish和_end_of_storage进行改变。resize分为三种情况,n大于当前容积,小于当前容积但大于size,小于size,具体看下面代码。
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endof_Storage - _start;
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t old_size = size();
T* tmp = new T[n];
if (_start) // 检查 _start 是否已经初始化
{
for (size_t i = 0; i < old_size; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + old_size;
_endof_Storage = _start + n;
}
}
void resize(size_t n, const T& value = T())
{
if (n > capacity())
{
reserve(n);
auto it = _finish;
while (it != _endof_Storage)
{
*it = value;
it++;
}
}
else if (n < capacity() && n > size())
{
auto it = _finish;
while (it != _endof_Storage)
{
*it = value;
it++;
}
}
else
{
_finish = _start + n;
}
}
功能函数
尾插,尾删,交换函数与前文的string函数的尾插,尾删,交换函数相似,不予赘述。而insert和erase就有趣的多。
insert函数首先定义一个size_t类型的数据len来记录pos到_start的距离,接着判断是否需要扩容,如果需要扩容,则调用reserve函数,但请注意,此时便会出现迭代器失效。为什么呢?我们的pos指向的是原来的空间中的某个位置,但当使用reserve进行扩容以后,_start的空间会发生改变,因为扩容的本质是另外开辟一片空间来使用,扩容结束以后,pos位置指向的位置却没有发生改变,这就会导致我们的pos不再指向目标点,因此,我们需要重新定义迭代器,len的功能这时候就出现了。在定义好迭代器后就是常规的插入。但是在最后要返回一个pos,因为若是外面也用了pos进行记录,当insert结束外面的pos也会失效,返回一个pos让外面的pos接收就可以避免这样的情况。
erase也是如此,当删除数据以后,会导致pos的位置指向不该指向的位置,如果pos<size(),那就会导致pos的位置指向pos的下一个数据,如果pos==size(),还会导致越界。该迭代器失效需要在外部进行通过判断调整。
void push_back(const T& x)
{
if (_finish == _endof_Storage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
*_finish++ = x;
}
void pop_back()
{
if (_finish != _start)
{
_finish--;
}
}
void swap(vector<T>& v)
{
if (v != *this)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endof_Storage, v._endof_Storage);
}
}
iterator insert(iterator pos, const T& x)//
{
size_t len = pos - _start;
assert(pos >= _start && pos <= _finish); // 确保 pos 是有效的
if (_finish == _endof_Storage)
{
size_t new_capacity = (_start == nullptr) ? 4 : 2 * capacity();
reserve(new_capacity);//pos 迭代器失效
pos = _start + len;
}
auto it = _finish;
while (it > pos)
{
*it = *(it - 1);
it--;
}
*pos = x;
_finish++;
return pos;
}
void erase(iterator pos)
{
assert(pos >= _start || pos <= _finish);
auto it = pos;
while (it < _finish)
{
*it = *(it+1);
it++;
}
_finish--;
}
以上,就是vector<T>的部分学习经历,