文章目录
1.vector的常用接口
先查看vector的常用接口,选择比较常用的接口进行模拟。
STL源码的部分定义
template<class T>
class vector {
public:
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type* iterator;
typedef const value_type* const_iterator;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
iterator start;
iterator finish;
iterator end_of_storage;
public:
iterator begin() { return start; }
const_iterator begin() const { return start; }
iterator end() { return finish; }
const_iterator end() const { return finish; }
reverse_iterator rbegin() { return reverse_iterator(end()); }
const_reverse_iterator rbegin() const {
return const_reverse_iterator(end());
}
reverse_iterator rend() { return reverse_iterator(begin()); }
const_reverse_iterator rend() const {
return const_reverse_iterator(begin());
}
size_type size() const { return size_type(end() - begin()); }
size_type max_size() const { return size_type(-1) / sizeof(T); }
size_type capacity() const { return size_type(end_of_storage - begin()); }
bool empty() const { return begin() == end(); }
reference operator[](size_type n) { return *(begin() + n); }
const_reference operator[](size_type n) const { return *(begin() + n); }
vector() : start(0), finish(0), end_of_storage(0) {}
vector(size_type n, const T& value) { fill_initialize(n, value); }
vector(int n, const T& value) { fill_initialize(n, value); }
vector(long n, const T& value) { fill_initialize(n, value); }
explicit vector(size_type n) { fill_initialize(n, T()); }
}
2.myvector的构建
template<class T>
class myvector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
/*....
类方法定义
*/
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
3.myvector类方法的实现
3.1myvector的构造函数实现
/*
源码中vector的构造函数
vector() : start(0), finish(0), end_of_storage(0) {}
*/
//这里,我们将迭代器初始化为nullptr
myvector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr) {}
myvector(int n,const T&x=T())
{
T* tmp = new T[n];
_start = _finish = tmp;
while (n--)
{
*_finish = x;
_finish++;
}
_endofstorage = _finish;
}
3.2end()和begin()
分别返回指向第一个元素的迭代器和指向最后一个元素的迭代器;
iterator begin() { return _start; }
const_iterator begin() const { return _start; }
iterator end() { return _finish; }
const_iterator end() const { return _finish; }
3.3size()、capacity()和empty()
/*
源码中vector的size()和capacity()
size_type size() const { return size_type(end() - begin()); }
size_type capacity() const { return size_type(end_of_storage - begin()); }
*/
/*
1.迭代器相减就是中间元素的个数
2.保证const类和非const类都可以调用,为了防止权限的放到,需要对this指针进行修饰
*/
size_t size() const {return size_t(end()-begin());}
size_t capacity() const {return size_t( _endofstorage-begin());}
bool empty() const {return begin()==end();}
3.4[ ]重载
/*
源码中vector的size()和capacity()
typedef value_type& reference;
typedef const value_type& const_reference;
reference operator[](size_type n) { return *(begin() + n); }
const_reference operator[](size_type n) const { return *(begin() + n); }
*/
//可能或出现越界的情况,所以对于越界访问可以加上断言或者抛异常
//写法一:
T& operator[](size_t pos){
assert(pos < size());
return _start[pos];
}
//防止权限的放大,需要用const修饰this
const T& operator[](size_t pos) const{
assert(pos < size()&&pos>-1);
return _start[pos];
}
//写法二:
T& operator[](size_t pos){
assert(pos<size()&&pos>-1);
return *(begin()+pos);
}
//防止权限的放大,需要用const修饰this
const T& operator[](size_t pos) const{
assert(pos<size()&&pos>-1);
return *(begin()+pos);
}
3.5resize()和reserve()
/*
STL源码里的resize()和reserve()使用了空间配置器
void reserve(size_type n) {
if (capacity() < n) {
const size_type old_size = size();
iterator tmp = allocate_and_copy(n, start, finish);
destroy(start, finish);
deallocate();
start = tmp;
finish = tmp + old_size;
end_of_storage = start + n;
}
}
void resize(size_type new_size, const T& x) {
if (new_size < size())
erase(begin() + new_size, end());
else
insert(end(), new_size - size(), x);
}
*/
//这里我们实现简单的myvector,暂时不使用空间配置器
//可能传入的T类型是自定义类型,最好调用T的构造函数进行初始化
//目前的写法会出现迭代器失效的情况,下面将给出改正
void reserve(size_t n)
{
if(n>capacity())
{
T* tmp=new T[n];
if(_start)
{
memcpy(tmp,begin(),size()*sizeof(T));
delete[] _start;
}
_start=tmp;
_finish=_start+size();
_endofstoage = _start + n;
}
}
void resize(size_t newsize,const T x=T()){
if(newsize>capacity())
{
resize(newsize);
}
if(n>size())
{
//对内容进行赋值
while(_finish<_start+newsize)
{
*finish=x;
finish++;
}
}
else
{
_finish=_start+newsize;
}
}
3.5.1push_back()
void push_back(const T&x)
{
//如果空间不够就要开辟空间
/*
if(_finish== _endofstorage)
{
size_t newcapacity=capacity()==0?2:2*capacity();
reserve(newcapacity);
}
*_finish=x;
*_finish++;
*/
insert(end(),x);
}
3.5.2迭代器失效
对于上面的接口,我们调用test()函数进行测试
void test()
{
myvector<int>r(10);
r.push_back(1);
r.push_back(2);
r.push_back(3);
for (auto i : r)
{
cout << i << " ";
}
cout << endl;
}
int main()
{
test();
return 0;
}
//结果程序崩溃
很明显发生了越界访问,接下来进行调试找出程序崩溃的原因。
程序崩溃的点出现在reserve()中,而且可以看到_ finish的值没有变化,_ _ finsh= _ start+size()=_ start+_ finish-_ start=_ finish(因为_ start发生了改变而_ finish没有改变,所以_ finish的值没有变化)
这种情况属于迭代器失效的一种;可以先保存一个size避免迭代器失效
void reserve(size_t n)
{
size_t size=size();
if(n>capacity())
{
T* tmp=new T[n];
if(_start)
{
memcpy(tmp,begin(),size()*sizeof(T));
delete[] _start;
}
_start=tmp;
}
_finish=_start+sz;
_endofstorage = _start + n;
}
3.5.3 pop_back()尾删函数
/*
源码:
void pop_back() {
--finish;
destroy(finish);
}
*/
//判断一下_finish的位置
void pop_back(){
if(_finish>_start)
{
_finish--;
}
}
3.6insert()和erase()
//先实现简单的insert(),在任意位置插入一个数据
//返回指向新添加元素的迭代器
iterator insert(iterator pos,const T& x)
{
//断言
assert(pos>=_start&&pos<=_finish);
if(_finish==_endofstorage){
size_t newcapacity=capacity()==0?4:2*capacity();
reserve(newcapacity);
}
iterator ed=_finish;
while(ed>pos)
{
*ed=*(ed-1);
ed--;
}
*pos=x;
_finish++;
return pos;
}
3.6.1迭代器失效
第一次插入时插入了一个随机值,下面通过调试解决。
可以观察到pos在扩容后还是指向的扩容前的位置,是在原位置插入了数据,所以无法在扩容后的数组中插入数据。
解决方法:记录插入位置到第一个元素的相对距离,然后在扩容后的数组相对距离相等的位置插入数据
iterator insert(iterator pos,const T& x)
{
//断言
size_t n=pos-_start;
assert(pos>=_start&&pos<=_finish);
if(_finish==_endofstorage){
size_t newcapacity=capacity()==0?4:2*capacity();
reserve(newcapacity);
pos=_start+n;
}
iterator ed=_finish;
while(ed>pos)
{
*ed=*(ed-1);
ed--;
}
*pos=x;
_finish++;
return pos;
}
3.6.2为什么不传引用
iterator insert(iterator&pos,const T& x)
如果传入引用,有些情况就调用不了。比如v.insert(v.begin(),20)传不了,因为insert()是传值返回,传值返回返回的是临时变量,不能传给引用类型的参数。
3.6.3erase()
/*
源码:
iterator erase(iterator position) {
if (position + 1 != end())
copy(position + 1, finish, position);
--finish;
destroy(finish);
return position;
}
*/
iterator erase(iterator pos)
{
assert(pos>=_start&&pos<_finish);
iterator bg=pos;
while(bg<_finish-1)
{
*bg=*(bg+1);
bg++;
}
_finish--;
return pos;
}
3.6.4erase()中的迭代器失效
(1).windows环境下vs编译器强制检查导致erase()失效
vs编译器中强制检查pos是否失效;
void test1() {
vector<int>m;
m.push_back(1);
m.push_back(2);
m.push_back(3);
m.push_back(4);
vector<int>::iterator pos = find(m.begin(), m.end(), 2);
m.erase(pos);
cout << *pos << endl;
}
int main()
{
test1();
return 0;
}
按道理删除操作结束后的pos指向应该为3:
但是对pos解引用访对应的元素,发生了权限冲突。
执行erase(pos)以后pos失效,pos的意义变了,原本指向的是2现在指向的是3。vs编译器对pos的访问权限进行了限制。再进行访问时就会报权限冲突。
对于这种情况,不同的编译器处理方式不同,下面是linux环境下的运行结果:
**整体总结:**对于insert和erase造成迭代器失效问题,linux平台检查依靠操作系统自身的野指针检查机制;而windows下检查更加严格。
(2)删除所有的偶数
void test_vetcor()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
auto it=v.begin();
while(it!=v.end())
{
if(*it%2==0)
{
v.erase(it);
}
it++;
}
for(auto e:v)
{
cout<<e<<" ";
}
cout<<endl;
}
Segmentation fault;结果发生段错误。
最后it会越过end(),循环一直进行。
//正确的写法
void test_vetcor()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
auto it=v.beign();
while(it!=v.end())
{
if(*it%2==0){
it=v.erase(it);
}
else
{
it++;
}
}
for(auto e:v)
{
cout<<e<<" ";
}
cout<<endl;
}
输出结果为1 3 5
3.6.5pop_back()
//可以直接赋用erase()
void pop_back()
{
/*
end()返回是一个临时变量,临时变量具有常性,因此不能使用--;
erase(end()--);
*/
erase(this->end()-1);
}
3.7析构函数
~myvector()
{
if(_start){
delete[] _start;
_start=_finish=_endofstoage=nullptr;
}
}
3.8myvector的其他重载的构造函数
//用迭代器构造
template <class InputIterator>
myvector(InputIterator first,InputIterator last){
while(first!=last){
push_back(*first);
first++;
}
}
3.9myvector的拷贝构造
void Swap(myvector<T>&v)
{
std::swap(_start,v._start);
std::swap(_finish,v._finish);
std::swqp(_endofstoage,v._endofstoge);
}
myvector(const myvector<T>&v)
{
myvector<T>tmp(v.begin(),v.end());
Swap(tmp);
}
//可以连续赋值所以需要返回值为引用传参
//传递变量的拷贝,拷贝是临时变量
//直接传递临时变量,生命周期结束自动销毁;
myvector<T>& operator=(myvector<T> V)
{
Swap(v);
return *this;
}
myvector(size_t n,const T& val=T())
:_start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
reserve(n);
for(int i=0;i<n;i++)
{
push_back(val);
}
}
3.10myvector构造函数的参数匹配问题
void test()
{
//目的:构造10个int类型,值为2
myvector<int>m(10,2);
}
int main()
{
test();
return 0;
}
报错:非法间接寻址。
/*
进行调试,可以发现myvector<int>m(10,2)调用的不是我们预想调用的myvector(size_t n,const T& val=T()),
而是调用了迭代器参数的构造函数。
template <class InputIterator>
myvector(InputIterator first,InputIterator last)
*/
参数匹配问题
程序运行时,编译器会调用最匹配和最合适的函数
template
myvector(InputIterator first,InputIterator last)两个参数相同,而myvector(10,2)编译器会认为是两个int类型的参数,所以编译器会调用迭代器类型的构造函数。
解决方法
//将size_t类型转换为int类型
myvector(int n,const T& val=T())
:_start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
reserve(n);
for(int i=0;i<n;i++)
{
push_back(val);
}
}
4.深层次的深浅拷贝问题
以拷贝杨辉三角形为例
//杨辉三角构造类
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>>ve;
//开空间
ve.resize(numRows);
for (int i = 0;i < numRows;i++) {
ve[i].resize(i + 1);
ve[i][0] = 1;
ve[i][i] = 1;
}
for (int i = 1;i < numRows;i++) {
for (int j = 1;j < ve[i].size();j++) {
//记忆化搜索
if (ve[i][j] == 0) {
ve[i][j] = ve[i - 1][j] + ve[i - 1][j - 1];
}
}
}
return ve;
}
};
拷贝
void copy()
{
//先调用Solution的构造函数,然后访问成员函数
myvector<myvector<int>>vv = Solution().generate(5);
for (int i = 0;i < vv.size();i++)
{
for (int j = 0;j < vv[i].size();j++)
{
cout << vv[i][j] << " ";
}
cout << endl;
}
}
int main()
{
copy();
return 0;
}
在析构函数的位置发生了权限冲突。
当进行到return ve时,会调用拷贝构造
4.1剖析原因
4.1.1vector存储模型
vector中存放了三个成员变量,_ start指针、_ finish指针和_endofstorage指针。
4.1.2函数赋用关系
当容量小于等于4时,myvector<myvector>不会发生扩容。当插入第5行的内容时,就需要调用reserve()扩容。而memcpy()属于浅拷贝,只会拷贝指向前四行的指针。tmp前四个元素和原_start前四个元素指向同一块空间
下一步_start指向的空间被delete释放,调用析构函数。tmp前四个元素指向的空间也被释放。然后insert()第5行。
因此我们可以看到打印的结果是,前四行是随机值,第5行是正确的值。
4.2解决方式
我们需要对浅拷贝的部分进行深拷贝,将memcpy部分进行一一拷贝。
void reserve(size_t n)
{
size_t sz=size();
if(n>capacity())
{
T* tmp=new T[n];
if(_start)
{
for(size_t i=0;i<size();i++)
{
tmp[i]=_start[i];
}
delete[] _start;
}
_start=tmp;
_finish=_start+size();
_endofstoage = _start + n;
}
}