小言_互联网的博客

【C++】浅谈vector(迭代器失效,深浅拷贝)

456人阅读  评论(0)

vector介绍及其使用

vector介绍

  1. vector是表示可变大小数组的序列容器。
  2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自
    动处理。
  3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是
    一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
  4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存
    储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
  5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
  6. 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和forward_lists统一的迭代器和引用更好

vector接口使用(实现)


在STL底层vector其实是有三个迭代器来实现的,我们经常将其看为sizecapacity,start,finish, end_of_storage.其实他们底层是模板,拿到数据后通过模板来推他们的数据类型。

底层结构

template<class T>
	class vector
	{
   
	public:
		typedef *T iterator;
		iterator begin()
		{
   
			return _start;
		}
		iterator end()
		{
   
			return _finish;
		}
		
	private:
		iterator _start;
		itertaor _finish;
		iterator _endofstorage;
	};

构造

1.当我们需要新的vector时候,就可以用迭代器,通过reserve来预留空间,然后将迭代器传入进行拷贝构造。
2.当swap交换的时候可以利用this指针来进行简单操作,原理是利用新创建的vector来和之前的想要赋值的v进行交换,然后将创建的值给this,出去调用析构销毁创建的新vector即可。
3.赋值操作符也是一样,利用this指针来交换然后返回*this。

template< class inputiterator>

		vector(inputiterator.first, inputiterator.last)//创建新vector
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
   
			std::reserve(last - first);//预留空间
			while (last != first)
			{
   
				push_back(*first);
				++first;
			}
		}
		swap(vector<T>& v)
		{
   
			this._start->swap(v._start);//以下同理 类中默认第一个参数为this
			swap(v._finish);
			swap(v._endofstorage);
		}
		vector()//无参
			:_start(nullptr)
			_finish(nullptr)
			_endofstroage(nullptr)
		{
   }
		vector(class<T>&v)
			:_start(nullptr)
			_finish(nullptr)
			_endofstroage(nullptr)
		{
   
				class<T> tmp(v.begin, v.end);
				swap(tmp);
				//当tmp被交换后成空vector后续直接调析构销毁
		}
		vector<T>&operator=(class<T>&v)
		{
   
			this->swap(v);
			return *this;
		}
		~vector()
		{
   
			delete[] _start;
			_start = _finish = _enofstorage = nullptr;
		}

reserve AND resize

这里需要进行数据的拷贝,但不可以是想string一样进行memcpy拷贝,因为memcpy是浅拷贝,当我们对数据进行增容时,capacity会开辟一块新空间来存储,因为string的类型是单一的而vector的成员可能是自定义类型所有就不能浅拷贝,不然会造成内存泄漏。




结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

void reserve(size_t n)
		{
   
			if (n > capacity())
			{
   
				size_t oldSize = size();
				T* tmp = new T[n];
				if (_start)
				{
   
					for (size_t i = 0; i < oldSize; ++i)
						tmp[i] = _start[i];
				}
				_start = tmp;
				_finish = _start + size;
				_endOfStorage = _start + n;
			}
		}

如果非要用memcpy进行拷贝,就得reserve开一块空间,将数据拷贝过去,然后对start finish endofstroge之后进行操作,不然就会指向之前已经释放的空间,就会出问题。

void resize(size_t n, const T& value = T())
		{
   
			// 1.如果n小于当前的size,则数据个数缩小到n
			if (n <= size())
			{
   
				_finish = _start + n;
				return;
			}
			// 2.空间不够则增容
			if (n > capacity())
				reserve(n);
			// 3.将size扩大到n
			iterator it = _finish;
			iterator _finish = _start + n;
			while (it != _finish)
			{
   
				*it = value;
				++it;
			}
		}

先保存_finish的位置然后对其进行操作,然后对it之后的值进行数据填充。

迭代器失效

首先要了解为什么会失效。

所以当需要插入的时候需要先保存pos和start的差值,然后在进行操作

iterator insert(iterator pos, x)
		{
   
			size_t posi = pos - _start;
			check_capacity()//**********
				pos = posi + _start;
			//因为插入所以挪数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
   
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++finish;//size++
			return pos;
		}

在删除时迭代器如何失效的呢?

vector erase(iterator pos)
		{
   
			iterator it = pos + 1;
			while (it != _finish)
			{
   
				*(it - 1) = *it;
			}
			--_finish;
			return pos;
		}

vector使用erase删除元素,其返回值指向下一个元素,但是由于vector本身的性质(存在一块连续的内存上),删掉一个元素后,其后的元素都会向前移动,所以此时指向下一个元素的迭代器其实跟刚刚被删除元素的迭代器是一样的


转载:https://blog.csdn.net/weixin_43762735/article/details/115413424
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场