三个成员函数;
都是指针类型;
_start
_finish
end_of_storage
迭代器end(),也就是返回的finisn,最后一个元素的后一个位置
//reserve()
//增容,先开辟新的空间,再把原空间释放(防止内存泄露),最后把指向新开辟的空间,成员变量也要改变;
//空间被释放时,要注意:释放后的空间就不去用了,size(),会调用到旧空间;
void reserve(size_t n)
{
size_t sz = size();
if (n > capacity())
{
T* tmp = new T[n];//string 扩容要n+1,因为最后还要放一个/0
//防止start直接是空,是空的话memcpy会报错
if (_start)
{
memcpy(tmp, _start, sizeof(T) * n);//长度写成size()行不行?
//当vector时要用赋值,memcpy不行;
//用string的operator=赋值是深拷贝,深拷贝就是会再开辟一个临时对象的空间;
delete[] _start;
}
//_finish = _start + size();
//_end_of_storage = _start + size();
_start = tmp;
_finish = _start + sz;
end_of_storage = _start + n;
}
}
问题出现在delete[] _start这里;
如果vector,这里面的T的类型是string,那么会出现问题;
mencpy(tmp,_start, sizeof(T)*sz),怎么拷贝的?是将_start的值赋值给tmp;
普通的数、字符串都没问题,就是有指针这种,指向一块空间就会有问题;
memcpy将里面两个指针的内容赋值成一样了,一个指针释放后空间后,另一个就不能用了;
memcpy用opertor,赋值,这个带有深拷贝的来替换掉;
这样如果是vector,那么每个vector的元素string都是深拷贝;
void reserve(size_t n)
{
size_t sz = size();
if (n > capacity())
{
T* tmp = new T[n];//string 扩容要n+1,因为最后还要放一个/0
//防止start直接是空,是空的话memcpy会报错
if (_start)
{
for (size_t i = 0; i < size(); i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
//_finish = _start + size();
//_end_of_storage = _start + size();
_start = tmp;
_finish = _start + sz;
end_of_storage = _start + n;
}
}
delete和free的区别就是delete会调析构函数;
new和malloc区别是,new会调用构造函数;
for循环这个遍历方式本质用的是迭代器
//第一种迭代器
vector<T>::iterator it = v.begin();
while(it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
//第二种[]
for(int i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
//第三种for循环
for(auto e : v)
{
cout << e << " ";
}
cout << endl;
vector()
:_start(nullptr)
, _finish(nullptr)
, end_of_storage(nullptr)
{}
//析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = end_of_storage = nullptr;
}
}
深拷贝要先开辟一个空间;
这里调用的v.capacity(),首先v是const,所以他的capacity也应改是const,不然他的权限就被放大了就会报错;
所以定义capacity后面都要再加个const;
//拷贝构造,深拷贝v2(v1)
//第二种办法就是初始化v2,然后扩容到v1的容量、再把v1的值赋值过去
vector(const vector<T>& v)//
:_start(nullptr)//初始化列表
,_finish(nullptr)
,end_of_storage(nullptr)
{
//先扩容
reserve(v.capacity());
//用两个迭代器进行遍历然后赋值
iterator it = begin();
const_iterator vit = v.cbegin();
while (vit != v.cend())
{
*it++ = *vit++;
}
_finish = it;
}
//第一种方法
/*vector(const vector& v)
{
//先开辟空间
_start = new T[v.capacity()];
_finish = _start;//后面遍历的时候再把_finsh调到正确位置
end_of_storage = _start + v.capacity();
for (size_t i = 0; i < v.size(); i++)
{
*_finish = v[i];
finish++;
}
}*/
因为浅拷贝,对于指针来说就是直接两个指针指向一个空间了,那其中一个指针如果说释放空间,另一个就变成野指针了;
如果没有数组指针这些需要开辟空间的,那就不用深拷贝;
深拷贝,就是自己先开个空间,再把东西拷贝过来,防止那边空间释放了;
其中注意有迭代器失效,增容后pos的指向位置要重新给他赋值,不然他指向的增容前的地址、增容会把原来地址释放掉,换一个新的地址;
//insert,注意要扩容,assert
//push,尾插可以直接用insert,这里有迭代器失效,assert
iterator insert(iterator pos,const T& x)
{
assert(pos <= _finish); //等于时就是尾插
if (_finish >= end_of_storage)
{
//算出pos和start的距离
//所以指针减去指针是之间的距离,是size_t类型
size_t n = pos - start;
size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
reverse(newcapacity);
//如果有增容注意要重置pos,因为这里面有迭代器失效
//增容后原来的地址被释放了,指向新的空间,但是pos还是指向原来的地址,所以是pos指向的变成随机值会报错
pos = _start + n;
}
//pos以及之后的一个个往后挪动
iterator it = _finish - 1;
while (pos <= it)
{
*(it + 1) = *it;
it--;
}
*pos = x;
return pos;
}
暂时可以看成两个指针
typedef T* iterator;
typedef const T* const_iterator;
//迭代器iterator,begin()或者end()
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
//只读迭代器
const_iterator cbegin() const//只读,const_iterator上面已经定义了是 const iterator
{
return _start;
}
const_iterator cend() const
{
return _finish;
}
//erase
//删除偶数那个例子试一下
iterator& erase(iterator pos)
{
assert(pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
begin++;
}
finish--;
return pos;//这里面pos没有扩容操作,所以可以直接用
}
两种写法,第二种是现代写法;
赋值也要用到深拷贝;
//赋值v3 = v1,深拷贝
//防止重复赋值判断不是自己给自己赋值、释放原来的空间、开辟新的空间再memcpy;
//第二种现代写法就是,传值过来时v就是v1的深拷贝,然后直接swap,v3和v的成员变量就行了,但是这样的话就不用传&,引用的值了,不然会改变值的;
//swap函数得自己写,如果用库里面的swap代价太大,要深拷贝三次,有个tmp这个临时变量,最后还要释放空间;写个swap它内部成员函数的v3.swap(v1);
//自定义swap时,swap要加个预作用限定符::swap(),这样表示调用的是全局的swap,不会自己调自己;
vector<T>& operator=(const vector<T>& v)
{
//第一种
/*if (this != &v)
{
//先将自己空间的东西西释放掉
delete[] _start;//finish等不用释放、因为本来就是_start+ v.size(),在同一块空间上
_start = new T[v.capacity()];//开辟相同的空间
memcpy(_start, v._start, sizeof(T) * v.size());//拷贝
}*/
//第二种
swap(v);//注意在类里面都有this指针的,就是this->swap(v),this指向调用者本身,v3 = v1,那就是v3调用的swap(v)实际是v3.swap(v)
return *this;//要支持连续等于,所以要有返回值;
}
外部的swap,有三次深拷贝,效率太低;
所以用自定义的;可以省去tmp这边的空间;
::swap是调用外部的swap,防止swap自己调用自己;
//自定义swap
void swap(vector<T> v)
{
::swap(_start, v._start);
::swap(_finish, v._finish);
::swap(end_of_storage, v.end_of_storage);
}
const的也要写;会遇到const vector v; v[]
//[]访问,assert
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos)const
{
assert(pos < size());
return _start[pos];
}
//pop_back,尾部删除,assert
void pop_back()
{
assert(_start < _finish);
--_finish;
//换成erase也行
//erase(end() - 1);
}
第二种直接借用insert尾插,更快
void push_back(const T& s)
{
//第一种写法
if (_finish == end_of_storage)
{
size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
reserve(newcapacity);
}
*_finish = s;
_finish++;
//第二种
//insert(_finish,s);
}
//size()
size_t size() const
{
return _finish - _start;
}
//capacity()
size_t capacity() const //拷贝构造时调用v.capacity(),首先这个v是const所以这里后面要加const
{
return end_of_storage - _start;
}
因为在外部调用类,所以在外面是要调用vector,不能继续写模板vector,T要给到具体值的;
在vector{}里面可以写模板的;
void print_v(const vector<int>& v)//指定int类型,这个是类外面的函数了,调用的话给个确定的类型,然后模板才能正常用,如果再类内部是不用给的直接T
{
vector<int>::const_iterator it = v.cbegin();
while (it != v.cend())
{
cout << *it << "";
it++;
}
cout << endl;
}
int a[10];
memset(a,1,sizeof(int)*10)
memset都是按字节处理,int就是4个字节,一个字节8bit;
如上代码不会将a数组每个元素变成1,是将每个元素上面int类型,一共4个字节,每个字节置成1;