目录
一.默认成员函数
二.扩容相关函数
三.[]重载
四.修改函数
五.迭代器
继上次写完string之后,可以写一个vector练练手以及熟悉其底层。vector是一个顺序表,相比普通数组不同点在于顺序表的数据必须是连续存放的。
string是只存放字符,而vector需要存放内置类型和自定义类型,所以需要引入类模板。以下是vector的最外层框架。
template//类模板参数,T是接收外面转进来的类型,可以使内置也可以是自定义类型
class vector
{
public:
//成员函数的定义
private:
//内置成员变量是三个迭代器,vector的迭代器实际上还是指针
iterator _start = nullptr; // 指向数据块的开始
iterator _finish = nullptr; // 指向有效数据的尾
iterator _endOfStorage = nullptr; // 指向存储容量的尾
};
以下是vector的默认成员函数,重载&和const &重载一般不需要手动写。
vector()//默认构造函数
{}
vector(int n, const T& value = T())//构造一个包含n个元素的vector,每个元素都是value
{
resize(n, value);//直接用resize即可
}
template
vector(InputIterator first, InputIterator last)//通过一个迭代器区间构造vector
{
while (first != last)
{
push_back(*first);
first++;
}
}
vector(const vector& v)//拷贝构造函数
{
vector tmp(v.begin(), v.end());//复用迭代器区间构造函数完成拷贝,然后再交换
swap(tmp);
}
vector& operator= (vector v)//通过传值来完成拷贝,然后再交换
{
swap(v);
return *this;
}
~vector()//析构函数
{
delete[] _start;//直接delete,由于是delete[]所以即使是多层嵌套(自定义类型或者vector或其他类型)也会调用他们的析构函数(这里需了解delete[]原理)
_start = nullptr;
_finish = nullptr;
_endOfStorage = nullptr;
}
扩容函数需要注意异地扩容时把原来空间的元素拷贝到新空间时需要使用赋值而不是memcpy或memmove。对于自定义类型并且实现了深拷贝的赋值就不会出现因为浅拷贝导致多次析构的问题。
size_t size() const
{
return _finish - _start;//返回当前元素个数
}
size_t capacity() const
{
return _endOfStorage - _start;//返回总容量大小
}
void reserve(size_t n)//扩容
{
if (n > capacity())//判断目标扩容大小有没有大于总容量
{
T* tmp = new T[n];//直接创建新空间,大小为n
size_t sz = size();//记录扩容前的元素个数
if (_start)//如果_start不为空(如果为空也不用复制元素内容了)
{
for (int i = 0; i < sz; i++)
{
tmp[i] = _start[i];//这里如果用memmove或者memcopy对于自定义类型会发生多次析构的问题(相当于浅拷贝),所以还是用赋值,如果自定义类型有实现深拷贝就没问题
}
delete[] _start;//这里把原来的删除,如果是浅拷贝,这里的删除了,tmp里面的自定义类型指针就变成野指针了
}
_start = tmp;//修改_start
_finish = tmp + sz;//记录sz的好处
_endOfStorage = tmp + n;
}
}
void resize(size_t n, const T& value = T())//扩容或减容至n个元素,扩容的话会用value填满所有空间
{
if (n < size())//判断目标扩容大小是不是小于当前元素个数
{
T* tmp = new T[n];//直接创建新空间,大小为n
for (int i = 0; i < n; i++)
{
tmp[i] = _start[i];//一个个拷贝,这里用赋值和reserve原因完全一样
}
//下面也和reserve一样
delete[] _start;
_start = tmp;
_finish = tmp + n;
_endOfStorage = tmp + n;
}
else if (n > size())//这里是目标扩容大小大于当前元素个数
{
int i = size();//记录当前元素个数
reserve(n);//扩容
while (i < n)
{
_start[i++] = value;//从当前元素的结尾开始填充,直到容量达到目标扩容大小
}
_finish = _start + n;//更新_finish
}
}
[]重载需要断言是否越界,以及还要实现const版本。
T& operator[](size_t pos)//方括号重载
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const//const方括号重载
{
assert(pos < size());
return _start[pos];
}
修改函数需要注意insert的pos迭代器失效问题以及erase的迭代器失效(对于外部的迭代器)。
void push_back(const T& x)//尾插
{
if (_finish == _endOfStorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
_start[size()] = x;//结尾插入元素
_finish++;//更新_finish
//或者复用insert
//insert(end(),x);
}
void pop_back()//尾删
{
erase(end());//直接调用erase函数尾删
}
void swap(vector& v)//交换两个vector的成员变量
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endOfStorage, v._endOfStorage);
}
iterator insert(iterator pos, const T& x)//在pos位置之前插入
{
assert(pos >= begin());//断言,防止pos越界
assert(pos <= end());
size_t sz = pos - _start;//计算并记录pos和_start的相对距离,也是待会要插入的位置
if (_finish == _endOfStorage)//判断是否要扩容
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
//重点注意,这里是insert迭代器可能失效的地方
pos = _start + sz;//如果是异地扩容,那么pos所指的位置会被删除就变成野指针了,所以要更新pos位置,之前记录的sz就有用了
if (size())//如果当前元素个数不为0的话
{
//从后往前走,然后把左边的数据挪到右边,需要注意下标的越界问题
iterator end = _finish;
while (end > pos)
{
*end = *(end - 1);//左边的数据挪到右边
end--;
}
}
_start[sz] = x;//放入数据
_finish++;//更新_finish
return pos;//返回插入元素的下一个位置,也就是pos
}
iterator erase(iterator pos)//删除当前位置的元素
{
assert(pos >= begin());//判断pos是否越界
assert(pos <= end());
iterator prev = pos + 1;//记录pos位置的前一个位置
//从左往右走,然后把右边的数据挪到左边,直接覆盖要删除的元素
while (prev < end())
{
*(prev - 1) = *prev;//把右边的数据挪到左边
prev++;
}
_finish--;//更新_finish
//这里是第二个迭代器失效问题,如果外面的迭代器不接受erase返回的迭代器可能后面的删除会出问题
return pos;//返回删除元素的下一个位置(其实还是pos)
}
vector迭代器就是原生指针,以及实现const迭代器。
typedef T* iterator;//vector的迭代器是原生指针,和string一样
typedef const T* const_iterator;//const迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
以上就是vector的模拟实现,写完后不仅可以提升对代码的操作熟练度,对于顺序表这种数据结构也能有更深的理解。