1.1:模板设置
因为不能与库已经有的vector冲突,首先定义命名空间namespace my
使用类模板。
1.2:成员变量
vector有size,capacity,同时也要进行扩容操作和缩容(shrink to fit)操作,所以需要3个迭代器。
private:
iterator _start;
iterator _end;//最后一个数据的下一个位置
iterator _endofstorage;
1.3:迭代器
在c++官网中,迭代器有const和非const,一般来说只读的,设计const接口,只写的设计非const,可读可写提供2个。
typedef T* iterator;
typedef const T* _iterator;
iterator begin()
{
return _start;
}
_iterator begin()const
{
return _start;
}
iterator end()
{
return _end;
}
_iterator end()
{
return _end;
}
1.4:[]重载
c++中很多有关赋值的操作符都会涉及到传引用返回
2个原因如下:
- 传引用可避免拷贝,提升效率
- 对于连续赋值,a=b=c,c++中赋值操作符返回的是左值(操作符左边的数,不是它的值)的引用。如果不用传引用返回,在c语言中我们学了函数的返回值是一个临时变量,具有const属性,这里也一样,a=b首先返回a的一个临时拷贝对象,再把c的值赋给这个临时对象。
同时,在库中,也给了const和非const版本。
且对于越界检查,库采用的是assert检查方式。
T& operator[](size_t pos)
{
//c++里面有断言检查是否越界
assert(pos < size);
return _start[pos];
}
const T& operator[](size_t pos)const
{
assert(pos < size());
return _start[pos];
}
1.5:构造函数
vector()
:_start(nullptr)
,_end(nullptr)
,_endofstorage(nullptr)
{}
构造函数先用初始化列表全给空指针,后续再完善。
1.6:size()接口
size_t size()const
{
return _end - _start;
}
1.7:capacity()接口
size_t capacity()const
{
return _endofstorage - _start;
}
1.8:reserve接口
功能:设置空间,预留空间。
void reserve(size_t n)
{
if (n > capacity())
{
size_t oldsize = size();
//需要扩容
T* temp = new T[n];
//使用new可以调用默认构造,比malloc要舒服的多。
//要注意如果start为空的时候,拷贝不了任何东西所以先判断start
if (_start)
{
memcpy(temp, _start, sizeof(T) * oldsize);
delete[] _start;
}
_start = temp;
_end = _start + oldsize;
_endofstorage = _start + n;
}
}
当需要预留的大小大于本身的容量(这里使用接口获取容量),就需要扩容。扩容的时候开辟新空间,将原来的start位置开始的数据,T类型大小*size大小的空间拷贝给新空间,释放掉旧空间。然后再将新空间(因为是数组,数组名是首元素地址)赋值给start,end加上原来的大小+start,这里我使用了oldsize,是因为如果不记录旧的大小,当start被新空间赋值的时候,size就会用新的start-旧的end,明显不可能,因此首先用oldsize记录原来的size。
1.9:resize()接口
使用方式:resize(n,val)
三种情况:
- n>capacity()
- size()<n<=capacity()
- n<size()
//这里使用匿名对象,并使用缺省值,缺省值是使用的默认构造函数
void resize(size_t n, T val = T())
{
//1:n>capacity
if (n > capacity())
{
//开始扩容
reserve(n);
}
//2:size<n<=capacity无需扩容填充数据即可
else if (size() < n <= capacity())
{
while (_end < _start + n)
{
*end = val;
++end;
}
}
//3:n<size
else
{
_end = _start + n;
}
}
官网说val的类型就是模板的参数类型,也就是T,当不给出val值的时候,就会调用T的默认构造函数,所以我们有时候给出默认构造函数是有意义的。同时这里的T也是一个缺省值。
1.10:pushback接口
功能:插入数据
void pushback(const T& x)
{
//要检查扩容
//当end指针等于容量指针
if (_end == _endofstorage)
{
//此时需要扩容
size_t newcapacity = capacity() = 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*end = x;
++end;
}
当我们需要插入的时候就得检查扩容,这在学习链表和顺序表的时候也有。
1.11:empty()接口
bool empty()
{
return _start == _end;
}
1.12:popback接口
void popback()
{
assert(!empty());
--_end;
}
1.13:insert接口
功能:在pos位置插入val
void insert(iterator pos, const T& val)
{
assert(pos < _end);
assert(pos >= _start);
if (_end == _endofstorage)
{
size_t len = pos - _start;
int newcapacity = capacity() > 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + len;
}
iterator _finish = _end - 1;
while (_finish >= pos)
{
*(_finish + 1) = *_finish;
--_finish;
}
*pos = val;
++_end;
}
还是需要检查扩容,这里为什么要保存pos和start的差值呢,如果扩容是用的reserve是异地扩容,那pos位置就迭代器失效了,因此需要保存差值。
插入数据,数据右移,所以需要把某位置的数据给+1的位置,如果我们直接使用end迭代的话,end是末尾元素下一个位置,所以需要用他前面一个位置作为迭代器,迭代到=pos位置,然后再让pos迭代器内容指向val。