vector的简单模拟实现和实现中遇到的一些问题C++

注意:
最终代码和我们遇见的问题会有出入,哪怕是修改后的代码都是有出入的
问题原因:复用,简化。

1.push_back中的位置确定顺序

问题代码:

		void push_back(const T& x)
		{
			if (_finish == _endofstorage)
			{
				//扩容
				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				T* tmp = new T[newCapacity];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;
				}
				_start = tmp;//易错点
				_finish = _start + size();//易错点
				_endofstorage = _start + newCapacity;
			}
			*_finish = x;
			++_finish;
		}

问题分析:
在倒数第78行中
起始位置等于了新数组的开始位置
finish 等于了开始位置加size()

出现的问题:
start指向了新空间
size代码:

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

finish和start指向的不是同一块空间
所以出现了问题

修改后的代码:

		void push_back(const T& x)
		{
			if (_finish == _endofstorage)
			{
				//扩容
				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				T* tmp = new T[newCapacity];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;
				}
				_finish = tmp + size();
				_start = tmp;
				_endofstorage = _start + newCapacity;
			}
			*_finish = x;
			++_finish;
		}

也可以把size提前算好,就不用担心这里的顺序问题

2.引用对象延长声明周期

我们常常会使用下面变量类型:

    vector<string>
    vector<vector<int>>

等等类型
vector里面不一定是int 所以需要使用匿名对象
这样我们就可以实现一份代码,来满足所有要求
问题:
匿名对象的生命周期只有一行,我们在传参中只写一下,显然不成立
所以需要加:const
即:

		void resize(size_t n, const T& val = T())

3.拷贝构造的传统写法和现代写法

传统写法:

		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();

			memcpy(_start, v._start, v.size() * sizeof(T));
		}

现代写法:
需要用到:InputIterator
他是一个类模板的成员函数,又可以是一个函数模板

		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}

		// v2(v1)
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}

4.为什么vector迭代器区间要用InputIterator来命名?

答:函数模板的模板参数要传迭代器区间时,命名规范:

迭代器有很多分类:
input_iterator只写迭代器
output_itearator只读迭代器
(上面两个没有实际对应的类型)
forward_iterator 单向迭代器: ++
forward_list,unordered_map,unordered_set
bidirectional_iterator双向迭代器: ++ –
list,map,set
randomaccess_iterator随机迭代器:++ – + -
deque,vector

5.迭代器失效问题之insert

一般情况下我们最开始实现的insert是这样的:

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

			// 满了就扩容
			if (_finish == _endofstorage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;

			return pos;
		}

问题:
当我们给vector中插入数据的时候,插入三个没事,但是插入第四个的时候就会出现问题

解决之后的代码:

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

			// 满了就扩容
			if (_finish == _endofstorage)
			{
				// 扩容会导致pos失效,扩容需要更新一下pos
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;

			return pos;
		}

测试代码:

	void test()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		vector<int>::iterator it = find(v1.begin(), v1.end(), 2);
		if (it != v1.end())
		{
			// 如果insert中发生了扩容,那么会导致it指向空间被释放
			// it本质就是一个野指针,这种问题,我们就叫迭代器失效
			v1.insert(it, 20);
		}
		// v1.insert(v1.begin(), -1);

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

延伸:
是否可以传引用解决?不可以,有些地方用的不恰当就会出现问题
如果加了引用,如果传递的时候是临时变量,那么就会出现错误
那如果加const呢?因为下面的迭代器要进行改变,所以不能加const

6.erase面临的迭代器失效问题

测试代码:

void test3()
	{
		// 三种场景:
		// 1 2 3 4 5 -> 正常
		// 1 2 3 4   -> 崩溃
		// 1 2 4 5   -> 没删除完
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(4);
		v1.push_back(5);
		//v1.push_back(5);

		// 要求删除v1所有的偶数
		vector<int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			if (*it % 2 == 0)
			{
				v1.erase(it);
			}

			++it;
		}

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

即:连续的偶数时,就会出现问题,
当检测到一个偶数的时候,我们删除它,此时他后面的偶数会进入他的位置,
指针却直接指向下一个位置

延伸:
有些版本erase的实现,可能会缩容,如果这样,erase(it)会造成野指针的问题

修改:

void test3()
	{
		// 三种场景:
		// 1 2 3 4 5 -> 正常
		// 1 2 3 4   -> 崩溃
		// 1 2 4 5   -> 没删除完
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(4);
		v1.push_back(5);
		//v1.push_back(5);
		
		// 要求删除v1所有的偶数
		vector<int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			if (*it % 2 == 0) it = v1.erase(it);
			else ++it;
		}

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

延伸:
对于我们的测试代码,
我们模拟实现的vector和linux里的vector都是可以输出的,但是结果不对
在vs下,运行代码会直接报错,会被断言检查出来

stl中的vector没有规定是否要检查这个,所以我们实现的和vs里的都是没有问题的

7.string的insert和erase迭代器是否会失效?

结论: 只要使用迭代器访问的容器,都可能会涉及迭代器失效

string迭代器什么时候会失效?
答:和vector完全类似
string迭代器失效的场景很少,并且不明显,使用时很少遇见
string常用[],插入操作用的也是很少,所以,,,

8.memcpy深浅拷贝问题

在这里插入图片描述
不仅出现乱码,程序也是崩溃的,崩溃点在delete中

这是我们写的运用memcpy的reserve

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;
				}

				_start = tmp;
				// _finish = _start + size(); size
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}

我们在扩容空间时,我们会运用memcpy一个一个的将_str中的内容一一拷贝至我们新的tmp中,这种拷贝属于浅拷贝,相当于tmp中的_str也指向了_start中的_str
我们又执行了:_start = tmp;
此时的_start变成了野指针

修改:

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T)*size());
					for (size_t i = 0; i < sz; ++i)
					{
						// T 是int,一个一个拷贝没问题
						// T 是string, 一个一个拷贝调用是T的深拷贝赋值,也没问题
						tmp[i] = _start[i];
					}

					delete[] _start;
				}

				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}

9.vector代码

namespace sakeww
{
	template<class T>
	class vector 
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{}

		//v2(v1)传统写法:
		//vector(const vector<T>& v)
		//{
		//	_start = new T[v.capacity()];
		//	_finish = _start + v.size();
		//	_endofstorage = _start + v.capacity();

		//	memcpy(_start, v._start, v.size() * sizeof(T));
		//}
		//现代写法:
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}
		// v2(v1)
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());

			swap(tmp);
		}

		vector<T>& operator=(vector<T> v) // 推荐
		{
			swap(v);
			return *this;
		}

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

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

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

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

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

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T)*size());
					for (size_t i = 0; i < sz; ++i)
					{
						// T 是int,一个一个拷贝没问题
						// T 是string, 一个一个拷贝调用是T的深拷贝赋值,也没问题
						tmp[i] = _start[i];
					}

					delete[] _start;
				}

				_start = tmp;
				_finish = _start + sz;
				_endofstorage = _start + n;
			}
		}

		void resize(size_t n, const T& val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}

				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}


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

			// 满了就扩容
			if (_finish == _endofstorage)
			{
				// 扩容会导致pos失效,扩容需要更新一下pos
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;

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

			iterator begin = pos + 1;
			while (begin < _finish)
			{
				*(begin - 1) = *begin;
				++begin;
			}

			--_finish;

			return pos;
		}

		//void push_back(const T& x)
		//{
		//	if (_finish == _endofstorage)
		//	{
		//		//扩容
		//		size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
		//		T* tmp = new T[newCapacity];
		//		if (_start)
		//		{
		//			memcpy(tmp, _start, sizeof(T) * size());
		//			delete[] _start;
		//		}
		//		_finish = tmp + size();
		//		_start = tmp; 
		//		_endofstorage = _start + newCapacity;
		//	}
		//	*_finish = x;
		//	++_finish;
		//}
		//复用reserve
		void push_back(const T& x)
		{
			if (_finish == _endofstorage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = x;
			++_finish;
		}

		void pop_back()
		{
			//因为原vector中 pop_back比较暴力,所以,,,
			assert(_finish > _start);

			--_finish;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}





  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值