【ONE·C++ || vector (二)】

news2025/1/16 0:51:58

总言

  主要讲述vector的模拟实现。

文章目录

  • 总言
  • 1、基本框架搭建:成员变量
  • 2、对构造函数、析构函数
  • 3、增删查改、空间扩容
    • 3.1、vector::push_back、vector::pop_back
    • 3.2、vector::reserve、vector::capacity、vector::size
    • 3.3、operator[ ]
    • 3.4、遍历:迭代器(begin、end)、范围for
    • 3.5、vector::insert、vector::erase 迭代器失效
      • 3.5.1、vector::insert
      • 3.5.2、迭代器失效
      • 3.5.3、vector::erase
      • 3.5.5、细节问题:不同编译器下的迭代器失效
    • 3.6、vector::resize
    • 3.7、vector::front、vector::back
  • 4、拷贝构造与赋值:vector的深拷贝问题
    • 4.1、深浅拷贝说明
    • 4.2、vector拷贝构造演示
      • 4.2.1、写法一:自己开辟空间,自己拷贝数据
      • 4.2.2、写法二:复用
      • 4.2.3、使用迭代器区间构造构造函数
      • 4.2.4、写法三:复用4.2.3、vector:: swap
      • 4.2.5、构造函数:n个val值
    • 4.3、vector赋值:operator=
    • 4.4、二维数组下深浅拷贝问题

  

1、基本框架搭建:成员变量

  同string类模拟实现一致,此处为了解决命名冲突问题,我们使用添加命名空间myvector的方式来处理。

#pragma once
namespace myvector
{
	template<class T>
	class vector
	{
		typedef T* iterator;//将模板T*命名为迭代器iterator
	public:

	private:
		iterator _start;//起始
		iterator _finish;//结束
		iterator _end_of_storage;//容量空间
	};
}

  由于后续涉及到迭代器问题,若将typedef T* iterator;定义成私有,则无法在类外很好的使用。此处修改如下:

#pragma once
namespace myvector
{
	template<class T>
	class vector
	{
		
	public:
		typedef T* iterator;//将模板T*命名为迭代器iterator
		typedef const T* const_iterator;
	private:
		iterator _start;//起始
		iterator _finish;//结束
		iterator _end_of_storage;//容量空间
	};
}

  
  

2、对构造函数、析构函数

  1)、构造函数
  对构造函数,我们之前学习时看到其中有内存池的相关内容,此处由于我们暂时没学习它,故对vector的模拟实现中我们不使用它。

		vector() //构造函数
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

  
  2)、析构函数

		~vector()//析构函数
		{
			delete[] _start;//null出来的空间是连续一块的,以_start为起始点。注意delete的使用方式
			_start = _finish = _end_of_storage = nullptr;
		}

  
  

3、增删查改、空间扩容

3.1、vector::push_back、vector::pop_back

  1)、库函数中声明回顾

  2)、push_back模拟实现

		void push_back(const T& x)
		{
			//涉及扩容检查
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0?4:capacity() * 2);
			}
			//尾插数据
			*_finish = x;
			_finish++;

		}

  为什么需要使用引用和const修饰?
  因为这里使用的是T模板参数,我们传入的值可能是内置类型,也可能是自定义类型,如果是后者,则传值传参代价很大。
  
  3)、pop_back模拟实现

		void pop_back()
		{
			assert(_finish > _start);
			_finish--;
		}

  
  
  
  

3.2、vector::reserve、vector::capacity、vector::size

  1)、库函数中声明回顾

在这里插入图片描述

  2)、模拟实现

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


		size_t capacity()const
		{
			return _end_of_storage - _start;
		}
		void reserve(size_t n)
		{
			if (n > capacity())//满足该条件才进行扩容
			{
				size_t sz = size();//因为后续重新确定指向关系时需要知道size值
				T* tmp = new T[n];
				if (_start)//说明原先空间有数据,
				{
					memcpy(tmp, _start, sizeof(T) * sz);//需要挪动
					delete[] _start;//释放旧空间
				}
				//重新确定指向关系
				_start = tmp;
				_finish = _start + sz;
				//_finish = _start + size();//如果是在这里获取size值,则在原先空间有数据的情况下,_start已经被delete

				_end_of_storage = _start + n;
			}
		}

  
  
  

3.3、operator[ ]

  1)、库函数中声明回顾

在这里插入图片描述

  2)、模拟实现

		T& operator[](size_t pos)//加&是为了支持可读可写
		{
			assert(pos < size());//检查下标是否非法
			return _start[pos];
		}


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

  
  

3.4、遍历:迭代器(begin、end)、范围for

在这里插入图片描述

  1)、普通迭代器

		//vector的迭代器就是原生指针
		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

  
  
  2)、const修饰的迭代器
  为什么需要?
  存在如下的情况:const vector<int>& v,所创建的vector对象被const修饰,如果直接使用vector<int>::iterator it = v.begin();,则属于权限放大。

		typedef const T* const_iterator;

		const const_iterator begin()const
		{
			return _start;
		}

		const const_iterator end()const
		{
			return _finish;
		}
	void Func(const vector<int>& v)
	{
		vector<int>::const_iterator it = v.begin();
		while (it != v.end())
		{
			//*it = 10;//error
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : v)//此处若使用范围for,const对象会调用对应的const迭代器
		{
			cout << e << " ";
		}
		cout << endl;
	}

  
  3)、为什么说范围for是傻瓜式替换?
  只要我们仿照库中使用对应字符begin、end,则访问for就能起效。相应的,如果我们使用了Begin、End等其它字母,则在我们模拟的vector中范围for失效。
  
  

3.5、vector::insert、vector::erase 迭代器失效

3.5.1、vector::insert

  1)、库函数中声明回顾
在这里插入图片描述

  2)、insert模拟实现1.0

		void insert(iterator pos, const T& val)
		{
			assert(pos <= _finish && pos >= _start);
			//扩容检查
			if (_finish == _end_of_storage)
			{

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

			}
			//数据挪动
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			//插入val值
			*pos = val;
			++_finish;
		}

在这里插入图片描述
  
  
  3)、insert涉及扩容时迭代器失效问题
  问题:

在这里插入图片描述
  
  原因解释:
在这里插入图片描述
  
  
  4)、insert模拟实现2.0

  解决方案: 若扩容,则要顺带更新pos指向位置

//保存二者指针差距
size_t len = pos - _start;
//扩容后更新pos指向
pos = len + _start;

		void insert(iterator pos, const T& val)
		{
			assert(pos <= _finish && pos >= _start);
			//扩容检查
			if (_finish == _end_of_storage)
			{
				//保存二者指针差距
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				//扩容后更新pos指向
				pos = len + _start;
			}

			//数据挪动
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			//插入val值
			*pos = val;
			++_finish;
		}

在这里插入图片描述
  
  
  

3.5.2、迭代器失效

  1)、问题引入与现象分析
  接上一阶段代码,分析下述情形:
  一开始我们使用find找到了p位置,然后在p位置前插入一次,现在我们变为p位置前连续插入多次。问:是否能成功?

		//使用算法中的find
		auto p = find(v.begin(), v.end(), 3);
		if (p != v.end())//找到了
		{
			v.insert(p, 30);//1、插入一个30

			cout << *p << endl;//2、再次来到p的位置
			v.insert(p, 40);//3、我们p位置前插入一个40,问:是否能成功?
		}

  现象如下:

在这里插入图片描述
  
  可能出现疑问如下:我们在3.5.1中解决了insert中pos位置更新的问题,为什么此处p仍旧不起效?
  这里我们需要思考上述写的insert函数中,形参pos和实参p之间的关系。可知晓的是,在insert函数中,它们是值传递,故形参改变不影响实参。

		void insert(iterator pos, const T& val)
		{
			assert(pos <= _finish && pos >= _start);
			//扩容检查
			if (_finish == _end_of_storage)
			{
				//保存二者指针差距
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				//扩容后更新pos指向
				pos = len + _start;
			}

			//数据挪动
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			//插入val值
			*pos = val;
			++_finish;
		}

  
  
  2)、解决方案
  ①一个相对比较适合的方法是,在此类情况中,最好别再p位置失效后再去访问p。
  
  
  ②有人可能提出,我们在insert中为pos加上一个引用,即传引用返回,这样不就解决了?void insert(iterator& pos, const T& val)

		void insert(iterator& pos, const T& val)
		{
			assert(pos <= _finish && pos >= _start);
			//扩容检查
			if (_finish == _end_of_storage)
			{
				//保存二者指针差距
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				//扩容后更新pos指向
				pos = len + _start;
			}

			//数据挪动
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			//插入val值
			*pos = val;
			++_finish;
		}

  事实上这样做,针对insert而言确实可以解决问题,但同样会面临新的问题。
  第一,这样的模拟实现与库中不匹配,库中直接使用iterator pos
  第二,这样做会带来新的问题,如下:

v.insert(v.begin(), 1);

  此段代码无法编译通过。因为begin模拟实现时,我们使用的是iterator传值返回,中间会生成一份临时变量,具有常性,后续insert处权限放大。
    Ⅰ、若为insert加上const,那么又无法解决修改问题。
    Ⅱ、而如果将begin也写为传引用返回,iterator& begin(),这样会使得begin具有修改能力,反而增添麻烦。

		iterator begin()
		{
			return _start;
		}

  
  
  3)、思考:上述值传递中,p一定存在迭代器失效问题吗?
  回答:同3.5.2中所讲,在涉及扩容问题时,p才存在失效。
  演示:如下述,假如我们一开始插入5个数值,容量空间足够的情况下,此处不存在p失效问题。

在这里插入图片描述

  
  
  4)、insert为push_back提供复用

		void push_back(const T& x)
		{
			insert(end(), x);
		}

  
  

  5)、insert模拟实现3.0:案例演示
  案例要求: 在所有的偶数前插入该偶数的二倍值。

  代码演示+现象分析:
  我们以上述insert2.0版本为例进行演示,顺带再来回顾一下迭代器失效问题。

	void test_vector5()
	{
		std::vector<int> v;
		v.reserve(10);
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		//v.push_back(5);

		//在所有偶数前插入该偶数的2倍
		auto it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
			{
				v.insert(it, *it * 2);
			}
			++it;
		}
	}

  上述代码我们直接运行则程序崩溃无输出结果,调试后发现如下:it始终指向2的位置。这就是迭代器失效的另一种模式:因为数据挪动,导致外部指针指向错乱。
  ps: 迭代器失效的第一种模式:函数内扩容,导致野指针。
在这里插入图片描述
  
  为了解决上述问题,也一并解决insert中扩容后外部迭代器失效问题,一个方案如下
    Ⅰ、对insert函数,模仿库中带上iterator返回值;
    Ⅱ、对原先的二倍插入循环,需要更新it指向的新位置,旨在解决扩容后外部迭代器失效问题。

		iterator insert(iterator pos, const T& val)
		{
			assert(pos <= _finish && pos >= _start);
			//扩容检查
			if (_finish == _end_of_storage)
			{
				//保存二者指针差距
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				//扩容后更新pos指向
				pos = len + _start;
			}

			//数据挪动
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			//插入val值
			*pos = val;
			++_finish;

			return pos;
		}
	void test_vector5()
	{
		std::vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		//v.push_back(5);

		//在所有偶数前插入该偶数的2倍
		auto it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
			{
				it=v.insert(it, *it * 2);
				++it;
			}
			++it;
		}
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

  演示结果:
在这里插入图片描述

  
  
  

3.5.3、vector::erase

  1)、erase模拟实现1.0


		void erase(iterator pos)
		{
			assert(pos < _finish && pos >= _start);
			iterator end = pos + 1;
			while (end < _finish)
			{
				*(end - 1) = *end;
				++end;
			}
			--_finish;
		}

在这里插入图片描述
  
  2)、erase是否会导致迭代器失效?
  ①主要看编译器如何实现erase函数,不排除有些编译器以时间换空间进行缩容:
  

			if (size() < capacity()/2)
			{
				// 缩容 -- 以时间换空间
			}

  
  
  ②其它案例演示:删除vector中的偶数
  使用代码如下:

		void erase(iterator pos)
		{
			assert(pos < _finish && pos >= _start);
			iterator end = pos + 1;
			while (end < _finish)
			{
				*(end - 1) = *end;
				++end;
			}
			--_finish;
		}
void test_vector4()
	{
		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;

		//删除所有偶数
		auto it = v.begin();
		while (it != v.end())
		{
			if(*it % 2 == 0)
			{
				v.erase(it);
			}
			++it;
		}

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

  现象如下:

在这里插入图片描述  
  
  
  
  3)、erase模拟实现2.0

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


			//if (size() < capacity()/2)
			//{
			//	// 缩容 -- 以时间换空间
			//}

			return pos;
		}

  基于2.0版本的erase我们再来修改上述 2) 中的题目:

void test_vector4()
	{
		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;

		//删除所有偶数
		auto 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;
	}

  
  
  

3.5.5、细节问题:不同编译器下的迭代器失效

  需要注意的是,上述我们对迭代器失效的问题演示,其结果是未定义的,因为针对不同平台其STL底层实现并不一致。
  即,STL只是一个规范,其细节如何实现不做要求。
  VS:PJ版。
  g++:SGI版。
  
  

  

3.6、vector::resize

  1)、库函数中声明回顾
在这里插入图片描述

  
  2)、模拟实现
  根据上述可知,resize面临三种情况:
    Ⅰ、当n>capacity:扩容+使用val初始化;
    Ⅱ、当size<n<capacity:使用val初始化;
    Ⅲ、当n<size:删除多余数据
  
  模拟实现如下:

		void resize(size_t n, const T& val=T())
		{
			if (n > capacity())
			{
				reserve(n);
			}

			if (n > size())//两种情况:n>capacity、size<n<capacity
			{
				//只需要初始化即可
				while (_finish < _start + n)
				{
					push_back(val);
					++_finish;
				}
			}
			else
			{
				_finish = _start + n;
			}
		}

  演示验证一:

	void test_vector11()
	{
		//resize常用场景:在生成对象后开辟空间
		vector<int>v;
		v.resize(5);//验证默认缺省值
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		v.resize(10, 2);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

在这里插入图片描述
  
  演示验证二:

	void test_vector11()
	{

		vector<int> v1;
		v1.reserve(10);
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

		v1.resize(8,8);//由大到小,而值只有5个,会添3个值

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

在这里插入图片描述
  
  
  
  

3.7、vector::front、vector::back

  1)、库函数中声明回顾
在这里插入图片描述

  
  2)、模拟实现


		T& front()
		{
			assert(size() > 0);
			return *_start;
		}

		T& back()
		{
			assert(size() > 0);
			return *(_finish - 1;
		}

  
  
  
  

4、拷贝构造与赋值:vector的深拷贝问题

4.1、深浅拷贝说明

  1)、知识回顾
  在类和对象章节,我们曾说明:拷贝构造对于内置类型完成浅拷贝/值拷贝,对于自定义类型则会调用它对应的拷贝构造。
  
  
  2)、vector拷贝构造分析
  vector的成员变量是内置类型,故编译器默认生成的拷贝构造函数完成的是浅拷贝。
  PS:typedef T* iterator;、此处尽管iterator是类模板,且T*会存在自定义类型的指针,但其仍旧是内置类型。

	private:
		iterator _start;//起始
		iterator _finish;//结束
		iterator _end_of_storage;//容量空间

  以如下代码进行拷贝构造:

		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);

		vector<int> v2(v);

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

在这里插入图片描述
  浅拷贝带来的两个问题:
    Ⅰ、析构两次
    Ⅱ、一个对象的修改,会影响另一个对象
  
  
  

4.2、vector拷贝构造演示

4.2.1、写法一:自己开辟空间,自己拷贝数据

  

		vector(const vector<T>& v)
		{
			_start = new T[v.size()];//此处也可以开辟v.capacity()大小的空间,各有各的优缺点
			memcpy(_start, v._start, sizeof(T)*v.size());//照搬数据
			_finish = _start + v.size();
			_end_of_storage = _start + v.size();//此处this._finish的大小根据上述我们开辟空间时的选择而变动
		}

  
  

4.2.2、写法二:复用

	vector(const vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			reserve(v.size());
			for (const auto& e : v)
			{
				push_back(e);//this.push_back()
			}
		}

  
  
  

4.2.3、使用迭代器区间构造构造函数

  1)、vector构造函数:使用迭代器区间构造1.0
  涉及问题:
  Ⅰ、能否双模板嵌套式使用?
  Ⅱ、为什么需要新定义一个模板InputIterator,而不使用原先的Iterator

		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_bakc(*first);
				++first;
			}
		}
	void test_vector7()
	{
		string str("hello world");
		vector<int> v(str.begin(), str.end());//使用迭代器区间构造
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

  上述代码是否会存在什么问题?
  回答:_start_finish_end_of_storage没有初始化,在有些编译器下其是随机值,那么reserve开辟空间时,此处非空就会拷贝数据、释放空间、存在越界问题。
在这里插入图片描述

  
  2)、vector构造函数:使用迭代器区间构造2.0

  _start_finish_end_of_storage初始化为空。

		//使用迭代区间的构造函数:含类模板
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

在这里插入图片描述

  
  

4.2.4、写法三:复用4.2.3、vector:: swap

在这里插入图片描述

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

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

  

  
  
  

4.2.5、构造函数:n个val值

  1)、模拟实现及细节解析

	vector(size_t n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

  对const T& val = T(),实际上这个函数使用了半缺省参数,其缺省值是T(),一个T类型的匿名对象。
    Ⅰ、假若T()是自定义类型,则调用自定义类型的默认构造(事实上内置类型也有模板参数)
    Ⅱ、假若T()是内置类型,因C++中模板的出现,也拥有对应的默认构造函数,此处以int举例。

	void test_vector9()
	{
		int i=11;
		int j = int();
		int k(10);
		cout << "i:" << i << endl;
		cout << "j:" << j << endl;
		cout << "k:" << k << endl;

	}

在这里插入图片描述

  
  
  2)、演示

	void test_vector10()
	{
		vector<int>v1(10);//验证默认缺省值

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

		vector<int*>v2(5);
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;
	}

在这里插入图片描述

  
  
  3)、一个错误说明

		vector<int>v1(10,1);//验证默认缺省值

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

在这里插入图片描述
  原因解释:类型匹配。

  
  
  

4.3、vector赋值:operator=

		//赋值v1=v2
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

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

  
  
  

4.4、二维数组下深浅拷贝问题

  1)、问题引入
  在vector(一)中,我们曾写过杨辉三角:其涉及到了vector<vector<int>>嵌套使用。

class Solution {
public:
    vector<vector<int>> generate(int numRows) {

        vector<vector<int>> vv;//定义一个vector<vector<int>>类型的数据
        vv.resize(numRows);//第一次开辟空间:numRows,表示总行数(整体大小)
        for(size_t i=0;i<numRows;i++)//对每行预处理:空间大小、边界数值
        {
            vv[i].resize(i+1,0);//第二次开辟空间,表示初始化杨辉三角的每行大小
            vv[i].front()=vv[i].back()=1;//杨辉三vv.size()角每行首尾数据为1

            //vv[i].resize(i+1,1);//上述代码也可以合并为一行实现
        }

        for(size_t i=2;i<vv.size();i++)//对每行的中间数据做处理:第i行第j个元素=第i-1行第j=1个元素+第i-1行第j个元素
        {
            for(size_t j=1;j<i;j++)
            {
                vv[i][j]=vv[i-1][j-1]+vv[i-1][j];
            }
        }

        return vv;
    }
};

vector<vector<int>> ret = Solution().generate(5);

  在使用std中的vector时,这段代码成功运行。而将其放入我们自己实现的vector中,则会发现运行失败。
  为什么我们自己的模拟实现的vector会失败呢?

  Ⅰ、对vector<vector<int>> generate(int numRows),如果我们不传值返回,运行成功,而传值返回运行失败。需要注意的是,此处传值返回涉及自定义类型,有深浅拷贝问题。
  Ⅱ、基于此我们调试发现:外层的vector深拷贝成功(值一致、地址空间不一致),而内存的vector居然是浅拷贝。
在这里插入图片描述

在这里插入图片描述
  以上是现象,现在来分析情况:
  ①自定义类型传值返回,中间生成一个临时变量,涉及深浅拷贝问题。
  ②拷贝构造我们模拟实现了两类,一类是传统写法,使用了memcpy,由上述图二可知,memcpy是浅拷贝,那么就涉及到同一空间析构两次的问题。修改方法如下:


		vector(const vector<T>& v)//拷贝构造传统写法
		{
			_start = new T[v.size()];//此处也可以开辟v.capacity()大小的空间,各有各的优缺点
			//memcpy(_start, v._start, sizeof(T)*v.size());//照搬数据
			for (size_t i = 0; i < sz; i++)//照搬数据
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_end_of_storage = _start + v.size();//此处this._finish的大小根据上述我们开辟空间时的选择而变动
		}

  ③假若使用的是现代写法,在我们写的两个以swap为交换的拷贝构造,都没问题。此处出现问题的是扩容。原先我们写的扩容函数中用了memcpy,同样是浅拷贝导致。修改方法如下:


		void reserve(size_t n)
		{
			if (n > capacity())//满足该条件才进行扩容
			{
				size_t sz = size();//因为后续重新确定指向关系时需要知道size值
				T* tmp = new T[n];
				if (_start)//说明原先空间有数据,
				{
					//memcpy(tmp, _start, sizeof(T) * sz);//需要挪动
					for (size_t i = 0; i < sz; i++)//需要挪动
					{
						tmp[i] = _start[i];
						//*(tmp + i) = *(_start + i);
					}
					delete[] _start;//释放旧空间
				}
				//重新确定指向关系
				_start = tmp;
				_finish = _start + sz;
				//_finish = _start + size();//如果是在这里获取size值,则在原先空间有数据的情况下,_start已经被delete

				_end_of_storage = _start + n;
			}
		}

  
  
  
  
  
  
  
  
  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/156077.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

记录robosense RS-LIDAR-16使用过程1

拿到设备&#xff0c;首先对照型号去官网下载相关资料&#xff08;用户手册/软件/SDK&#xff09;,需要填写资料https://www.robosense.ai/resources-27工业相机通常也有出厂SDK文件&#xff0c;之前有使用知微传感的D130相机&#xff0c;也是先安装SDK、看手册然后使用。大型厂…

【Java集合】Map接口常用方法及实现子类

文章目录01 Map 接口实现类的特点02 Map 接口和常用方法03 Map 接口遍历方法04 HashMap 用例 小结05 HashMap 底层&扩容机制06 Hashtable07 PropertiesMap为双列集合&#xff0c;Set集合的底层也是Map&#xff0c;只不过有一列是常量所占&#xff0c;只使用到了一列。 01 …

国科大《高级人工智能》沈老师部分——行为主义笔记

国科大《高级人工智能》沈老师部分——行为主义笔记 沈华伟老师yyds&#xff0c;每次上他的课都有一种深入浅出的感觉&#xff0c;他能够把很难的东西讲的很简单&#xff0c;听完就是醍醐灌顶&#xff0c;理解起来特别清晰今年考试题目这部分跟往年基本一样&#xff0c;沈老师画…

长城汽车2022年销量106万辆,20万以上车型占比15%

2023年&#xff0c;长城汽车预计将推出超10款新能源车型&#xff0c;发力新能源和智能化。1. 年度销量&#xff1a;超106万辆 根据长城最新发布的产销数据&#xff1a;•2022年&#xff0c;长城汽车全年销售1,067,523辆&#xff1b; •其中&#xff0c;海外市场累计销售173,180…

2022CTF培训(十二)IOT 相关 CVE 漏洞分析

附件下载链接 NETGEAR R7800&#xff08;CVE-2020-11790&#xff09; NETGEAR R7800 存在命令注入漏洞&#xff0c;下面以 V1.0.2.62 版本固件为例进行介绍。 固件仿真 漏洞存在于 uhttpd 中&#xff0c;由于该功能比较独立&#xff0c;可以直接用 qemu user mode 仿真。 /…

在 anaconda 中安装 tensorflow models (gpu)

环境&#xff1a;Windows; Intel CPU Nvidia GPU 1. 创建环境 不推荐单次安装过多的库&#xff0c;可能导致安装失败&#xff08;如超出终端缓存等&#xff09;注意添加库的顺序 tensorflow-gpu 需要在 cudatoolkit 之前否则下载的 tensorflow-gpu 不支持 gpu 「实测」 TODO…

设备注册挂载流程(包含上电、使能、i2c通讯介绍)

目录 简介 上电时序 电压不同 时序不同 使能与复位 CLK时钟 I2C通讯 主从关系 识别设备 通讯格式 简介 任何相对于主板芯片的外挂设备都需要一定的注册挂载流程 &#xff08;外挂设备&#xff1a;比如摄像头、nfc芯片、显示屏等等&#xff09; 设备的挂载则需要满足…

JAVAEE-多线程(4)

目录 定时器 实现自己的Timer 线程池 常见的锁策略&#xff1a; 乐观锁和悲观锁 读写锁和普通互斥锁 重量级锁和轻量锁 自旋锁和挂起等待锁 公平锁和非公平锁 可重入锁和不可重入锁 synchronized CAS CAS和ABA问题 锁粗化 JUC 原子类 Semaphore CountDownLatc…

CAN总线控制器MCP2515 替代芯片 DP2515 DP2515-I/ST

汽车K总线与CAN的区别是什么 1、功能不同   K线一般用于检测系统&#xff0c;属单线模式&#xff0c;与诊断仪器连接并相互传递数据。CAN线主要用于控制单元与控制单元之间传递数据、属双线模式&#xff0c;分高位线和地位线。   2、通讯速度不同   K线通讯速率较低&…

101.对称二叉树 | 递归 + 迭代

对称二叉树 leetcode : https://leetcode.cn/problems/symmetric-tree/ 参考 对称二叉树 递归思路 首先在开始时, 一定要注意, 对称二叉树对比的并不是一个节点的左右子树, 而是两棵树, 这个很关键! 对比时是内侧和内侧对比, 外侧和外侧对比, 递归三步 : 确定递归的参数以…

1.1.2 了解JAVA语言

文章目录1 JAVA语言发展史2 面向对象的概念3 跨平台性4 JDK1 JAVA语言发展史 JAVA是由詹姆斯•高斯林&#xff08;James Gosling&#xff09;所创建的&#xff0c;其1977年获得了加拿大卡尔加里大学计算机科学学士学位&#xff0c;1983年 获得了美国卡内基梅隆大学计算机科学博…

4)Mybatis数据源以及事务实现

1. Mybatis数据源分为两种&#xff0c;一种直接连接数据库&#xff0c;一种使用连接池连接数据库&#xff0c;具体代码实现在包目录下 org.apache.ibatis.datasource 数据源接口&#xff1a; javax.sql.DataSource 池化数据源&#xff1a; org.apache.ibatis.datasource.…

OpenGL集锦(1)-安装与概述

目录概述&#xff46;&#xff45;&#xff44;&#xff4f;&#xff52;&#xff41;下安装编写OpenGL应用程序测试hello,world概述 OpenGL&#xff08;英语&#xff1a;Open Graphics Library&#xff0c;译名&#xff1a;开放图形库或者“开放式图形库”&#xff09;是用于…

Lichee_RV学习系列--CoreMark-Pro移植

Lichee_RV学习系列文章目录 Lichee_RV学习系列—认识Lichee Rv Dock、环境搭建和编译第一个程序 Lichee_RV学习系列—移植dhrystone 文章目录Lichee_RV学习系列文章目录一、CoreMark-Pro简介二、获取源码三、编译coremark-pro1、配置coremark-pro2、编译coremark-pro四、开发板…

各种树的总结

1.B树和B树 数据库的大量数据用什么存储&#xff1f;为什么是B树和B树&#xff1f;使用二叉树不行吗&#xff1f;先来说说他们的演变吧&#xff0c;首先如果用二叉树的话都为排好序的树查询起来是不是效率不高&#xff1f;所以此时我们提出了对树排序&#xff0c;就变成了二叉…

联想拯救者屏幕亮度无法调节,监视器和显卡驱动问题,经过多种测试

主要的问题位置 1&#xff0c;设备管理器中的监视器部分 2&#xff0c;设备管理器的显卡适配器部分 个人电脑出现这种情况的原因 自己拆一下机器加装固态&#xff0c;但这种感觉不应该导致问题。但导致这种问题的原因可能是装固态时候把电池拔了。 一些网上常说的方法 更新…

数字化转型对企业有什么意义?有哪些案例可以分享?

如何看待制造企业数字化转型&#xff1f;制造业企业数字化转型有哪些思路和案例&#xff1f; 一提到制造企业数字化转型&#xff0c;大多数人都认为&#xff0c;这是专属于大型制造企业的行为。其实不然&#xff0c;对于中小型制造企业&#xff0c;数字化转型也应该从易到难&a…

interview

1.PyTorch1.1 Conv2d1.2 dataset&#xff0c;dataloader1.3 训练pipeline1.4 梯度归零1.5 torch保存模型种类及区别2.目标检测2.1 yolo3,4,5,7区别2.2 yolo使用的loss(ciou,BCE等等)ciouBCElossL1,L2,CE,BCE2.3 图像增强2.4 IOU计算公式3.深度学习基础3.1 卷积公式4.TensorRT5.…

Niantic:未来AR重要场景,VPS众包3D地图到底是啥?

几个世纪以来&#xff0c;人们使用指南针、地图、星盘和象限仪来找路&#xff0c;而在过去二十年里&#xff0c;GPS成为了主流的定位系统&#xff0c;并且与手机结合后&#xff0c;让人们的出行越来越方便。而随着摄像头等技术发展&#xff0c;我们也开始看到视觉定位技术的崛起…

(almalinux,rockylinux,openeuler,openanolis,centos,ubuntu)云原生容器镜像漏洞trivy扫描对比

一、下载并安装trivy漏洞扫描工具 下载&#xff1a; https://github.com/aquasecurity/trivy/releases/download/v0.31.3/trivy_0.31.3_Linux-64bit.rpm 以下为centos平台的安装&#xff1a; [rootlocalhost ~]# rpm -ivh trivy_0.31.3_Linux-64bit.rpm Preparing... …