
1.vector的成员变量的介绍
cpp
namespace xiaoli
{
template<class T>
class vector
{
public:
private:
T* _start = nullptr;
T* _finish = nullptr;
T* _end_of_storage = nullptr;
};
}

_start:指向容器起始位置;
_finish:指向有效元素的下一位置;
_end_of_storage:指向容器空间的尾;
2. 默认成员变量
2.1 构造函数
(1)构造函数1:无参构造
cpp
vector()
{
}
(2)构造函数2:指定空间大小
给定空间大小n,如果n<size(),那么移动_finish=_start+n的位置;
如果n>size(),说明空间大小不足,需要扩容reserve,再按照给定的内容修改;
cpp
vector(size_t n, const T& val)
{
if (n < size())
{
_finish = _start + n;
}
else
{
//扩容
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
说明:这里可以进一步完善,上面的代码先判断空间大小是否足够,在进行数据填充;
那么是否捡现成吃?-- 啥意思呢? 在模拟实现过程会发现这段代码和resize(n)的实现是一样的,直接复用resize()进行完善,代码会更加简洁!
cpp
vector(size_t n, const T& val)
{
resize(n, val);
}
(3)构造函数3:迭代器构造
根据参数first和last,进行比较,如果不等说明是有效的迭代器,复用push_back可以插入数据;
注:因为push_back内部实现是判断了是否需要扩容的问题!!
那么插入的位置就是first的位置,插入之后,first往后移动即可;
cpp
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
first++;
}
}
注意:这里存在频繁扩容的问题!!
解决办法就是:提前预留好指定大小空间。
(4)构造函数4:初始化列表
传统写法:开辟等大空间,在finish指定位置插入数据;
cpp
vector(initializer_list<T> il)
{
//step1:先判断空间够不够
reserve(il.size());
for (auto& e : il)
{
if (_finish == _end_of_storage)
{
size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
}
*_finish = e;
_finish++;
}
}
优化:提前开辟好空间(是为了防止push_back频繁的扩容),复用push_back,还解决了扩容问题;
cpp
vector(initializer_list<T> il)
{
// 开辟等大的空间
reserve(il.size());
for (auto& e : il)
{
push_back(e);
}
}
2.2 析构函数
cpp
~vector()
{
if(_start) //空指针不用处理
{
delete[] _star;
_start = _finish = _end_of_storage = nulptr;
}
}
注意,如果vector里面的内容是自定义类型,在vector析构的时候会调用对应析构函数!
2.3 拷贝构造
传统写法:
开辟等大空间,挨个拷贝内容
cpp
vector(vector<T>& v)
{
//_start = _finish = _end_of_storage = nullptr;
reserve(v.capacity());
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
}
一个一个拷贝有点太复杂了,欸C语言之前不是有一个拷贝函数memcpy,这样不仅就简化了代码吗?事实上这个真的能行吗?我们先来试试,遇到问题在分析!
于是就写出了这样的代码:
cpp
vector(vector<T>& v)
{
reserve(v.capacity());
memcpy(tmp, v._start, , sizeof(T) * v.size());
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
问题:这个代码一运行哪哪都报错!为什么?
我的老大哥啊,首先_start是私有成员,你怎么能访问?
其次,memcpy是浅拷贝,如果拷贝完后,你的v给释放掉了,那么你的v2也会受到影响。
如果是内置类型影响可能没有很明显,但是如果是自定义类型就会有很大隐患!!!这里就假设vector里面存储自定义类型!!!

当你的v释放掉之后,v2的_start也会随之变化。
如何解决?既然已经知道是浅拷贝的问题,那么深拷贝就能处理(即传统写法/现代写法)

**总结一下:**如果是浅拷贝就可以使用memcpy,但是如果是深拷贝,不能使用否则就会出问题!!
现代写法:
cpp
vector(vector<T>& v)
{
//_start = _finish = _end_of_storage = nullptr;
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);//优化
}
}
现代写法:直接套用push_back方法,同时提前预开辟了空间,不会频繁扩容!!!
2.4 赋值运算符
传统写法:
step1:开辟同样大小的空间;step2:遍历原vector数组,进行赋值;step3:更新私有成员。
cpp
//传统写法
vector<T>& operator=(vector<T>& v)
{
// 清理
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
reserev(v.capacity());
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish=_start+v.size();
_endofstorage = _start + v.capacity();
return *this;
}
现代写法:
你不是想要和我一样,那我用临时对象接受你,在和临时对象进行数据交换不就好了?
实现交换由两个办法:
方法1:创建临时对象,构造一份,在和我交换;
方法2:传值构造,在和形参进行交换;(这里可不是拷贝构造,不要求是引用传参)
cpp
iterator operator=(vector<T> tmp)
{
swap(tmp);
return _start;
}
3. 容量和大小相关的函数
3.1 size和capacity
只需要知道size:有效元素的个数;capacity:空间的大小。
cpp
size_t capacity() const
{
return _end_of_storage - _start;
}
size_t size()const
{
return _finish - _start;
}
3.2 reserve
首先要判断空间是否足够,不够就扩容并更新内容和私有成员;
cpp
void reserve(size_t n)
{
if (n > capacity())
{
//扩容
T* tmp = new T[n];
// 拷贝
for (size_t i = 0; i < size(); i++)
{
tmp[i] = _start[i];
}
delete[] _start;
//error
//_start = tmp;
// 注意这里的_start已经更新,但是_finish还是空,就有问题
//所以得先更新_finish
_finish = tmp + size();
_start = tmp;
_end_of_storage = _start + n;
}
}
但是这里需要注意两个问题:
问题1:当拷贝完内容,释放_start时,如果直接写_finish=_start+size()会报错,原因如下:
size()是_finish - _start,但此时_finish还是空的,_start已经更新,那么size()就会报错;
所以需要先更新_finish(目的是为了size()不报错),再去更新_start.

问题2:memcpy代替拷贝,会出现浅拷贝的问题

这里观看监视窗口,可以发现他们的地址是一样的,当_start资源释放的时候,tmp也会销毁;
注意:vector里面的资源是string,但是当释放资源时,string会调用它的析构函数,所以string也会被销毁。这里还是使用赋值拷贝,是因为string的赋值拷贝时深拷贝!!!

所以要注意memcpy的使用场景!!!
3.3 resize
当n<capacity()时,一般不做处理或者删除有效元素;
当n>capacity()时,扩容/扩容并填充内容。
cpp
void resize(size_t n, T val = T())
{
if (n < capacity())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
_finish++;
}
}
}
3.4 empty
empty是检测是否为空,那就是判断有没有有效元素,即判断_start==_finish?
cpp
bool empty()
{
if (_start == _finish)
return true;
else
return false;
}
4. 修改容器内容相关的函数
4.1 push_back
同样的先判断空间是否足够,在进行数据插入。
cpp
void push_back(const T& val)
{
// 先判断空间大小
if (_finish == _end_of_storage)
{
size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
}
*_finish = val;
_finish++;
}
4.2 pop_back
无需修改内容,之间--finish即可,有效元素减1
cpp
//void pop_back()
//{
// if (_start == _finish)
// return;
// else
// _finish--;
//}
// 完善
void pop_back()
{
assert(_finish > _start);
_finish--;
}
4.3 insert

要在pos位置插入数据,那就得把pos位置空出来,那么pos之后的数据要统一往后移动1位;
但是要分情况:
如果pos>it,直接尾插即可;
如果pos<=it,就需要把*(it+1)=*(it)
cpp
iterator insert(iterator pos, const T& val)
{
assert(pos <= _finish);
assert(pos >= _start);
if (_finish == _end_of_storage)
{
//满了,扩容
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
T* it = _finish - 1;
if (it >= pos)
{
*(it + 1) = *(it);
it--;
}
*pos = val;
_finish++;
return _start;
}
但是这里存在一个问题:就是迭代器失效的问题
当我正常执行5次push_back后,程序 是正常的,但是当我注释掉一行push_back的时候,此时的结果并不是我想要的,这是为什么?
执行5次是正常的,但执行4次却有问题,那么可以推断问题出在扩容上面,于是调试代码发现,_pos和_start在执行完扩容之后,地址不一样了,但是我要在pos位置插入数据,而新空间的地址和pos不一样,这不就是非法访问了吗?

画图来分析一下:

我们知道,当扩容的时候,会释放就空间,并更新_start,_finish,_end_of_storage,但是你并没有更新pos的位置,也就是说pos还愣在那里嘞,此时再对pos位置进行写入,不就是非法访问了,所以这里的主要问题就是更新pos。
所以再扩容之前我们要记录pos的相对位置,再在扩容后的新空间里根据相对位置更新pos!正确的代码如下:
cpp
iterator insert(iterator pos, const T& val)
{
assert(pos <= _finish);
assert(pos >= _start);
if (_finish == _end_of_storage)
{
size_t len = pos - _start;//提前记录pos的相对位置
//满了,扩容
reserve(capacity() == 0 ? 4 : 2 * capacity());
pos = _start + len;
}
T* it = _finish - 1;
if (it >= pos)
{
*(it + 1) = *(it);
it--;
}
*pos = val;
_finish++;
return _start;
}
4.4 erase
首先需要先判断_start==_finish?如果相等无法再删。
相对insert整体把pos位置的数据往后挪动1位,那么erase就时把pos位置的数据往前挪动1位。
cpp
iterator erase(iterator pos)
{
if (_start == _finish)
return nullptr;
T* it = pos + 1;
while (it < _finish)
{
//*(it) = *(it + 1);//容易越界访问
*(it - 1) = *(it);
it++;
}
}
**注意:**细节1:it从pos位置开始,往前挪容易越界访问!!!
细节2:vs编译器认为erase之后迭代器是失效的
这里举个典型的例子:

就比如第一种情况:迭代器失效(右侧是修改的代码)

注意:修改后的代码LInux下的g++是能跑通的,但是vs不可以,因为vs认为erase之后失效了,不能访问,失效的迭代器会被vs标记,一旦访问就会报错。
5.迭代器相关的函数
5.1 begin
typedef是为便于封装和根据标准来的,begin函数其实就是封装函数。
cpp
typedef T* iterator; //方便迭代器的使用
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
const_iterator begin() const
{
return _start;
}
5.2 end
cpp
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
6. 容器访问相关函数
6.1 operator[ ]
cpp
T& operator[](size_t i)
{
assert(i < size());
return _start[i];
}
const T& operator[](size_t i)const
{
assert(i < size());
return _start[i];
}
注:C++中的stl大多数容器支持下标+[ ]访问,少数不支持!!