C++ vector【常用接口、迭代器失效问题、模拟实现等】

目录

1.vector的介绍及使用

1.1 vector的介绍

1.2 vector的使用

1.2.1 vector的定义

构造函数声明

接口说明

1.2.2 vector iterator 的使用

iterator的使用

接口说明

1.2.3 vector 空间增长问题

容量空间

接口说明

1.2.3 vector 增删查改

vector增删查改

接口说明

1.2.4 vector 迭代器失效的问题(重点)

2.vector模拟实现


1.vector的介绍及使用

1.1 vector的介绍

1. vector是表示可变大小数组的序列容器。

2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。

3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是 一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大 小。

4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存 储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是 对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增 长。

6. 与其它动态序列容器相比(deque, list and forward_list,vector在访问元素的时候更加高效,在末 尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list 统一的迭代器和引用更好。

1.2 vector的使用

下面将介绍常用接口。

1.2.1 vector的定义

构造函数声明

接口说明

vector(重点)无参构造
vector(size_type n,const value_type& val=value_type())构造并初始化n个val
vector(const vector& x)(重点)拷贝构造
vector(InputIterator first, InputIterator last);使用迭代器进行初始化构造

1.2.2 vector iterator 的使用

iterator的使用

接口说明

begin + end (重点)

获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置 的iterator/const_iterator
rbegin + rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的 reverse_iterator

1.2.3 vector 空间增长问题

容量空间

接口说明

size获取数据个数
capacity获取容量大小
empty判断是否为空
resize(重点)改变vector的size
reserve(重点)改变vector的capacity

1.capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。具体增长多少是根据具体的需求定义 的。vs是PJ版本STL,g++是SGI版本STL。

2.reserve只负责开辟空间,如果确定知道需要用多少空间,提前reserve可以缓解vector增容的代价缺陷问题。

3.resize在开空间的同时还会进行初始化,影响size。

根据上面的演示,可以看出vs基本是按照1.5倍方式扩容

1.2.3 vector 增删查改

vector增删查改

接口说明

push_back(重点)尾差
pop_back(重点)尾删
find查找(这个是算法模块实现,不是vector的成员接口)
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据空间
operator[](重点)

像数组一样访问

1.2.4 vector 迭代器失效的问题(重点)

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T*。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

对于vector可能会导致其迭代器失效的操作有:

  1.会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。

(下图)为什么会出现随机值的情况?

根据上图,我们发现①②③④⑤的操作都有可能导致vector扩容,旧空间被释放,数据存放在新空间中。

而我们此时的迭代器 it ,指向的是被释放掉的旧空间。在完成打印时,实际操作的是一块已经被释放的空间,而引起代码运行崩溃。

如何解决上述问题?在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可。(如下图)

2. 指定位置元素的删除操作——erase

erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。(所以"cout<<*pos<<endl;"会导致非法访问,但是在vs2022上会正常运行)

以下代码的功能是删除vector中所有的偶数,请问那个代码是正确的,为什么?

答案:②是正确的,因为前面说过再erase后,vs认为该位置的迭代器失效了。若还想继续使用it,需要对it进行更新,即“it=v.erase(it);”。

在vs2022上①和②都会正常运行。

3. 注意:Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

 4. 与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效

通过1.2.4的讲解,在不同版本,不同的编译器下运行,可能出来的结果都是不一样的。为了我们的代码在每个环境下都能正常运行,所以我们遇到1.2.4的问题的时候,都按严格要求完成代码。对可能产生迭代器失效做好及时更新。

2.vector模拟实现

vector.h

#pragma once
namespace wmm
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}

		//vector() = default;//强制编译器生成默认构造
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{

		}

		vector(initializer_list<T> il)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(il.size());
			for (auto e : il)
			{
				push_back(e);
			}
		}

		vector(const vector<T>& vv)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(vv.capacity());

			for (auto e : vv)
			{
				push_back(e);
			}
		}

		template<class inputiterator>
		vector(inputiterator first, inputiterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		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(size_t n, const T& val = T())
		//{
		//	for (int i = 0; i < n; i++)
		//	{
		//		push_back(val);
		//	}
		//}
		//vector(size_t n, const T& val = T())和vector(int n, const T& val = T())
		//为什么一个size_t,一个int
		//如果输入的两个值都为整型,eg:vector<int> v(9, 10);
		//那编译器将会调用vector(inputiterator first, inputiterator last)
		//而不是按照我们的想法调用这个vector(size_t n, const T& val = T())或vector(int n, const T& val = T())
		//解决方法:①vector<int> v(9u, 10);+上字母u,代表调用无符号整型,调用vector(size_t n, const T& val = T())
		//②要不然就是参数类型改成int,编译器会在vector(inputiterator first, inputiterator last)和vector(int n, const T& val = T())之间,优先选择符合情况且非模板的函数,即vector(int n, const T& val = T())

		void swap(vector<T>& vv2)
		{
			std::swap(_start,vv2._start);
			std::swap(_finish,vv2._finish);
			std::swap(_end_of_storage,vv2._end_of_storage);
		}

		vector<T>& operator=(vector<T> vv2)
		{
 			swap(vv2);

			return *this;
		}

		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}

		size_t capacity()
		{
			return _end_of_storage - _start;
		}
		
		size_t size() const
		{
			return _finish - _start;
		}

		size_t capacity() const
		{
			return _end_of_storage - _start;
		}

		size_t size()
		{
			return _finish - _start;
		}

		void reserve(size_t n)
		{
			if(n>capacity())
			{ 
				size_t oldsize = size();
				T* tmp = new T[n];

				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T)*size());//如果遇到string类型,memcpy是浅拷贝,会导致野指针
					for (size_t i = 0; i < oldsize; i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				
				_start = tmp;
				_finish = _start + oldsize;
				_end_of_storage = _start + n;
			}
		}

		T& operator[](size_t pos)
		{
			assert(pos < size());

			//return *(_start + pos); //这个也可以
			return _start[pos];
		}

		const T& operator[](size_t pos) const
		{
			assert(pos < size());

			//return *(_start + pos); //这个也可以
			return _start[pos];
		}

		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			++_finish;
		}

		void pop_back(const T& x)
		{
			assert(size() > 0);

			--_finish;
		}

		iterator insert(iterator pos,const T& x)
		{
			assert(pos >= _start);
			assert(pos <= _finish);

			if (_finish == _end_of_storage)
			{
				
				size_t len = pos - _start;//pos指向的还是原来旧空间的位置,计算出位置

				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);

				pos = _start + len;//再让pos指向新空间的的相应位置
				//pos是迭代器(其实我们的模拟实现用指针方式实现),如果不这样做会产生迭代器失效的问题(pos指向被释放的空间(出现野指针也是迭代器失效的一种情况))
			}
			
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *(end);
				--end;
			}
			*pos = x;
			++_finish;

			return pos;
		}

		void erase(iterator pos)
		{
			assert (pos >= _start);
			assert (pos <=_finish);

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

	private:
		/*iterator _start=nullptr;
		iterator _finish=nullptr;
		iterator _end_of_storage=nullptr;*/
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

	void Test()
	{
		vector<int> v;
		//push_back
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		
		for (auto e : v)
		{
			cout << e << endl;
		}
		cout << endl;

		//operator[];
		v[0] = 0;
		for (auto e : v)
		{
			cout << e << endl;
		}
		cout << endl;

		//insert
		v.insert(v.begin()+1, 1);
		for (auto e : v)
		{
			cout << e << endl;
		}
		cout << endl;
		v.insert(v.end(), 4);
		for (auto e : v)
		{
			cout << e << endl;
		}
		cout << endl;

		int x;
		cin >> x;
		// 没有x就不插入,有x的前面插入
		vector<int>::iterator it2 = find(v.begin(), v.end(), x);
		if (it2 != v.end())
		{
			// insert以后it这个实参会不会失效?
			 it2=v.insert(it2, 1000);

			// 建议失效后迭代器不要访问。除非赋值更新一下这个失效的迭代器
			cout << *it2 << endl;
		}
		cout << endl;

		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		cout << endl;

		//erase
		v.erase(v.begin());
		for (auto e : v)
		{
			cout << e << endl;
		}
		cout << endl;

		std::vector<int> v1;
		v1.push_back(7);
		v1.push_back(8);
		v1.push_back(9);
		v1.push_back(10);

		//std vector erase
		cout << "std下vector的erase" << endl;
		std::vector<int>::iterator it=v1.begin()+2;
		while (it != v1.end())
		{
			it=v1.erase(it);
			// v1.erase(it); 
			// 这种写法错误。会导致迭代器失效。
			// (不同的编译器可能不会导致迭代器失效,比如:linux-g++。
			// 我们遇到这种情况,统一当做迭代器失效情况解决,以免在不同的编译器环境下,就运行失败了)
			// 为什么错了呢?
			// 因为it被erase后,就被vs打上false的标签,表明it指向的空间已经不再有了,(有些编译器是会进行缩容,导致错误)
			// 所以无法继续删除
			// 所以需要更新it,即it=v1.erase(it);
		}
		for (auto e : v1)
		{
			cout << e << endl;
		}
		cout << endl;

		//拷贝构造
		cout << "v2" << endl;
		vector<int> v2(v);
		for (auto e : v2)
		{
			cout << e << endl;
		}
		cout << endl;
		v2[0] = -1;
		for (auto e : v2)
		{
			cout << e << endl;
		}
		cout << endl;

		//赋值运算符
		cout << "v3" << endl;
		vector<int> v3;
		v3 = v;
		for (auto e : v3)
		{
			cout << e << endl;
		}
		cout << endl;
		v3[0] = -2;
		for (auto e : v3)
		{
			cout << e << endl;
		}
		cout << endl;

		cout << "v" << endl;
		for (auto e : v)
		{
			cout << e << endl;
		}
		cout << endl;

		cout << "v4" << endl;
		vector<int> v4(v.begin(), v.end());
		for (auto e : v4)
		{
			cout << e << endl;
		}
		cout << endl;

		cout << "v5" << endl;
		vector<int> v5(9, 10);
		for (auto e : v5)
		{
			cout << e << endl;
		}
		cout << endl;

		cout << "v6" << endl;
		vector<int> v6(9);
		for (auto e : v6)
		{
			cout << e << endl;
		}
		cout << endl;

		cout << "s" << endl;
		vector<string> s({ "111","222","333","444","555" });//直接构造
		for (auto e : s)
		{
			cout << e << endl;
		}
		cout << endl;

		cout << "s1" << endl;
		vector<string> s1={ "777","222","333","444","555" };//隐式类型转换
		for (auto e : s1)
		{
			cout << e << endl;
		}
		cout << endl;

		vector<string> s3;
		s3.push_back("111111111111111111");
		s3.push_back("111111111111111111");
		s3.push_back("111111111111111111");
		s3.push_back("111111111111111111");
		s3.push_back("111111111111111111");
		for (auto e : s3)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

 Test.cpp

#include <iostream>
#include <assert.h>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;

#include "vector.h"

int main()
{
	wmm::Test();

	return 0;
}

~~

有问题请指正批评~~

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值