STL----vector的模拟实现

目录

1. vector的介绍与使用

1.1 vector的介绍

1.2 vector的使用

1.2.1 vector的定义与使用 

1.2.2 iterator的定义与使用

1.2.3 vector的空间增长问题 

 1.2.4 vector的增删查改

1.2.5 迭代器失效问题

 2. vector的深度剖析及模拟实现

2.1 深度剖析

 2.2 模拟实现

2.1 成员变量的构建

2.2迭代器的实现 

2.3 容量与有效字符问题

2.4 插入与删除

2.5 构造,赋值重载与析构

 3. 浅拷贝与深拷贝的问题

4. 完整代码 


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在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。

1.2 vector的使用

我们在学习中,一定要学会查看文档

vector - C++ Reference

这里就是vector的文档,大家可以看一看 

1.2.1 vector的定义与使用 

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

vector的类模板可以实例化各种类型,无论是内置类型int,char或是自定义类型string,vector都可以。 

void vector_test1()
{
	vector<int> v();//无参
	vector<char> vv(10, 'x');//构造并初始化
	vector<string> vvv(5, "haha");

	vector<char> vs(vv);//拷贝构造
	vector<string> vvs(vvv.begin()+2, vvv.end());//使用迭代器区间进行初始化定义

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

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

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

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

}

 

1.2.2 iterator的定义与使用

iterator的使用接口说明
begin +
end(重点)
获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置
的iterator/const_iterator
rbegin + rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的
reverse_iterator


 

void vector_test2()
{
	vector<char> str;
	str.push_back('a');
	str.push_back('b');
	str.push_back('c');
	str.push_back('d');
	str.push_back('r');
	str.push_back('g');
	str.push_back('y');

	vector<char>::iterator it = str.begin();
	while (it != str.end())
	{
		cout << *it << "  ";
		it++;
	}
	//while (it != str.end())//第二次打印什么都没有,因为迭代器已然失效
	//	                   //it已经指在了str.end()的位置
	//{
	//	cout << *it << "  ";
	//	it++;
	//}
	cout << endl;
	vector<char>::const_reverse_iterator rit = str.rbegin();
	while (rit != str.rend())//逆反打印
	{
		cout << *rit << "  ";
		rit++;
	}
	cout << endl;


	vector<int> arr{ 1,2,3,4,5,6,7, };
	vector<int>::iterator iit = arr.begin();
	while (iit != arr.end())//打印的同时做修改
	{
		(*iit)++;
		cout << *iit << " ";
		iit++;
	}
}

 

1.2.3 vector的空间增长问题 

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

capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
resize在开空间的同时还会进行初始化,影响size

void vector_test3()
{
	vector<int> v;//创建一个空的vector数组
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	if (v.empty())
		cout << "该容器为空!" << endl;
	else
		cout<< "该容器bu为空!" << endl;
	cout << endl;

	v.reserve(10);//扩容至10
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	if (v.empty())
		cout << "该容器为空!" << endl;
	else
		cout << "该容器bu为空!" << endl;
	cout << endl;

	v.resize(15,1);//有效字符扩充为15,并填充为1
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	if (v.empty())
		cout << "该容器为空!" << endl;
	else
		cout << "该容器bu为空!" << endl;
	cout << endl;

	v.reserve(5);//容量小于原容量,不做更改
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	if (v.empty())
		cout << "该容器为空!" << endl;
	else
		cout << "该容器bu为空!" << endl;
	cout << endl;

	v.resize(7, 3);//缩小有效字符为7,未被填充的填充为3
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << v.size() << endl;
	cout << v.capacity() << endl;
	if (v.empty())
		cout << "该容器为空!" << endl;
	else
		cout << "该容器bu为空!" << endl;
	cout << endl;
}

关于空间增长问题,我们可以看到,当size==capacity时,即vector的_finish==_endOfStorage时,会触发自动扩容机制 。扩容是比较耗费资源的,因此倘若我们直到所需的空间大小,可以提前reserve足够的空间,可以节省扩容消耗。

void vector_test4()
{
	vector<int> v;
	size_t sz = v.capacity();
	for (int i = 0; i < 100; i++)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			cout << "capacity: " << v.capacity() << endl;
			sz = v.capacity();
		}
	}
}

 1.2.4 vector的增删查改

我们发现,vector似乎并不是很支持在其他位置的增删,数组的增删是得不偿失的,往往对一个元素的操作,要挪动一堆元素。

void vector_test5()
{
	vector<int> 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;

	v.pop_back();//尾删
	v.pop_back();
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	v.push_back(4);
	v.push_back(5);
	vector<int>::iterator pos=find(v.begin(), v.end(), 3);//查找,返回迭代器,需要包含算法头文件,<algorithm>
	cout << *pos << endl;


   
	v.insert(pos, 5);//在pos前插入5
	
	//当插入5时,vector可能会触发扩容机制,扩容后会产生迭代器失效问题,这里的指:
	// 扩容后产生新的空间,原空间释放,而pos指向之前的空间,变成野指针
	vector<int>::iterator poss = find(v.begin(), v.end(), 3);
	poss++;//由于数组的物理地址是连续的,因此可以++,但如list等不连续的空间就不可以
	v.insert(poss, 5, 99);//在poss前插入5个99
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	for (size_t i = 0; i < v.size(); i++)//像数组一样,下标访问
	{
		cout << v[i] << " ";//等同于v.operator[i]
	}
	cout << endl;

	vector<int>::iterator it = v.begin()+3;//it指向第4个位置
	cout << *it << endl;
	it=v.erase(it, it + 3);//从第四个位置开始,删除三个元素,更新it的值
	cout << *it << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	vector<int> vv{ 3,3,3,3,3 };
	cout << "交换前:" << endl;
	cout << "v: " << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "vv: " << endl;
	for (auto e : vv)
	{
		cout << e << " ";
	}
	cout << endl;

	v.swap(vv);//交换vector空间
	cout << "交换后:" << endl;
	cout << "v: " << endl;
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "vv: " << endl;
	for (auto e : vv)
	{
		cout << e << " ";
	}
	cout << endl;
}

1.2.5 迭代器失效问题

在前面的代码注释中,我们提到了迭代器失效的问题,那么到底什么是迭代器失效呢? 

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

迭代器失效分为两种情况:

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

以下面的代码为例,倘若插入会触发扩容,那么扩容之后会产生新的vector空间,释放原先的空间,而此时pos仍然指向原来的空间,访问一块已经被释放的空间,会导致崩溃。

那么有人说,如果没有触发扩容,是不是就不会崩溃了?

这个要看编译器,vs下比较严格,即便没有触发扩容,也会崩溃;Linux的g++下可能不会崩溃。 

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

下面这段代码,我们删除之后更新了it,如果不更新,就会崩溃,这又是为什么呢?

按理说,我们删除当前位置的元素,也就是后面的元素逐个向前挪动覆盖,我的迭代器it不应该有变化才是。但如果我们删除的是最后一个元素呢?it指向v.end(),还能对他解引用吗?

同样这种代码在不同编译器结果不同,如果删除的是其他位置并且没有更新迭代器位置的话,在vs下会崩溃 ,g++下就不会崩溃。

那么如何解决这个问题呢?

其实上面的代码已经告诉我们了,只需要更新迭代器的位置就ok了。 

 2. vector的深度剖析及模拟实现

2.1 深度剖析

下图是根据vector底层源码逻辑构画出的图,vector的三个成员变量都是由迭代器构建的。

迭代器的底层是T*

 begin()指向vector第一个元素的位置,end()指向最后一个元素的下一个位置。

 2.2 模拟实现

2.1 成员变量的构建

这里注意,我们的变量后都有缺省参数,后面实现构造函数会方便很多。


	private:

		iterator _start = nullptr; // 指向数据块的开始

		iterator _finish = nullptr; // 指向有效数据的尾

		iterator _endOfStorage = nullptr; // 指向存储容量的尾

2.2迭代器的实现 

这里我们只实现了正向迭代器的普通与静态对象版本。这个比较简单就不做过多赘述了。

		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;
		}

 在源码处我们可以看到反向迭代器的实现

2.3 容量与有效字符问题

 关于reserve这里我们需要注意迭代器失效的问题,resize由于我们对reserve进行了复用,因此规避了迭代器失效。

		size_t size() const//size()外界不能做修改
		{
			return _finish - _start;
		}

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

		void reserve(size_t n)//扩容函数
		{
			if (n > capacity())//如果需要的空间大于原空间容量
			{
				size_t old = size();//这里为什么要记录size呢?
				T* tmp = new T[n];//new一个新的n大小的空间tmp
				if (_start)//如果对象原本就存在
				{
					for (size_t i = 0; i < old; i++)//将空间内的数据转移到tmp
					{
						tmp[i] = _start[i];
					}
					delete[] _start;//释放原空间
				}
				_start = tmp;//更新对象的成员变量
				_finish = _start + old;
				_endOfStorage = _start + n;
			}
			//需要的空间小于原空间容量,不做修改
		}

		void resize(size_t n, const T& value = T())
		{
			if (n > size())
			{
				reserve(n);//复用reserve
				for (size_t i = size(); i < n; i++)//从size()处开始填充
				{
					_start[i] = value;
				}
			}
			else//n<size()
			{
				_finish = _start + n;//缩小空间的有效字符
			}
		}



		///access///

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

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

2.4 插入与删除

这里的insert与erase是由迭代器失效的隐患的,因此需要多加注意。

		void push_back(const T& x)//尾插
		{
			if (_finish == _endOfStorage)//如果空间已满,则需要扩容
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}
			*_finish = x;
			_finish++;
		}

		void pop_back()//尾删
		{
			assert(size() > 0);
			_finish--;
		}

		void swap(vector<T>& v)//交换,复用std下的swap,实现深拷贝
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endOfStorage, v._endOfStorage);
		}

		iterator insert(iterator pos, const T& x)//插入
		{
			assert(pos >= _start && pos <= _finish);//pos需要在vector对象的迭代器区间内

			if (_finish == _endOfStorage)//容量已满
			{
				size_t len = pos - _start;//这里同样需要记录,因为pos指向旧空间,一旦扩容就会产生新空间
				                         //此时pos失效,
				reserve(capacity() == 0 ? 4 : 2 * capacity());
				pos = _start + len;//在这里更新pos
			}
			iterator end = _finish - 1;
			while (end >= pos)//不能使用memcpy,memcpy本质是浅拷贝
			{
				*(end + 1) = *(end);
				end--;
			}
			*pos = x;
			_finish++;
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			iterator tmp = pos + 1;
			while (tmp < _finish)
			{
				*(tmp - 1) = *tmp;
				tmp++;
			}
			_finish--;
			return pos;//需要返回pos值,更新外界的pos
		}

2.5 构造,赋值重载与析构

这里复用其他函数会很方便。 

		vector()//由于成员变量的声明处给了缺省值,这里可以不做处理
		{}

		vector(int n, const T& value = T())//构建以t填充的n个有效字符的对象
		{
			resize(n, value);//复用resize
		}

		template<class InputIterator>
		vector(InputIterator first, InputIterator last)//构建以迭代器区间为基础的对象
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

		vector(const vector<T>& v)//拷贝构造
		{
			reserve(v.capacity());//提前reserve,减少开辟空间的损耗
			for (const auto& e : v)
			{
				push_back(e);
			}
		}

		vector<T>& operator= (vector<T> v)//赋值重载,传入赋值对象的拷贝v
		{
			swap(v);//交换v与this,此时this指向赋值对象的拷贝,v指向原this空间
			return *this;//函数结束后,原this空间被释放
		}

		~vector()//析构
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _endOfStorage = nullptr;
			}
		}

 3. 浅拷贝与深拷贝的问题

有人会说,在insert与erase中都涉及到了数据的移动覆盖,那我们使用memcpy不就好了吗?
事实证明,这样是不行的。

memcpy的拷贝是浅拷贝,如果vector内嵌的是自定义类型,此时tmp内的string与vstr中的string都指向同一空间。那么后面释放原空间,会调用原空间内string的析构函数,此时tmp对象内的string就会指向已经被释放的空间。因此这里我们使用了最原始的方法。

4. 完整代码 

class vector

{

public:

	// Vector的迭代器是一个原生指针

	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;
	}



	// construct and destroy

	vector()//由于成员变量的声明处给了缺省值,这里可以不做处理
	{}

	vector(int n, const T& value = T())//构建以t填充的n个有效字符的对象
	{
		resize(n, value);//复用resize
	}

	template<class InputIterator>
	vector(InputIterator first, InputIterator last)//构建以迭代器区间为基础的对象
	{
		while (first != last)
		{
			push_back(*first);
			first++;
		}
	}

	vector(const vector<T>& v)//拷贝构造
	{
		reserve(v.capacity());//提前reserve,减少开辟空间的损耗
		for (const auto& e : v)
		{
			push_back(e);
		}
	}

	vector<T>& operator= (vector<T> v)//赋值重载,传入赋值对象的拷贝v
	{
		swap(v);//交换v与this,此时this指向赋值对象的拷贝,v指向原this空间
		return *this;//函数结束后,原this空间被释放
	}

	~vector()//析构
	{
		if (_start)
		{
			delete[] _start;
			_start = _finish = _endOfStorage = nullptr;
		}
	}

	// capacity

	size_t size() const//size()外界不能做修改
	{
		return _finish - _start;
	}

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

	void reserve(size_t n)//扩容函数
	{
		if (n > capacity())//如果需要的空间大于原空间容量
		{
			size_t old = size();//这里为什么要记录size呢?
			T* tmp = new T[n];//new一个新的n大小的空间tmp
			if (_start)//如果对象原本就存在
			{
				for (size_t i = 0; i < old; i++)//将空间内的数据转移到tmp
				{
					tmp[i] = _start[i];
				}
				delete[] _start;//释放原空间
			}
			_start = tmp;//更新对象的成员变量
			_finish = _start + old;
			_endOfStorage = _start + n;
		}
		//需要的空间小于原空间容量,不做修改
	}

	void resize(size_t n, const T& value = T())
	{
		if (n > size())
		{
			reserve(n);//复用reserve
			for (size_t i = size(); i < n; i++)//从size()处开始填充
			{
				_start[i] = value;
			}
		}
		else//n<size()
		{
			_finish = _start + n;//缩小空间的有效字符
		}
	}



	///access///

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

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



	///modify/

	void push_back(const T& x)//尾插
	{
		if (_finish == _endOfStorage)//如果空间已满,则需要扩容
		{
			reserve(capacity() == 0 ? 4 : 2 * capacity());
		}
		*_finish = x;
		_finish++;
	}

	void pop_back()//尾删
	{
		assert(size() > 0);
		_finish--;
	}

	void swap(vector<T>& v)//交换,复用std下的swap,实现深拷贝
	{
		std::swap(_start, v._start);
		std::swap(_finish, v._finish);
		std::swap(_endOfStorage, v._endOfStorage);
	}

	iterator insert(iterator pos, const T& x)//插入
	{
		assert(pos >= _start && pos <= _finish);//pos需要在vector对象的迭代器区间内

		if (_finish == _endOfStorage)//容量已满
		{
			size_t len = pos - _start;//这里同样需要记录,因为pos指向旧空间,一旦扩容就会产生新空间
			                         //此时pos失效,
			reserve(capacity() == 0 ? 4 : 2 * capacity());
			pos = _start + len;//在这里更新pos
		}
		iterator end = _finish - 1;
		while (end >= pos)//不能使用memcpy,memcpy本质是浅拷贝
		{
			*(end + 1) = *(end);
			end--;
		}
		*pos = x;
		_finish++;
		return pos;
	}

	iterator erase(iterator pos)
	{
		assert(pos >= _start && pos < _finish);
		iterator tmp = pos + 1;
		while (tmp < _finish)
		{
			*(tmp - 1) = *tmp;
			tmp++;
		}
		_finish--;
		return pos;//需要返回pos值,更新外界的pos
	}


private:

	iterator _start = nullptr; // 指向数据块的开始

	iterator _finish = nullptr; // 指向有效数据的尾

	iterator _endOfStorage = nullptr; // 指向存储容量的尾

};

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值