可以将vector理解为一个
动态数组
,使用时要使用vector.h
头文件
mystl
封装——就是只暴露接口,并且在实现的时候一定要谨记这个函数的功能是什么,绝不超过它范围的实现,不要想着给他多来点儿功能,这不是一件好事儿,反而会对后面的函数使用留下不可估量的后患
typedef T* iterator;
typedef const T* const_iterator;
typedef 只是将他取别名
,千万不要将他以为是一个新的类型,就不知道如何去解引用了,他在预处理阶段就会转换成T*,这就是一个指针(最起码写代码的人要是这么以为的)
//vector就是完完全全就是一个迭代器
iterator _start = nullptr;//如果新开的空间,将新空间存在_start中
iterator _finish = nullptr;
iterator _EndOfStorage = nullptr;
typedef和声明的顺序要注意,现有typedef然后才能进行使用
要明确_finish指向
的位置,指向的是最后一个元素的下一个位置
(也就是说,这个空间有0个元素,_finish指向第一个元素位置)
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
//使用const修饰this构成重载
return _start;
}
const_iterator end() const
{
return _finish;
}
重载条件:
相同作用域,相同函数名,参数类型不同/参数顺序不同/参数个数不同
不以返回值类型进行区分
如果不加const就是错的
,const修饰this
,这里就是改变了参数类型
vector()
{
//需要在声明的时候就给成nullptr
//也可以在每一个构造中都给出nullptr
//否则会出现野指针的情况
}
/*
vector()
:_start(nullptr),_finish(nullptr),_EndOfStorage(nullptr)
{}
*/
最好不要写成参数列表
,最好在声明变量
的时候就直接写成nullptr
,或者在构造函数中严格控制好这些指针
如果自己的某个构造函数写错了,指针没有赋值,但是我们就是使用这个构造函数进行构造对象的,同时我们又要使用这个野指针解引用,那不就费了,如果我们将指针设置成nullptr,最起码比野指针要好吧
vector(const size_t n,T val=T())
{
//reserve(n);
这么写始终没有改变size的值
for (int i = 0; i < n; i++)
_start[i] = val;
//for (int i = 0; i < n; i++)
//{
// //不能直接调用[],在调用[]的时候没有这么多空间可以使用
// //只是进行赋值而已
// _start[i] = val;
// _finish++;
//}
//如果调用push_back就可以不提前开空间,直接复用就可以了
for (int i = 0; i < n; i++)
push_back(val);
}
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
void push_back(T val = T())
{
//push_back不能使用&,可能传进来的是一个需要进行类型转换或者进行计算的值
//默认生成一个val
if (size() == capacity())
{
int new_capacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(new_capacity);
}
*_finish = val;
_finish++;
}
在实现这个插入函数的时候,可以选择复用函数或者手写
在复用函数的时候一定要明确另一个函数中所实现的内容
,比如[]访问需要提起开好空间
但是在push_back的时候就不需要提前保证足够的空间
vector(const vector<T>& v)
{
//需要进行值拷贝
//reserve(v.capacity());
//for (int i = 0; i < v.size(); i++)
// _start[i] = v[i];
//只要调用push_back就不需要使用reserve进行扩容
//使用auto引用是为了节省空间,如果vector中的类型是一个内存很大的结构体
//那么auto每次拿到一个都需要进行拷贝,那样的时间消耗将是巨大的
//况且在push_back中接收参数也是使用传值进行接收参数的
//push_back比[]好
//1.[]调用的是push_back
//2.push_back使用的空间开辟手段更好
for (auto& e : v)
push_back(e);
}
一定要进行
值拷贝
,如果存储的类型是string类型
,如果使用指针拷贝的话,在一个对象释放空间的时候,另一份空间将要去哪里找???所以要进行值拷贝
范围遍历for,需要加&防止内部的对象是一个内存很大的类型
,那样大量数据出现时,拷贝的压力将会很大
~vector()
{
delete[] _start;
_start = _finish = _EndOfStorage = nullptr;
}
vector<T>& operator=(vector<T> temv)
{
swap(temv);
return *this;
}
使用现代的方法进行赋值
迭代器失效的原因:空间开辟和消除
void reserve(size_t n)
{
//reserve是开多少空间
//if (n > capacity())
//{
// //可能发生迭代器失效
// T* old_Storage = _start;
// T* tem = new T[n];//使用new会直接生成T类型的看空间
// int t_size = size();
// _start = tem;
// _finish = _start + t_size;//结尾位置默认会向后移动一个位置
// _EndOfStorage = _start + n;
// //开心空间之后要将原来空间中的数据拷贝回来
// if(old_Storage)
// memcpy(_start, old_Storage, sizeof(T) * t_size);
// delete[] old_Storage;
//}
if (n > capacity())
{
T* tem = new T[n];
int t_size = size();
if (_start)
{
//不能使用memcpy,如果使string类型,需要深拷贝,但是使用的是浅拷贝
for (int i = 0; i < t_size; i++)
{
tem[i] = _start[i];
}
}
_start = tem;
_finish = _start + t_size;
_EndOfStorage = _start + n;
}
}
只要涉及到指针扩容,就可能产生
迭代器失效
迭代器失效:由于扩容的时候没有将指向原来表中的指针改变指向,指向新的空间中
void resize(size_t n)
{
//将没有设置的为置为0
//缩容不会调整capacity,只会调整size
if (n < size())
{
//缩容
_finish = _start + n;
}
if (n > size())
{
reserve(n);
//此时it已经在end的位置
while (_finish < _EndOfStorage)
{
*_finish = 0;
_finish++;
}
}
}
resize在
缩容
的时候不改变总容量,只改变size
;在扩容
的时候增大总容量
,并且将其余位置设置为0
void push_back(T val = T())
{
//push_back不能使用&,可能传进来的是一个需要进行类型转换或者进行计算的值
//默认生成一个val
//if (size() == capacity())
//{
// int new_capacity = capacity() == 0 ? 4 : capacity() * 2;
// reserve(new_capacity);
//}
//*_finish = val;
//_finish++;
insert(end(), val);
}
void insert(iterator pos, T val = T())
{
//任何插入的函数都不能使用&接收val
assert(pos >= begin());
assert(pos <= end());
//如果开了空间,那么迭代器就可能失效了
//int gap = pos - _start;
//reserve(size() + 1);//如果比他大就开空间
既然push_back也要开空间,那么可以合并到insert中,进行统一操作
//pos = _start + gap;
//iterator it = end();
//while (it >= pos)
//{
// *it = *(it - 1);
// it--;
//}
//*pos = val;
//_finish++;
if (_EndOfStorage == _finish)
{
int gap = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + gap;
}
iterator it = end() - 1;
while (it >= pos)
{
*it = *(it - 1);//c语言中允许向前访问越界,不允许向后访问越界
it--;
}
*pos = val;
_finish++;
}
这里就需要体会
代码复用
的魅力,代码的高度复用使得代码未来修改的时候将会格外的方便
就拿这里来看,push_back复用了功能更强大的insert,插入的时候都需要进行扩容,在修改代码的时候也只需要在一个地方改就行了
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
auto it = pos;
while (it<_finish-1)
{
*it = *(it + 1);
it++;
}
return pos;
}
返回的是删去位置的下一个位置,也就是
新的这个位置
vector(const size_t n,const T& val=T())
{
for (int i = 0; i < n; i++)
push_back(val);
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
//reserve(last - first);
while (first != last)
{
push_back(*first);
first++;
}
}
如果有
vector
,编译器会将他识别为v(5,6) int,int
进行调用,但是我们只写了size_t类型的构造,那么编译器就会调用模板,推到成int类型进行调用,进而就形成了非法间接寻址
如果要改变这种显现,就需要给他一个int类型的构造函数
vector(const int n, const T& val = T())
{
//重载int
//在输入int类型的数据时,可能会调用迭代器构造进行解引用
//为什么会调用迭代器构造呢?因为数据在进行自动类型推导的时候
//size_t不好推,编译器就推到成了迭代器构造
for (int i = 0; i < n; i++)
push_back(val);
}