片头
嗨~小伙伴们,今天我们来一起学习关于C++的vector类的模拟实现,准备好了吗?咱们开始咯~
一、基本框架
namespace bit {
template<class T>
class vector {
public:
typedef T* iterator;
typedef const T* const_iterator;// 针对const修饰的迭代器
private:
iterator _start; //指向数据块的开始
iterator _finish; //指向有效数据的结尾
iterator _end_of_storage; //指向存储容量的结尾
};
}
我们定义了一个模板类,这里的vector三个成员均为迭代器,而Vector的迭代器是一个原生指针,我们这里为其定义别名iterator
私有成员:
iterator _start; //指向数据块的开始
iterator _finish; //指向有效数据的结尾
iterator _end_of_storage;//指向存储容量的结尾
这些成员变量用于管理vector内部的动态数组
(1)_start:这是一个指针,指向分配给vector的内存区域的开始。这是数组的第一个元素。
(2)_finish:这个指针指向数组中最后一个实际存在的元素的下一个位置。这意味着它指向结束后的第一个元素,它用来表示存储在vector中的实际元素的结束。
(3)_end_of_storage:这个指针指向分配给vector的内存块的末尾。这不是最后一个有效元素的位置,而是整个内存块的结束位置,在这之后可能会有额外的未初始化空间,预留以实现当vector增长时无需重新分配整个数组
二、相关函数
(1)size();
获取有效元素的个数
//获取有效元素的个数
size_t size() {
return _finish - _start;
}
size_t size() const {
return _finish - _start;
}
(2)capacity();
获取容量的大小
//获取容量的大小
size_t capacity() {
return _end_of_storage - _start;
}
size_t capacity() const {
return _end_of_storage - _start;
}
(3)reserve(size_t n);
如果容量不足,就开辟空间
有同学肯定会说,那还不简单~ ,下面我们来示范错误代码:
//如果当前容量不足,就开辟空间
void reserve(size_t n)
{
//n大于实际的容量,那么开辟新空间
if (n > capacity())
{
T* temp = new T[n];//重新申请一块新空间,能够存放n个有效数据
memcpy(temp, _start, sizeof(T) * size());//将原来的数据拷贝到temp中
delete[] _start;//释放旧空间
_start = temp; //将新空间的地址赋给_start
}
_finish = _start + size();//更新_finish
_end_of_storage = _start + n;//更新_end_of_storage
}
但是这里有一个很大的问题:原来的_start的地址被销毁了,temp赋值给_start,_start指向新的空间,但是_finish还是旧的_finish,而size()函数需要用_finish-_start,所以此时的size()是无效的。
怎么解决这个问题呢?我们可以先用一个变量oldsize把原来的size()保存,后面_finish直接使用oldsize即可。
改进代码(仍不完善):
//不完善的代码
void reserve(size_t n)
{
size_t oldsize = size();//使用oldsize变量将原来的size()保存
if (n > capacity())
{
T* temp = new T[n];
memcpy(temp, _start, sizeof(T) * size());
delete[] _start;
_start = temp;
}
_finish = _start + oldsize;//直接使用oldsize
_end_of_storage = _start + n;
}
我们知道,只有当传递过来的n大于capacity()时,才需要扩容;并且,只有当旧空间有值的时候,才需要拷贝到新空间中,否则不做处理。因此,还可以进行优化:
//优化版本
void reserve(size_t n)
{
if (n > capacity())
{
size_t oldsize = size();//使用oldsize变量将原来的size()保存
T* temp = new T[n];
if (_start) //当_start存在的时候,进行拷贝
{
memcpy(temp, _start, sizeof(T) * size());
delete[] _start;
}
_start = temp;
_finish = _start + oldsize;//直接使用oldsize
_end_of_storage = _start + n;
}
}
但是,我们举一个例子:
将vector实例化为string类,调用4次push_back函数都没有问题。
但是,调用第5次push_back函数时,就出现了乱码,这是为什么呢?
因为memcpy函数是浅拷贝,所以reserve函数里面的string* temp和原来的_start指向的是同一块空间。调用delete[] _start时,会调用析构函数,将原来空间释放。所以temp指向的是一块非法的空间,因此会报错。
怎么解决这个问题呢?我们可以使用深拷贝
void reserve(size_t n)
{
if (n > capacity())
{
size_t oldsize = size();//使用oldsize变量将原来的size()保存
T* temp = new T[n];
if (_start) //当_start存在的时候,进行拷贝
{
// memcpy(temp, _start, sizeof(T) * size()); //浅拷贝
for (size_t i = 0; i < oldsize; i++)//深拷贝
{
temp[i] = _start[i];
}
delete[] _start;
}
_start = temp;
_finish = _start + oldsize;//直接使用oldsize
_end_of_storage = _start + n;
}
}
或者是调用swap函数
void reserve(size_t n)
{
if (n > capacity())
{
siz