vector模拟实现下篇及迭代器失效和深浅拷贝问题详解

1:构造函数

1.1默认构造函数

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

1.2迭代器构造

		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			: _start(nullptr)
			, _end(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)
			{
				pushback(*first);
				++first;
			}
		}

在类模板中使用函数模板。

1.3用n个val构造

		vector(size_t n, const T& val = T())
			: _start(nullptr)
			, _end(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				pushback(val);
			}
		}

我们测试一下

	void test1()
	{
		vector<char> v(10, 1);

		for (size_t i = 0; i < v.size(); i++)
		{
			cout << v[i];
		}
	}

image-20221215142047255

这个报错产生在 image-20221215142105794

分析:

用10个1构造vector,1是整形,那么T就会推演为int类型,10也是整形,但是这个n个val构造的n是size_t类型,int转为size_t需要发生隐式类型转换,而1.2里面我们写的迭代器,inputiterator只是个名字,int可以被这个名字表达,所以编译器不会去找要转换的而是直接调用1.2的构造函数,但是int如果作为迭代器,*it访问的时候就会出错,也就是非法的间接寻址。因此为了解决这个问题,我们需要再重载一个构造函数

		vector(int n, const T& val = T())
			: _start(nullptr)
			, _end(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				pushback(val);
			}
		}

1.4拷贝构造

		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_end, v._end);
			std::swap(_endofstorage, v._endofstorage);
		} 		
        vector(const vector<T>& v)
			: _start(nullptr)
			, _end(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T> temp(v.begin(), v._end());
			swap(v);
		}

和string一样,用库里面的swap进行深拷贝交换。

2:operator=

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

和string一样,传值的形参不改变实参,形参出了作用域自动销毁对象。而且这里的swap传形参的引用,形参拿到原本的vector地址,当他出了函数作用域的时候顺便帮vector也析构。

3:析构函数和clear

		~vector()
		{
			delete[] _start;
			_start = _end = _endofstorage = nullptr;
		}
		void clear()
		{
			_end = _start;
		}

clear是清空数据

4:迭代器失效问题

上篇我们设计了insert和erase成员函数。

		void insert(iterator pos, const T& val)
		{
			assert(pos < _end);
			assert(pos >= _start);
			if (_end == _endofstorage)
			{
				size_t len = pos - _start;
				int newcapacity = capacity() > 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				pos = _start + len;
			}
			iterator _finish = _end - 1;
			while (_finish >= pos)
			{
				*(_finish + 1) = *_finish;
				--_finish;
			}
			*pos = val;
			++_end;
		}
		void erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);


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

			--_finish;

			return pos;
		}

使用len是解决了内部迭代失效的原因。

	void test2()
	{
		std::vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		std::vector<int>::iterator it = find(v.begin(), v.end(), 3);
		if (it != v.end())
		{
			v.erase(it);
		}
		cout << *it << endl;
		for (auto e : v)
		{
			cout << e << ' ';
		}
	}

当删除了it原本的位置后,

image-20221215145610834

直接崩溃,不打印。

而看看g++

image-20221215145640200

vs报错,g++不报错。

迭代器失效,因此在g++源码里面,是这样实现的。

将erase和insert的返回值设定为iterator。这样可以更新位置后再继续使用。

it = v.erase(it);

4.1:删除偶数

image-20221215151355939

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


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

			--_end;

			return pos;
		}

如果是1234,如果是最后一个位置是偶数要删除,那么begin就会从pos+1的位置删除也就是和end一样的位置,那么我们的erase都不会进去,直接end–了,然后此时it还要++,也就是2个迭代器错过了,这样就会一直判断下去导致不断越界。问题就是最后一个数是偶数。

如果是122345, image-20221215152045383

当it是第一个2的时候,会erase,挪动数据,将下一个2移动到前面一个2的位置,然后还要++it,这样的话就会导致第二个2没有判断到,所以结果不符合。

如果单纯的解决办法是如下

else
{
++it;
}

这样子可以解决末尾有偶数的问题(段错误)。

但是我们参考的是sgi,sgi是linux的,采用的迭代器是原生指针,而vs是windows下的。 迭代器不是原生指针。所以erase和insert等涉及到迭代器失效的需要用迭代器作为返回值。

	//void test_vector6()
	//{
	//	// 要求删除所有偶数
	//	std::vector<int> v;
	//	v.push_back(1);
	//	v.push_back(2);
	//	v.push_back(2);
	//	v.push_back(3);
	//	v.push_back(4);

	//	std::vector<int>::iterator it = v.begin();
	//	while (it != v.end())
	//	{
	//		if (*it % 2 == 0)
	//		{
	//			it = v.erase(it);
	//		}
	//		else
	//		{
	//			++it;
	//		}
	//	}

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

深浅拷贝

	void test_vector9()
	{
	/*	vector<vector<int>> vvRet = Solution().generate(5);

		for (size_t i = 0; i < vvRet.size(); ++i)
		{
			for (size_t j = 0; j < vvRet[i].size(); ++j)
			{
				cout << vvRet[i][j] << " ";
			}
			cout << endl;
		}
		cout << endl;*/

		vector<vector<int>> vv;
		vector<int> v(5, 1);
		vv.push_back(v);
		vv.push_back(v);
		vv.push_back(v);
		vv.push_back(v);
		vv.push_back(v);

		for (size_t i = 0; i < vv.size(); ++i)
		{
			for (size_t j = 0; j < vv[i].size(); ++j)
			{
				cout << vv[i][j] << " ";
			}
			cout << endl;
		}
		cout << endl;
	}
}

看下面的代码,创建包含5个1的vector,再往一个vector里面插入这种vector,然后打印这个大的vector。

image-20221215154542562

运行结果报错,还出现了随机数。这是为什么?

在前面章节我提到过,当按字节序拷贝的时候会发生浅拷贝,按字节序拷贝的函数有memcpy,而memcpy是在reserve过程的,这里面当我们在总的vector里面插入小的vector的时候一定会发生扩容,当发生扩容的时候就会reserve,将旧数据拷贝到新空间。

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t oldsize = size();
				//需要扩容
				T* temp = new T[n];
				//使用new可以调用默认构造,比malloc要舒服的多。
				//要注意如果start为空的时候,拷贝不了任何东西所以先判断start
				if (_start)
				{
					memcpy(temp, _start, sizeof(T) * oldsize);
					delete[] _start;
			    }
				_start = temp;
				_end = _start + oldsize;
				_endofstorage = _start + n;
			}
		}

image-20221215154852024

image-20221215154914444

当开辟好新空间,调用memcpy的时候,是发生的字节序拷贝,将上面4个单元格内容拷贝到下面的几个单元格里面,并且用的是字节序拷贝,所以下面的单元格里面的start都和上面的4个小单元格里面的start指向内容一样,是同一块空间,当调用delete的时候,就会析构掉上面的整体,会调用整体(自定义类型vector int)的析构函数,析构掉每个单元,这时每个小单元格指向的内容都变成了随机的,temp里的单元格和上面的单元格指向内容是一样的,所以temp赋值给下面的总体的start迭代器的时候,相当于把随机值给他,所以导致了这个结果。

本质上就是因为reserve的memcpy导致了浅拷贝,内容先被释放,成为随机空间,又把随机空间赋值给新空间的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)*oldSize);
					for (size_t i = 0; i < oldSize; ++i)
					{
						tmp[i] = _start[i];
					}

					delete[] _start;
				}

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

使用赋值,赋值的话是深拷贝。不会引起上面问题。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不熬夜不抽烟不喝酒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值