目录
vector使用
vector模拟实现
vector实现解析:
memcpy进行元素拷贝问题:
扩容问题:
vector迭代器解析:
vector迭代器失效问题:
1. 示例一:一个典型的迭代器失效bug:insert实现
2. 示例二:insert引起的迭代器失效问题:
3. erase导致迭代器失效问题:
迭代器失效总结:
vector
vector就是一个可变大小的数组,有多种构造函数。有析构函数,因为涉及内存管理,自然也有了拷贝构造和operator=。
大小相关:size获取数组大小,capacity获取当前可存储最大元素个数。主要是resize和reserve,resize改变数组大小,可变大可变小,扩size时可同时对元素初始化。reserve用于扩容,当知道vector大致存储元素个数时,可以用reserve缓解扩容带来的性能消耗。reserve的值小于当前capacity时,不会缩容。大于时会扩容,这里的容量不是size而是capacity。
我们知道,当vector的size==capacity时,再push_back会扩容,那么,vector的扩容机制是怎样的呢?实际上STL并没有规定vector的扩容机制,不同版本的STL有不同的存储策略。vs下capacity是按1.5倍增长的,g++是按2倍增长的。 这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
元素访问:支持operator[],这也是list,forward_list所不支持的。根本原因是底层存储为连续存储。back front at
修改:push_back pop_back,因为底层存储问题,push_front 和 pop_front时间复杂度较高,所以不支持头删头插,这也导致vector不适合做queue的适配容器。而可以做stack的适配容器。还比较重要的就是insert 和 erase了,也没什么,重载了多个版本,适于不同情况下的insert和erase,且都有返回值。insert返回新插入的第一个元素处的迭代器。erase返回删除元素或元素序列后的第一个元素处的迭代器。
说了很多FH,看https://cplusplus.com/reference/vector/vector/即可。
#ifndef STL_VECTOR_H
#define STL_VECTOR_H
#include "reverse_iterator.h"
namespace yzl
{
// 拷贝构造,重载赋值都没实现。
template> // 就是一个用于申请T类型对象的类型而已。Alloc就是这么一个类型
class vector {
public:
typedef T value_type;
typedef value_type *iterator;
typedef const value_type *const_iterator;
typedef yzl::__reverse_iterator reverse_iterator;
typedef yzl::__reverse_iterator const_reverse_iterator;
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
public:
iterator begin() {
return _start;
}
iterator end() {
return _finish;
}
const_iterator begin() const {
return _start;
}
const_iterator end() const {
return _finish;
}
const_iterator cbegin() const {
return _start;
}
const_iterator cend() const {
return _finish;
}
reverse_iterator rbegin()
{
return yzl::__reverse_iterator(end());
}
reverse_iterator rend()
{
return yzl::__reverse_iterator(begin());
}
yzl::__reverse_iterator rbegin() const
{
return yzl::__reverse_iterator(end());
}
const_reverse_iterator rend() const
{
return const_reverse_iterator(begin());
}
const_reverse_iterator crbegin() const
{
return const_reverse_iterator(end());
}
const_reverse_iterator crend() const
{
return const_reverse_iterator(begin());
}
public:
vector()
: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}
// vector(size_t n, const T& val=T())
// : _start(new T[n]), _finish(_start+n), _end_of_storage(_start+n)
// {
// for(size_t i = 0; i < n; ++i)
// *(_start+i) = val; // 调用T的赋值运算符
// }
// 必须有下面这个,如果是size_t T的如上函数,则会出现报错。
vector(int n, const T &value = T())
: _start(new T[n]), _finish(_start + n), _end_of_storage(_finish) {
for (int i = 0; i < n; ++i) {
_start[i] = value;
}
}
template
vector(InputIterator first, InputIterator last)
: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {
// size_t sz = last - first;
while(first+sz != last)
{
++sz;
}
// _start = new T[sz];
// _finish = _start + sz;
// _end_of_storage = _start + sz;
// for(size_t i = 0; i < sz; ++i)
// {
// *(_start+i) = *first;
// ++first;
// }
// 复用版本: 有问题:
while (first != last) {
this->push_back(*first);
first++;
}
}
// vector(const vector &v)
// : _start(new T[v.size()]), _finish(_start + v.size()), _end_of_storage(_finish) {
// // 不能用memcpy,memcpy是逐字节拷贝,若涉及深度拷贝则出错。
// for (size_t i = 0; i < v.size(); ++i) {
// *(_start + i) = *(v._start + i);
// }
// }
// vector拷贝构造的第二种写法:
// vector(const vector& v)
// : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
// {
// reserve(v.size());
// for(const auto& i : v)
// {
// this->push_back(i);
// }
// }
// vector的现代写法:借用其他的构造
vector(const vector &v)
: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {
// 这是要基于迭代器区间构造
vector tmp(v.begin(), v.end());
this->swap(tmp);
}
vector &operator=(vector v) {
this->swap(v);
return *this;
}
~vector() {
if (_start) {
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
// -------------------------------------------------------------------
size_t size() const {
return _finish - _start;
}
size_t capacity() const {
return _end_of_storage - _start;
}
void reserve(size_t n) {
if (n > capacity()) {
size_t oldSize = size();
T *tmp = new T[n];
// 不能memcpy,必须这样,防止元素类型涉及深拷贝。
for (size_t i = 0; i < size(); ++i) {
*(tmp + i) = *(_start + i);
}
delete[] _start;
_start = tmp;
_finish = _start + oldSize;
_end_of_storage = _start + n;
}
}
void resize(size_t n, const T &val = T()) {
if (n > size()) {
reserve(n);
size_t sz = size();
for (; sz < n; ++sz) {
*(_start + sz) = val;
}
_finish = _start + n;
} else {
_finish = _start + n;
}
}
void push_back(const T &val) {
if (_finish == _end_of_storage) {
reserve(size() == 0 ? 4 : 2 * size());
}
*(_start + size()) = val;
++_finish;
}
void pop_back() {
assert(size() > 0);
--_finish;
}
iterator insert(const_iterator position, const value_type &val) {
assert(position >= _start);
assert(position <= _finish);
if (_finish == _end_of_storage) {
size_t diff = position - _start;
reserve(size() == 0 ? 4 : 2 * size());
position = _start + diff; // 纠正迭代器,因为扩容之后原迭代器就失效了 insert引起的迭代器失效!!!!!
}
iterator end = _finish - 1;
while (end >= position) {
*(end + 1) = *(end);
end--;
}
*(_start + (position - _start)) = val; // 调用value_type的赋值操作。
++_finish;
return _start + (position - _start);
}
iterator insert(const_iterator position, size_t n, const value_type &val) {
assert(position >= _start);
assert(position <= _finish);
if (_finish + n > _end_of_storage) {
size_t diff = position - _start;
reserve(size() + n);
position = _start + diff;
}
iterator it = _finish - 1;
while (it >= position) {
*(it + n) = *it;
--it;
}
size_t diff = position - _start;
for (size_t i = diff; i < diff + n; ++i) {
*(_start + i) = val;
}
_finish += n;
return _start + diff;
}
iterator erase(const_iterator pos) {
assert(pos >= _start && pos < _finish);
iterator it = _start + (pos - _start);
while (it != _finish - 1) {
*it = *(it + 1);
++it;
}
--_finish;
return _start + (pos - _start);
}
// iterator erase(const_iterator first, const_iterator lase)
// {
// return first;
// }
T &operator[](size_t n) {
assert(n < size());
return *(_start + n);
}
const T &operator[](size_t n) const {
assert(n < size());
return *(_start + n);
}
bool empty() const {
return _start == _finish;
}
T &front() {
assert(size() > 0);
return *_start;
}
const T &front() const {
assert(size() > 0);
return *_start;
}
T &back() {
assert(size() > 0);
return *(_finish - 1);
}
const T &back() const {
assert(size() > 0);
return *(_finish - 1);
}
void swap(vector &v) {
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
void clear() {
_finish = _start;
}
};
}
#endif //STL_VECTOR_H
1. vector就是靠三个元素指针T*维护的,_start _finish _end_of_storage。所以,每个vector对象中只存储三个指针变量T*,这些指针指向的区域存储着具体的元素。push_back reserve insert erase等操作都是基于这三个指针进行操作。
我们知道,当_finish == _end_of_storage时,表示当前没有多余空间,若进行push_back,则需要扩容,扩容并非简单的原地扩容,而是要经历申请新空间->拷贝元素->释放旧空间的过程。
拷贝元素时,并不能一概使用memcpy进行拷贝,memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。如果拷贝的是简单的内置类型int等,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
void reserve(size_t n) {
if (n > capacity()) {
size_t oldSize = size();
T *tmp = new T[n];
// 不能memcpy,必须这样,防止元素类型涉及深拷贝。
for (size_t i = 0; i < size(); ++i) {
*(tmp + i) = *(_start + i);
}
delete[] _start;
_start = tmp;
_finish = _start + oldSize;
_end_of_storage = _start + n;
}
}
韩信带净化,无多余可用空间时,扩容机制并非一律以2倍或1.5倍扩容,而是要看具体STL实现版本。
但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
因为vector底层实现是在一段连续的内存中存储,所以,vector的迭代器就是原生指针。
typedef T value_type;
typedef value_type *iterator;
typedef const value_type *const_iterator;
typedef yzl::__reverse_iterator reverse_iterator;
typedef yzl::__reverse_iterator const_reverse_iterator;
iterator 即 T* const_iterator 即 const T*
那么,这样可行的原因是什么呢?实际上,我们要看迭代器需要哪些操作:解引用,++,--,==,!=。那么T*完全具有这些操作。并且const T*也完全符合const迭代器的要求,即返回值为const的,且迭代器本身可++,--。
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装。比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器, 程序可能会崩溃)。
那么,那些可能使原生态指针指向空间销毁的情况,也是我们需要注意迭代器失效的情况。
iterator insert(const_iterator position, const value_type &val) {
assert(position >= _start);
assert(position <= _finish);
if (_finish == _end_of_storage) {
size_t diff = position - _start;
reserve(size() == 0 ? 4 : 2 * size());
position = _start + diff; // 纠正迭代器,因为扩容之后原迭代器就失效了 insert引起的迭代器失效!!!!!
}
iterator end = _finish - 1;
while (end >= position) {
*(end + 1) = *(end);
end--;
}
*(_start + (position - _start)) = val; // 调用value_type的赋值操作。
++_finish;
return _start + (position - _start);
}
如上代码中,如果size == capacity,则需要reserve,在reserve之前,存储diff,是因为一旦reserve了,则原来的_start _finish _end_of_storage 包括上方的position就都失效了。这时,对position重新赋值,就可以解决此bug。
void test4()
{
yzl::vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
// v.push_back(5);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
auto p = find(v.begin(), v.end(), 3);
if (p != v.end())
{
// 在p位置插入数据以后,不要访问p,因为p可能失效了。
v.insert(p, 30);
// cout << *p << endl;
v.insert(p, 40);
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
void test4()
{
yzl::vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
auto p = find(v.begin(), v.end(), 3);
if (p != v.end())
{
// 在p位置插入数据以后,不要访问p,因为p可能失效了。
v.insert(p, 30);
cout << *p << endl;
v.insert(p, 40);
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
第一段test4代码中,push_back了4次,此时已满,第一次insert时,是需要reserve的,导致vector的整个存储空间都发生了改变,原来空间上的指针都失效了,而p迭代器就是原生指针,导致此时再进行第二次insert时,就会发生报错,因为访问了非法空间。
而第二段test4中,push_back5次,此时size==5 capacity == 8,在进行两次insert时,不会发生扩容,也就使得p所指向空间还是有效的,程序不会发生报错。并且cout<<*p< 为了解决上述问题,insert函数本身具有迭代器返回值,返回的迭代器指向新插入的第一个元素。所以,为了避免insert带来的迭代器失效问题,我们应该 a、不排除某些STL实现的erase在size b、 error为错误的erase使用,后方为正确的。 我们会发现,随着v的内容不同,基于错误的erase使用,结果不同。12345 1234 124345 会有不同的结果。 比如1234 删除2 ++ 指向4 删除4 ++ 越界。导致程序崩溃。 其实很简单,就是erase的错误使用。所以,erase有返回值,我们需要正确使用erase c、erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代 器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是 没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效 了。 不同IDE使用的不同版本的STL,对于erase操作的管控程度不同,但是根本上来说,erase之后,都不应该再访问原来的迭代器,而必须更新迭代器。 4. 与vector类似,string在经历扩容后,原来的迭代器也会失效。 会引起其底层空间改变的操作,都有可能使迭代器失效,比如:resize、reserve、insert、assign、 push_back等。包括erase。 迭代器失效解决办法:在使用前,对迭代器重新赋值即可。insert和erase因此也有了返回值。 上面两个图就足够清楚了。pos = v.insert(pos, 3);
3. erase导致迭代器失效问题:
void test5()
{
yzl::vector
比如124345 删除2 ++ 指向3 ++ 删除4 ++ 等于end,程序不崩溃,但是没有删除第一个4迭代器失效总结:
vector