vector的迭代器失效、深拷贝、模拟实现
迭代器失效
vector容器支持插入和删除操作.
insert:插入的成员函数原型是iterator insert (iterator position, const value_type& val);
给定一个位置的迭代器,在该位置插入一个元素,返回一个新的迭代器,指向插入的那个元素。
vector<int> v{1, 2, 3};
vector<int>::iterator it = v.begin() + 1;
it=v.insert(it, 9);
for (auto e : v)
cout << e << ' ';
cout << endl;
cout << "返回的迭代器指向" << *it << endl;
运行结果:
1 9 2 3
返回的迭代器指向9
在insert的过程中可能会出现迭代器失效,指的是在进行insert操作的时候vector如果容量不够会发生扩容。vector的扩容有3个步骤:
- 开辟一块新的空间
- 将原来空间的内容拷贝到新空间
- 在新空间进行插入
- 释放旧空间
迭代器失效的原因是当开辟了新空间以后,原来使用的迭代器依然指向旧空间,此时如果在使用该迭代器就是野指针。
vector<int> v{1, 2, 3};
v.reserve(3);
vector<int>::iterator it = v.begin() + 1;
v.insert(it, 9);//此时进行了扩容,将原数据拷贝到新空间
for (auto e : v)
cout << e << ' ';
cout << endl;
v.insert(it, 9);//it已经是野指针,迭代器失效
for (auto e : v)
cout << e << ' ';
cout << endl;
运行结果:
1 9 2 3
0 1 9 2 3
*** Error in `./mybin': double free or corruption (out): 0x0000000001da2c40 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x81329)[0x7f6f6cb30329]
由于在进行insert操作时扩容会导致迭代器失效,所以在使用迭代器it以后就不要在使用了,如果想要多次插入,正确的做法接收insert函数的返回值,不是一直使用it。虽然在进行insert的时候it可能不会失效(没有发生扩容),可以继续使用,但是不推荐这种做法,不安全。
erase:删除的成员函数原型是iterator erase (iterator position);
,给定一个位置的迭代器,删除该位置的元素,返回值是指向删除位置的迭代器。
vector<int> v{1, 2, 3};
vector<int>::iterator it = v.begin() + 1;
it = v.erase(it);
for (auto e : v)
cout << e << ' ';
cout << "删除元素以后返回的迭代器指向" << *it << endl;
运行结果:
1 3 删除元素以后返回的迭代器指向3
理论上而言,erase是删除元素,不会存在扩容,也就没有迭代器失效的问题,然而实际上在不同的平台下对于erase有不同的规定,在windows平台下,进行erase了以后会对it做强制检查,禁止再次直接使用it.在Linux平台是可以再次直接使用it的。
vector<int> v{1, 2, 3};
vector<int>::iterator it = v.begin() + 1;
v.erase(it);
for (auto e : v)
cout << e << ' ';
cout<<endl;
v.erase(it);//在vs2022下,不允许在直接使用it(可以用it接收第一个erase的返回结果,然后使用)
for (auto e : v)
cout << e << ' ';
cout<<endl;
上面这段程序在windows下是报错的,在Linux下可以通过,结果是:
1 3
1
在有的版本的STL中,vector进行erase可能会有缩容的操作,以时间换空间。这种情况下会出现迭代器失效的问题。所以进行erase以后也不要在直接使用it了,正确的做法是接收erase的返回值。
深拷贝
由于vector内部在堆区申请了空间,所以在进行拷贝构造和赋值运算符重载时,需要进行深拷贝,否则会出现不同对象共用同一块空间的问题。
深拷贝的做法是在堆区重新为v2开辟一块空间,并让v2的指针指向重新开辟的那块空间
vector拷贝构造函数的写法
template<typename T>
class vector
{
public:
//......
vector(vector<T>& v)
{
_start=new T[v.capacity()];//开空间
_finish=_start+v.size();
_end_of_storage=_start+v.capacity();
for(int i=0;i<v.size();i++)//拷贝原数据
{
*(_start+i)=v[i];
}
}
private:
_start;
_finish;
_end_of_storage;
};
vector的拷贝构造函数中,拷贝原数据不能使用memcpy,因为vector是一个模板类,T可能是一个自定义类型,而使用memcpy只会把原数据进行简单的值拷贝,可能引发更深层次的拷贝的问题。例如当T是一个vector<int>
的时候,就会引发更深层次的拷贝问题。
如果T是一个vector<int>
类型,进行memcpy的话只对第一层进行了深拷贝,对每一个元素vv[]是浅拷贝。不能解决更深层次的深拷贝问题,拷贝原数据的步骤应该写成for循环,这样的话在拷贝数据时,对于自定义类型,是调用它的赋值运算符重载,对于内置类型也能完成值拷贝的工作。可以实现更深层次的深拷贝
for(int i=0;i<v.size();i++)//拷贝原数据
{
*(_start+i)=v[i];
}
模拟实现
vector是一个模板类,底层在物理空间上是连续的。
成员变量
template<typename T>
class vector
{
private:
T* _start;//指向起始元素
T* _finish;//指向结束元素的下一个元素
T* _end_of_storage;//指向开辟空间的下一个位置
};
迭代器
typedef T *iterator; // vector的迭代器是原生指针
typedef const T *const_iterator; // const迭代器
成员函数
//无参的默认构造函数
vector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}
//迭代器区间的构造函数
template <class Inputiterator> //使用模板的原因是可以支持不同类型的迭代器
vector(Inputiterator first, Inputiterator last)
: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
while (first != last)
{
push_back(*first);
first++;
}
}
//用n个对象进行构造
vector(size_t n, const T &val = T())
: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
for (int i = 0; i < n; i++)
push_back(val);
}
//为了防止与使用模板的迭代器区间的构造函数混淆,将n个对象构造的构造函数写一个重载
vector(int n, const T &val = T())
: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
for (int i = 0; i < n; i++)
push_back(val);
}
//析构函数
~vector()
{
if (!_start)
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
void reserve(size_t n)
{
assert(n > _end_of_storage - _start); //确保真的在"扩容"
T *tmp = new T[n];
int count = _finish - _start; // count表示原来空间的元素个数
for (int i = 0; i < count; i++) //这里拷贝必须使用for循环,不能使用memcpy,因为T可能是自定义类型,需要深拷贝
tmp[i] = _start[i]; //将原空间的内容拷贝到新空间
delete[] _start; //释放掉原来的空间
_start = tmp;
_finish = _start + count;
_end_of_storage = _start + n; //更新_start,_finish,_end_of_storage
}
void push_back(const T &t) //使用引用,因为T可能是自定义类型
{
if (_finish == _end_of_storage) //需要扩容
{
int newcapacity = _start == nullptr ? 4 : 2 * (_end_of_storage - _start);
reserve(newcapacity); //扩容以后,_start,_finish,_end_of_storage已经变化
}
*_finish = t;
_finish++;
}
size_t capacity() const //普通对象和const对象都能调用
{
return _end_of_storage - _start;
}
size_t size() const
{
return _finish - _start;
}
// v[2]
T& operator[](size_t n) //重载[]
{
assert(n < this->size()); //断言
return *(_start + n);
}
//要给const对象也提供[]
const T& operator[](size_t n)const
{
assert(n< this->size());
return *(_start+n);
}
bool empty() const
{
return _start == _finish;
}
void pop_back() //删除尾部元素
{
assert(!empty()); //不为空才能删除元素
_finish--; //不用真删除,_finish--即可
}
iterator insert(iterator pos, const T &val) //在迭代器位置进行插入元素
{
if (_finish == _end_of_storage) //满了
reserve((_end_of_storage - _start) * 2);
iterator tmp = _finish;
while (tmp > pos)
{
*tmp = *(tmp - 1); //后移
tmp--;
}
*pos = val;
_finish++; //插入以后_finish++
return pos; //返回指向新插入元素的迭代器
}
iterator begin() //普通对象的起始迭代器
{
return _start;
}
iterator end() //普通对象的结束迭代器
{
return _finish;
}
const_iterator begin() const // const对象的起始迭代器
{
return _start;
}
const_iterator end() const // const对象的结束迭代器
{
return _finish;
}
iterator erase(iterator pos)
{
assert(!empty());
iterator tmp = pos + 1;
while (tmp != _finish) //将pos及其之后的元素前移
{
*(tmp - 1) = *tmp;
tmp++;
}
_finish--;
return pos;
}
// vector(const vector<T> &v) //拷贝构造函数,传统写法
// {
// _start = new T[v.size()]; //这里new size个,防止空间浪费
// for (int i = 0; i < v.size(); i++)//使用for循环,当T是自定义类型,调用赋值重载
// _start[i] = v[i];
// _finish = _end_of_storage = _start + v.size();
// }
//v2(v1),拷贝构造的现代写法
// vector(const vector<T> &v)
// : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
// {
// reserve(v.size()); //先给v2足够空间
// for (const auto &e : v) //使用引用,T可能是自定义类型
// push_back(e); //将v1中的元素往v2插入
// }
//使用迭代器区间的构造函数实现拷贝构造函数的纯现代写法.v2(v1)
vector(const vector<T> &v)
: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
vector<T> tmp(v.begin(), v.end()); //先根据迭代器区间构造一个tmp
swap(tmp); //将this的成员变量和tmp交换,而且会调用tmp的析构,所以初始化列表要初始化
}
void swap(vector<T> &tmp)
{
std::swap(_start, tmp._start);
std::swap(_finish, tmp._finish);
std::swap(_end_of_storage, tmp._end_of_storage);
}
// v.resize(1)
void resize(size_t size, const T &val = T()) // resize复用reserve
{
if (size <= this->size())
{
_finish = _start + size;
}
else if (size > this->size() && size <= capacity())
{
iterator cur = _finish;
_finish = _start + size;
while (cur != _finish)
{
*cur = val;
cur++;
}
}
else //需要扩容
{
reserve(size);
_end_of_storage = _start + size;
while (_finish != _end_of_storage)
{
*_finish = val;
_finish++;
}
}
}
// 可以使用resize实现拷贝构造.v2(v1)
// vector(const vector<T> &v)
// : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
// {
// resize(v.size()); //此时v2中有size个默认生成的对象
// for (size_t i = 0; i < v.size(); i++)
// (*this)[i] = v[i]; //当T时自定义类型,调用赋值重载,赋值重载里面会对默认生成的对象进行处理
// }
// v1=v2,赋值运算符重载
vector<T> &operator=(vector<T> v) //这里要的就是传值,v是v2的拷贝
{
swap(v); //交换以后,实现了v的属性到v1上,并且调用v的析构,清理v1的资源。
//所以进行传值操作,如果传引用,交换以后v2会被修改。
return *this;
}
T& front()//顺序表头部元素
{
return *_start;
}
const T& front()const//顺序表头部元素
{
return *_start;
}
T& back()//顺序表尾部元素
{
return *(_finish-1);
}
const T& back()const //顺序表尾部元素
{
return *(_finish-1);
}