C++:vector讲解+模拟实现【深拷贝和迭代器失效问题】

一、vector简介

vector代表数组的序列容器,其大小可以改变。也就是数据结构中能动态增容的顺序表


此文章将从两个方面开始:使用和实现(注意事项)


二、vector的使用

1.构造函数

在这里插入图片描述
在这里插入图片描述
vs的编译器下无参的构造函数会将vector对象里面的size和capacity初始化为0;


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


在这里插入图片描述

拷贝构造:使用已经存在的对象构造另一个对象


在这里插入图片描述
使用迭代器区间初始化对象:将引用这段区间的值深拷贝到对象初始区间中

2.vector的遍历

#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	// 1、下表+[]
	for (size_t i = 0; i < v1.size(); i++)
	{
		cout << v1[i] << ' ';
	}
	cout << endl;

	// 2.迭代器
	vector<int>::iterator it = v1.begin();
	//
	while (it != v1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	// 3.范围for
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

在这里插入图片描述

3.vector的增容

在这里插入图片描述
增加 vector 的容量(即 vector 在不重新分配存储的情况下能最多能持有的元素的数量)到大于或等于 n 的值。如果n 大于当前的 capacity(),那么就会分配新存储,否则该方法不做任何事。
reserve() 不会更改 vector 的大小。
如果 n 大于 capacity(),那么所有迭代器,包含 end() 迭代器和所有到元素的引用都会失效。否则,没有迭代器或引用会失效。
在调用 reserve() 后,插入只会在它将导致 vector 的大小大于 capacity() 的值时触发重新分配。

我们看一下vs下的扩容机制:

#include <iostream>
#include <vector>
using namespace std;

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

在这里插入图片描述


3.2 如果vector想尾插数据,开空间用reserve还是resize?——用reserve

我们先了解一下resize函数
在这里插入图片描述

重设vector容器大小以容纳 count 个元素,在 count == size() 时不做任何事。
如果当前大小大于count,那么减小容器到它的开头 count 个元素。
如果当前大小小于 count :

  1. 那么后附额外的默认插入的元素
  2. 那么后附额外的 value 的副本

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

如果知道要开多大空间且后续要进行尾插,就用reserve提前开好。

如果用resize就是开空间并初始化,比如v1.resize(100); 开100个int空间,全部初始化成0,如果再v1.push_back(i); 就是在这100个空间以后再加数据,因为前100个已经有数据了
在这里插入图片描述

4.vector的几个常用函数

1.容器中的元素数量

在这里插入图片描述
在这里插入图片描述

2.返回容器当前已为之分配空间的元素数

在这里插入图片描述

在这里插入图片描述

3.后附给定元素 value 到容器尾

如果新的 size() 大于 capacity(),那么所有迭代器和引用(包含 end() 迭代器)都会失效。否则只有 end() 迭代器会失效。

在这里插入图片描述

在这里插入图片描述

4.插入元素到容器中的指定位置
在这里插入图片描述
在这里插入图片描述
如果新 size() 大于旧 capacity() 就会导致重分配。 如果新的 size() 大于 capacity(),那么所有迭代器和引用都会失效。因为需要新开辟一段连续并且更大的capacity空间,然后将原来顺序表中的数据深拷贝到新的表中。否则,只有在插入点前的迭代器和引用会保持有效。end() 迭代器也会失效。(因为插入数据之后之前的end()位置是新插入元素的位置,所以vector内部实现的时候end()会后移一位)

5、从容器擦除指定的元素

在这里插入图片描述
在这里插入图片描述
返回值
最后移除元素之后的迭代器

  1. 如果 pos 指代末元素,那么返回 end() 迭代器。
  2. 如果在移除前 last == end(),那么返回更新的 end() 迭代器。
    如果范围 [first, last) 为空,那么返回 last。
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	vector<int>::iterator pos = v1.erase(v1.begin() + 2);
	cout << *pos << endl;
}

这里删除的是v1[2],后面的元素往前移动,返回的是删除位置的迭代器,所以删除位置之后的迭代器都失效了,以为元素往前移动了一位
在这里插入图片描述

使位于擦除点或之后的迭代器失效,包含 end() 迭代器。
迭代器 pos 必须合法且可解引用,因此不能以 end() 迭代器(合法,但不可解引用)作为 pos 的值。
如果 first == last,那么迭代器 first 不需要可解引用:擦除空范围是无操作。


5.vector的几个迭代器

1.begin()
在这里插入图片描述
在这里插入图片描述

2.end()
在这里插入图片描述


三、模拟实现vector

还是老样子,先搭建整体框架


#include <iostream>
#include <assert.h>


namespace zxy_vector
{
	template <class T>
	class vector
	{
	public:
		typedef T* iterator; // Vector的迭代器是一个原生指针
	
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

先编写构造函数,这样就可以初始化对象了

		vector()
			:_start(nullptr)  //指向第一个数据的起始位置
			,_finish(nullptr)  //指向最后一个数据的位置
			,_endofstorage(nullptr)  //存储容量的最后一个位置
		{
		}

		//v2(v1)
		vector(const vector<T>& v)
			:_start(nullptr) 
			, _finish(nullptr) 
			, _endofstorage(nullptr) 
		{
			reserve(v.capacity());

			//for (auto& ch : v)
			//{
			//	push_back(ch);
			//}

			auto it_begin = v.begin();
			int i = 0;
			while (it_begin != v.begin() + v.size())
			{
				_start[i++] = *it_begin;
				it_begin++;
			}
			_finish = _start + i;
		}

注意:写拷贝构造的时候一定要是深拷贝,因为模板参数不一定总是内置类型,还有可能是自定义类型比如说:string,所以我们一定要注意深拷贝的问题,不然调用析构函数的时候,同一个空间会释放两次,导致程序崩溃

接下来就可以写插入数据的接口了

		void push_back(const T& val)
		{
			if (_finish == _endofstorage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = val;
			_finish++;
		}

完成reserve接口

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* temp = new T[n];
				if (_start)
				{
					//将数据拷贝到新空间  深拷贝
					auto it_begin = begin();
					int i = 0;
					while (it_begin != begin() + size())
					{
						temp[i++] = *it_begin;
						it_begin++;
					}
					//释放旧空间
					delete[] _start;
				}

				_finish = temp + size();
				_start = temp;
				_endofstorage = _start + n;
			}
		}

注意:这里的三个私有数据成员会有是失效的问题,因为reserve扩充之后的新空间是重新开辟的,三个指针还是指向旧空间的位置,所以我们要保存好各自之间的差值,以便更新指针

插入数据之后,就可以访问数据了

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

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

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

		iterator begin() const
		{
			return _start;
		}

		iterator end() const
		{
			return _finish;
		}

这里添加const修饰this指针是因为:const修饰的对象不能调用非const成员函数,不能进行权限的放大;还有就是成员函数返回值返回的对象是临时对象,临时对象具有常属性,也就是const修饰的对象,所以我们通常使用返回值对象调用成员函数的时候,就只可以调用const修饰的成员函数

接下来就是编写可能导致迭代器失效的接口:

		void insert(iterator pos, const T& val)
		{
			int len = pos - _start;
			assert(pos <= _finish && pos >= _start);
			//检查扩容
			if (size() == capacity())
			{
				reserve(capacity() + 1);
			}
			pos = _start + len;
			iterator it_end = _finish - 1;
			while (it_end >= pos)
			{
				*(it_end + 1) = *it_end;
				it_end--;
			}
			*pos = val;
			// reserve之后容量指针已经发生改变,但是再插入数据之前_finish还是指向原来数据的下一个位置,等新添加数据之后,_finish指针需要后移一位
			_finish++;
		}

		iterator erase(iterator pos)
		{
			int len = pos - _start;
			assert(pos < _finish && pos >= _start);
			iterator it_begin = pos;
			while (it_begin < _finish - 1)
			{
				*it_begin = *(it_begin + 1);
				it_begin++;
			}
			_finish--;
			return _start + len;
		}

vector.h


#include <iostream>
#include <assert.h>


namespace zxy_vector
{
	template <class T>
	class vector
	{
	public:
		typedef T* iterator;

		vector()
			:_start(nullptr)  //指向第一个数据的起始位置
			,_finish(nullptr)  //指向最后一个数据的位置
			,_endofstorage(nullptr)  //存储容量的最后一个位置
		{
		}

		//v2(v1)
		vector(const vector<T>& v)
			:_start(nullptr) 
			, _finish(nullptr) 
			, _endofstorage(nullptr) 
		{
			reserve(v.capacity());

			//for (auto& ch : v)
			//{
			//	push_back(ch);
			//}

			auto it_begin = v.begin();
			int i = 0;
			while (it_begin != v.begin() + v.size())
			{
				_start[i++] = *it_begin;
				it_begin++;
			}
			_finish = _start + i;
		}

		//v1 = v2;
		vector<T>& operator=(vector<T> temp)
		{
			std::swap(_start, temp._start);
			std::swap(_finish, temp._finish);
			std::swap(_endofstorage, temp._endofstorage);
			return *this;
		}

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

		void push_back(const T& val)
		{
			if (_finish == _endofstorage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = val;
			_finish++;
		}

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

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

		void resize(size_t n, T val = T())
		{
			if (n <= size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* temp = new T[n];
				if (_start)
				{
					//将数据拷贝到新空间
					auto it_begin = begin();
					int i = 0;
					while (it_begin != begin() + size())
					{
						temp[i++] = *it_begin;
						it_begin++;
					}
					//释放旧空间
					delete[] _start;
				}

				_finish = temp + size();
				_start = temp;
				_endofstorage = _start + n;
			}
		}

		void insert(iterator pos, const T& val)
		{
			int len = pos - _start;
			assert(pos <= _finish && pos >= _start);
			//检查扩容
			if (size() == capacity())
			{
				reserve(capacity() + 1);
			}
			pos = _start + len;
			iterator it_end = _finish - 1;
			while (it_end >= pos)
			{
				*(it_end + 1) = *it_end;
				it_end--;
			}
			*pos = val;
			// reserve之后容量指针已经发生改变,但是再插入数据之前_finish还是指向原来数据的下一个位置,等新添加数据之后,_finish指针需要后移一位
			_finish++;
		}

		iterator erase(iterator pos)
		{
			int len = pos - _start;
			assert(pos < _finish && pos >= _start);
			iterator it_begin = pos;
			while (it_begin < _finish - 1)
			{
				*it_begin = *(it_begin + 1);
				it_begin++;
			}
			_finish--;
			return _start + len;
		}
		
		size_t capacity() const
		{
			return _endofstorage - _start;
		}

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

		iterator begin() const
		{
			return _start;
		}

		iterator end() const
		{
			return _finish;
		}
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

测试代码


#include "vector.h"
using namespace std;
//#include <vector>



void test_vector1()
{
	zxy_vector::vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	for (int i = 0; i < v1.size(); i++)
	{
		cout << v1[i] << ' ';
	}
	cout << endl;
	v1.insert(v1.begin(), 10);
	v1.insert(v1.begin() + 2, 30);
	v1.insert(v1.begin() + v1.size(), 60);
	for (auto c : v1)
	{
		cout << c << ' ';
	}
	cout << endl;
	v1.erase(v1.begin());
	v1.erase(v1.begin() + 1);
	v1.erase(v1.begin() + v1.size() - 1);

	for (auto c : v1)
	{
		cout << c << ' ';
	}
	cout << endl;
}

void test_vector2()
{
	zxy_vector::vector<int> v1;
	v1.push_back(2);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);



	zxy_vector::vector<int>::iterator begin = v1.begin();
	while (begin < v1.end())
	{
		if (*begin % 2 == 0)
		{
			v1.erase(begin);
		}
		else
		{
			begin++;
		}
	}
	for (auto c : v1)
	{
		cout << c << ' ';
	}
	cout << endl;
}

void test_vector3()
{
	zxy_vector::vector<string> v1;
	v1.push_back("111111");
	v1.push_back("111111");
	v1.push_back("111111");
	v1.push_back("111111");
	v1.push_back("111111");
	v1.push_back("111111");
	v1.push_back("111111");
	v1.push_back("111111");
	v1.push_back("111111");
	v1.push_back("111111");
	v1.push_back("111111");
	v1.push_back("111111");


	for (auto c : v1)
	{
		cout << c << ' ';
	}
	cout << endl;
}

void test_vector4()
{
	zxy_vector::vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	zxy_vector::vector<int> v2(v1);


	for (auto c : v1)
	{
		cout << c << ' ';
	}
	cout << endl;

	for (int i = 0; i < v2.size(); i++)
	{
		cout << v2[i] << ' ';
	}
	cout << endl;
}


void test_vector5()
{
	zxy_vector::vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	zxy_vector::vector<int> v2;
	v2 = v1;
	for (auto c : v1)
	{
		cout << c << ' ';
	}
	cout << endl;
	for (auto c : v2)
	{
		cout << c << ' ';
	}
	cout << endl;
}

int main()
{
	test_vector5();
	return 0;
}

四、 std::vector::at 和 std::vector::operator[]

at() 函数和 [] 运算符的重载,两者都可以得到相应下标的值。

当发生越界行为时:
at 是抛异常;
operator[] 不进行边界检查,通过此运算符访问不存在的元素是未定义行为。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值