本文带你进入vector的模拟实现,对于vector,是我们深入学习STL的必要途径。
根据库的实现方式,成员函数如下:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
c++11开始可以对成员变量使用缺省值,在这里我们可以使用缺省值。
size的大小为_finish - _start
capacity的大小为_end_of_storage - _start
该函数的作用是:扩容。
void reserve (size_type n);
//扩容
void reserve(size_t n)
{
//防止有些情况是直接调用
if (n > capacity())
{
size_t oldsize = size();//必须要记录旧的size,旧的size是用旧的_finish - _start获得的
iterator tmp = new T[n];
//memcpy(tmp, _start, sizeof(T) * size());
//这里如果使用memcpy函数的话,如果vector对象是自定义类型,比如string,会造成浅拷贝问题。
for (size_t i = 0; i < oldsize;i++) // 如果是string类,会调用string的赋值重载,赋值重载调用string的拷贝构造,完成深拷贝
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
//_finish = _start + size();
//如果这样用的话,size()是用旧的_finish - 新的_start,不是连续的空间,没有意义
_finish = _start + oldsize;//_finish必须更新,旧的_finish迭代器已经失效了
_end_of_storage = _start + n;
}
//tmp不用处理,出了作用域自动调用析构
}
注意:在扩容时,如果是自定义类型,比如string,使用memcpy函数进行拷贝数据,会出现浅拷贝问题。
v1会拷贝给v2,v2的指针仍然指向v1的string对象所指向的空间。这就造成了两次调用析构函数释放同一块空间的问题。
正确的解法是:
在拷贝时调用string的赋值运算符重载,完成深拷贝。
还有一个点是:
注意:在这里会出现迭代器失效的问题。
此时空间已满,则需要重新申请空间。
重新申请空间后,_start和_end_of_stortage都会指向新的空间,唯独_finish还指向旧的已经被释放的空间,此时如果这样更新_finish位置:
_finish = _start + size();
这里的size()是用旧的_finish - 新的_start得到,是没有意义的。
解决方案:
保存旧的size,随后_start加上旧的size即为_finish的位置。
该函数的功能是:重新改变vector容器的大小,让其到达n。新的空间默认初始化为val
思路:
void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
//扩容到n,如果n
reserve(n);
//将多的空间全部设置成缺省值
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
注意这里的缺省值为T(),是一个T类型的匿名对象。
不管T是什么,都可以使用模板实例化出具体的匿名对象,即使是内置类型,比如int,也可以实例化出int(),这里的值为0
该函数的作用是:
在pos位置插入模板对象val。
思路:
void insert(iterator pos, const T& val)
{
assert(pos <= _finish);
//插入前先检查扩容
if (_finish == _end_of_storage)
{
size_t oldpos = pos - _start;//记录相对位置
size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
pos = _start + oldpos;
}
//记录旧的size,防止出现迭代器失效问题。
//将pos之后的位置往后挪,再插入
iterator end = _finish - 1; // reserve之后,_finish也跟着更新了。
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
_finish += 1;
}
注意:insert在扩容时仍然会出现迭代器失效的问题。
解决方案:提前记录pos的相对位置,即oldpos = pos - _start;
然后再扩容完成后,更新pos 的位置,pos = _start + oldpos;
否则在扩容后,旧的pos迭代器就已经失效了。
该函数的功能是:删除pos位置的值。
//在删除最后一个位置或者只剩下一个待删除元素时,会出现迭代器失效问题。
iterator erase(iterator pos)
{
assert(pos < _finish);
iterator end = pos;
while (end != _finish)
{
*end = *(end + 1);
++end;
}
--_finish;
return pos;
}
有一个需要注意的点:当我们删除元素后,pos迭代器就会失效,因为空间已经释放,这时候不能使用pos迭代器。
解决方法:返回删除位置的下一个元素的位置,即pos位置。
然而,在只剩下一个待删除元素或者删除最后一个位置的元素时,不能再使用pos迭代器。
顾名思义,这两个函数的功能是在最后一个位置插入元素和删除最后一个位置的操作。
直接复用insert和erase函数即可,这里不再赘述。
typedef T* iterator;
typedef const T* const_iterator;
返回_start地址即可。
iterator begin()
{
return _start;
}
const_iterator begin() const
{
return _start;
}
返回_finish地址即可。
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
思路:只需给定pos下标即可。
在此之前需要判断pos位置不能超过整个顺序表的size大小。
T& operator[]( size_t pos)
{
assert(pos < size());
return *(_start + pos);
}
//不可修改的
const T& operator[]( size_t pos) const
{
assert(pos < size());
return *(_start + pos);
}
构造函数可分为3种:
对于无参的构造,我们只需要初始化成员函数即可。
//无参构造函数
vector()
//:_start(nullptr)
//,_finish(nullptr)
//,_end_of_storage(nullptr)
{}
//构造n个对象
vector(size_t n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
//1.开空间
reserve(n);
//2.将空间填充为val对象
for (size_t i = 0; i < capacity(); i++)
{
_start[i] = val;
}
//也可以复用resize
//resize(n, val);
}
注意:这里在构造时必须初始化,如果不初始化,在调用reserve函数开空间时会出现访问野指针的问题。
也可以调用resize函数进行拷贝构造n个对象。
同理,如果不初始化,会出现扩容后访问野指针的情况。
template<class Inputiterator>
vector(Inputiterator first, Inputiterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造,必须实现深拷贝
//传统写法
//v1(v)
vector(const vector<T>& v)
//这里必须初始化,否则扩容时会访问不属于自己的空间
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
//如果私有成员给了缺省值,就不用再再次初始化了
{
reserve(v.capacity());
//memcpy(_start, v._start, sizeof(T) * v.size());
//这里使用memcpy的错误跟扩容的错误一样,不再赘述
for (size_t i = 0; i < v.size(); i++) // 如果是string类,会调用string的赋值重载,赋值重载调用string的拷贝构造,完成深拷贝
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
}
拷贝构造需要注意的几个问题:
释放_start指向的空间,然后全部置为空即可。
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage;
}
}
vector的模拟实现就到此结束了,如果对你有帮助的话,不妨来一个关注吧!