模拟实现vector(非常详细)

模拟实现vector

  • 1.vector基本概念
  • 2.vector()默认构造函数
  • 3.size()成员函数+迭代器
  • 4.capacity()成员函数
  • 5.empty()成员函数
  • 6.reverse()成员函数
  • 7.push_back()成员函数
  • 8.pop_back()成员函数
  • 9.operator[ ]成员函数
  • 10.resize()成员函数
  • 11.insert()成员函数
  • 12.erase()成员函数
  • 13.swap()成员函数
  • 14.operator=()赋值重载函数
  • 15.vector其他的构造函数
  • 16.vector拷贝构造函数
  • 17.~vector()析构函数
  • 18.vector模拟实现完整代码

🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【C++的学习】
📝📝本篇内容:vector基本概念;vector()默认构造函数;size()成员函数+迭代器;capacity()成员函数;empty()成员函数;reverse()成员函数;push_back()成员函数;pop_back()成员函数;operator[ ]成员函数;resize()成员函数;insert()成员函数;erase()成员函数;swap()成员函数;operator=赋值重载函数;vector其他构造函数;vector拷贝构造函数;~vector()析构函数;vector模拟实现完整代码
⬆⬆⬆⬆上一篇:C++STL之vector
💖💖作者简介:轩情吖,请多多指教(> •̀֊•́ ) ̖́-

1.vector基本概念

上一节我们讲了vector的概念以及常用的接口,这一节我们讲一下它的实现,它的底层其实就只有三个成员变量:_start,_finish,_end_of_storage。_start指向目前使用空间的头,_finish指向目前使用空间的尾,_end_of_storage指向目前可用空间的尾。通过这个三个指向就可以完成我们vector的实现,可以看一下下面的图来简单理解一下

在这里插入图片描述

//基本结构
#include <iostream>
using namespace std;
namespace lnb
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;//迭代器类型,指针本身就可以做迭代器类型


	private:
		iterator _start;//指向目前使用空间的头
		iterator _finish;//指向目前使用空间的尾
		iterator _end_of_storage;//指向目前可用空间的尾
	};

}

2.vector()默认构造函数

		vector()//默认构造函数
		:_start(nullptr),//全部设为nullptr指针
		_finish(nullptr),
		_end_of_storage(nullptr)
		{}

这是默认的构造函数,不需要传任何参数,把指针全部设为nullptr即可。后续的其他构造函数,建立在其他的成员函数上写会更方便,先继续往后看

3.size()成员函数+迭代器

		iterator begin()//迭代器的开始
		{
			return _start;
		}

		iterator end()//迭代器的结束
		{
			return _finish;
		}

		size_t size()const//vector的大小
		{
			return end() - begin();
		}

这边在实现size()的基础上顺便把迭代器的开始和结束也一起实现了,这样再写size()可以看上去更加的简便
在这里插入图片描述

我们的迭代器是前闭后开的,也就是整个实际范围从begin()开始,直到end()-1,迭代器end()所指的是“最后一个元素的下一个位置”
在这里插入图片描述

4.capacity()成员函数

		typedef const T* const_iterator;
		
		const_iterator begin()const//针对于const类型的对象
		{
			return _start;
		}

		const_iterator end()const//针对于const类型的对象
		{
			return _finish;
		}
		
		size_t capacity()const//vector容量大小
		{
			return _end_of_storage - begin();
		}

capacity()也是同样的道理,同时也把const迭代器给一起实现了,这边主要是capacity()被const类型修饰了,那么调用的begin()就需要也是被const修饰的,capacity()理解见下图
在这里插入图片描述

5.empty()成员函数

bool empty()const//判空
		{
			return begin() == end();
		}

这个函数是用来判断vector是否为空的,我们直接使用了_start和_finish,因为当它们都为nullptr空时才会相同

6.reverse()成员函数

void reverse(size_t n)//预留空间,提前扩容用的
		{
			if (n > capacity())//保证n是大于现有空间的
			{
				size_t old_size = size();
				//1.先开辟新空间
				T* new_start = new T[n];//这边暂时不处理异常
				//2.将数据拷贝到新空间去
				for (int i = 0; i < size(); i++)
				{
					//这边不能使用memcpy这种直接内存拷贝的函数
					new_start[i] = _start[i];
				}
				//3.释放空间
				delete[] _start;
				//4.修改指向
				_start = new_start;
				_finish = new_start + old_size;
				_end_of_storage = new_start + n;
			}
		}

reverse主要是为了提前预留空间,来防止后续添加元素造成的多次扩容从而效率低下,具体的流程:先开辟新的空间→拷贝旧的数据到新空间→释放旧空间→修改指向。其中最需要注意的是拷贝元素的过程,不能直接使用内存拷贝函数,我们看下面的图来理解一下
在这里插入图片描述
在这里插入图片描述
在我们的代码中,如果通过memcpy拷贝完后,需要把旧的空间给释放掉,那么此时我们新的new_start中的string对象中的_str就是野指针,它指向的是一片已经释放的空间
在这里插入图片描述
因此我们需要使用循环赋值才能改变这个情况,当使用循环赋值后,会自动调用string的赋值重载函数,这样str指向的空间就不是同一个了。_start即使是释放掉旧的空间,那它也只是释放的是自己的string元素

7.push_back()成员函数

	void push_back(const T& val)//尾插
		{
			if (_finish==_end_of_storage)
			{
				//我们扩容以2倍增长
				reverse(capacity() == 0 ? 2 : capacity() * 2);
			}
			*_finish = val;
			_finish++;
		}

我们的尾插也非常的方便,只不过是要注意一下空间够不够,不够就需要进行扩容

8.pop_back()成员函数

我们先来看一下下面的代码,尾删这样写对吗

//!!!error code
	void pop_back()//尾删
		{
			assert(!empty());//保证有数据
			_finish--;
		}

对于内置类型确实没有问题,但是对于string这种就不行了,会造成内存泄露,因此我们需要将其进行释放

	void pop_back()//尾删
		{
			assert(!empty());//保证有数据
			_finish->~T();//需要调用对应元素的析构,不然会造成内存泄露
			_finish--;
		}

9.operator[ ]成员函数

		T& operator[](size_t n)//像数组一样去访问
		{
			assert(n < size());
			return _start[n];
		}

		//针对const类型的vector
		const T& operator[](size_t n)const
		{
			assert(n < size());
			return _start[n];
		}

10.resize()成员函数

		void resize(size_t n,const T& val=T())
		{
			//要分三种情况,真正有作用的就两种情况
			if (n >size())
			{
				for (int i = 0; i < n; i++)
				{
					push_back(val);
				}
			}
			else if(n<size())
			{
				size_t distance = size()-n;//计算出多了多少元素
				for (int i = 0; i < distance; i++)
				{
					_finish->~T();//这边要记得释放,不然对于自定义类型会内存泄露
					_finish--;
				}
			}	
			else
			{
				return;
			}
		}

我们的resize要分三种情况,分别是大于等于小于n,大于n的需要减少vector的大小;小于n的需要增大vector的大小,用val来填充;而等于n什么都不用做

11.insert()成员函数

iterator insert(iterator position, const T& val)//插入
		{
			//迭代器失效问题,因此需要保存迭代器位置
			size_t pos = position-_start;

			//1.先查看是否需要扩容
			if (_finish == _end_of_storage)
			{
				reverse(capacity() == 0 ? 2 : capacity() * 2);
				position= _start + pos;//扩容后position不在原来的空间了
			}
			//2.先进行元素的移动,将插入位置留出
			iterator end = _finish-1;
			while (end >= position)
			{
				*(end + 1) = *end;
				end--;
			}
			//3.放入元素
			*position = val;
			_finish++;
			
			return position;
		}

我们可以看一下下图来理解一下它整体的逻辑以及移动的过程
在这里插入图片描述
在这里插入图片描述

12.erase()成员函数

	//删除
		iterator erase(iterator position)
		{
			iterator begin = position+1;
			while (begin < end())
			{
				*(begin - 1) = *begin;
				begin++;
			}
			_finish--;
			return position;
		}

这个函数也很简单,只需要往前移动元素覆盖即可

13.swap()成员函数

void swap(vector<T>& v)//交换两个对象
		{
			//调用库中的swap来进行交换
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

通过调用库中的swap函数来交换对象中的三个指针,这样就做的效率非常高,只进行交换成员变量即可完成对象之间的交换,std::swap()如下
在这里插入图片描述

14.operator=()赋值重载函数

//赋值重载--一个新奇的写法如下
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

可以看到我们的赋值重载写的非常的简洁,注意参数没有使用&,它其实是利用了被拷贝的对象,对其进行了拷贝构造,在函数体内又进行了交换,这样我们的目的就达成了,而被交换后的对象v会在出函数前析构,就把原来要拷贝的对象的资源释放掉了

15.vector其他的构造函数

		//T()是匿名对象,对于int这种内置类型,值为0
		vector(size_t n, const T& val = T())
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{
			for(int i=0;i<n;i++)
			{
				push_back(val);
			}
		}

		//通过迭代器构造
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);//直接调用其他函数来完成功能
				first++;
			}
		}

这里主要是讲一下第二个构造函数,可以发现它本质上是个模板函数,我们的vector是个模板类,在我们的模板类里面可以套模板函数的

16.vector拷贝构造函数

		//拷贝构造函数
		vector(const vector<T>& v)
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{
			//1.先开辟新空间
			_start = new T[v.capacity()];
			//2.拷贝数据,和reverse一样,不能使用memcpy
			for (int i = 0; i < v.size(); i++)
			{
				_start[i] = v[i];
			}
			//3.修改指向
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}
		//拷贝构造的另一种写法
		vector(const vector<T>& v)
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{
			vector<T> tmp(v.begin(),v.end());
			swap(tmp);
		}

在上面的代码中一共提供了两种写法,第二种更加简便,它的道理和operator=中的一样。第一种写法就是最老的写法,通过开辟空间,拷贝数据,修改指向来完成拷贝构造,但是如果一不注意使用内存拷贝就会造成浅拷贝,因此第一种写法需要注意一下细节,它和reverse中的情况其实差不多,我们可以再来讲一下
假设我们vector的元素是一个string,又或者是一个vector呢?我们来看图理解一下,当两个vector中的string中的_str指向相同的空间时会发生什么呢?在析构的时候,old_vector底层会释放开辟的空间,而空间中的是string类型,也需要调用它的析构函数,在string的析构中会把_str指向的空间释放掉,到这也什么问题,但是当new_vector释放时呢,它也会需要释放_str指向的空间,但是已经释放过一次了呀,这时候又释放就会造成程序崩溃。
在这里插入图片描述
所以说使用memcpy这种函数来拷贝,其实本质上也是一种浅拷贝,因此我们需要使用循环赋值才能改变这个情况,当使用循环赋值后,会自动调用string的赋值重载函数,这样str指向的空间就不是同一个了。
在这里插入图片描述
我们的string::operator=()并不是简单的拷贝,而是会进行开辟新的空间
在这里插入图片描述

17.~vector()析构函数

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

18.vector模拟实现完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cassert>
using namespace std;
namespace lnb
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;//迭代器类型,指针本身就可以做迭代器类型
		typedef const T* const_iterator;

		vector()//默认构造函数
			:_start(nullptr),//全部设为nullptr指针
			_finish(nullptr),
			_end_of_storage(nullptr)
		{}


		iterator begin()//迭代器的开始
		{
			return _start;
		}


		iterator end()//迭代器的结束
		{
			return _finish;
		}


		const_iterator begin()const//针对于const类型的对象
		{
			return _start;
		}


		const_iterator end()const//针对于const类型的对象
		{
			return _finish;
		}


		size_t size()const//vector的大小
		{
			return end() - begin();
		}


		size_t capacity()const//vector容量大小
		{
			return _end_of_storage - begin();
		}

		bool empty()const//判空
		{
			return begin() == end();
		}


		void reverse(size_t n)//预留空间,提前扩容用的
		{
			if (n > capacity())//保证n是大于现有空间的
			{
				size_t old_size = size();
				//1.先开辟新空间
				T* new_start = new T[n];//这边暂时不处理异常
				//2.将数据拷贝到新空间去
				for (int i = 0; i < size(); i++)
				{
					//这边不能使用memcpy这种直接内存拷贝的函数
					new_start[i] = _start[i];
				}
				//3.释放空间
				delete[] _start;
				//4.修改指向
				_start = new_start;
				_finish = new_start + old_size;
				_end_of_storage = new_start + n;
			}
		}


		void push_back(const T& val)//尾插
		{
			if (_finish == _end_of_storage)
			{
				//我们扩容以2倍增长
				reverse(capacity() == 0 ? 2 : capacity() * 2);
			}
			*_finish = val;
			_finish++;
		}


		void pop_back()//尾删
		{
			assert(!empty());//保证有数据
			_finish--;
			_finish->~T();//需要调用对应元素的析构,不然会造成内存泄露

		}


		T& operator[](size_t n)//像数组一样去访问
		{
			assert(n < size());
			return _start[n];
		}

		//针对const类型的vector
		const T& operator[](size_t n)const
		{
			assert(n < size());
			return _start[n];
		}


		//拷贝构造函数
		/*vector(const vector<T>& v)
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{
			//1.先开辟新空间
			_start = new T[v.capacity()];
			//2.拷贝数据,和reverse一样,不能使用memcpy
			for (int i = 0; i < v.size(); i++)
			{
				_start[i] = v[i];
			}
			//3.修改指向
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}*/

		//拷贝构造的另一种写法
		vector(const vector<T>& v)
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}


		//T()是匿名对象,对于int这种内置类型,值为0
		vector(size_t n, const T& val = T())
			:_start(nullptr),
			_finish(nullptr),
			_end_of_storage(nullptr)
		{
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}


		//通过迭代器构造
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);//直接调用其他函数来完成功能
				first++;
			}
		}


		//赋值重载--一个新奇的写法如下
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}


		void swap(vector<T>& v)//交换两个对象
		{
			//调用库中的swap来进行交换
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

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


		void resize(size_t n, const T& val = T())
		{
			//要分三种情况,真正有作用的就两种情况
			if (n > size())
			{
				for (int i = 0; i < n; i++)
				{
					push_back(val);
				}
			}
			else if (n < size())
			{
				size_t distance = size() - n;//计算出多了多少元素
				for (int i = 0; i < distance; i++)
				{
					_finish->~T();//这边要记得释放,不然对于自定义类型会内存泄露
					_finish--;
				}
			}
			else
			{
				return;
			}
		}


		iterator insert(iterator position, const T& val)//插入
		{
			//迭代器失效问题,因此需要保存迭代器位置
			size_t pos = position - _start;

			//1.先查看是否需要扩容
			if (_finish == _end_of_storage)
			{
				reverse(capacity() == 0 ? 2 : capacity() * 2);
				position = _start + pos;//扩容后position不在原来的空间了
			}
			//2.先进行元素的移动,将插入位置留出
			iterator end = _finish - 1;
			while (end >= position)
			{
				*(end + 1) = *end;
				end--;
			}
			//3.放入元素
			*position = val;
			_finish++;

			return position;
		}


		//删除
		iterator erase(iterator position)
		{
			iterator begin = position + 1;
			while (begin < end())
			{
				*(begin - 1) = *begin;
				begin++;
			}
			_finish--;
			return position;
		}


	private:
		iterator _start;//指向目前使用空间的头
		iterator _finish;//指向目前使用空间的尾
		iterator _end_of_storage;//指向目前可用空间的尾
	};
}

🌸🌸模拟实现vector的知识大概就讲到这里啦,博主后续会继续更新更多C++的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Elastic Cloud Serverless:深入探讨大规模自动扩展和性能压力测试

作者&#xff1a;来自 Elastic David Brimley, Jason Bryan, Gareth Ellis 及 Stewart Miles 深入了解 Elasticsearch Cloud Serverless 如何动态扩展以处理海量数据和复杂查询。我们探索其在实际条件下的性能&#xff0c;深入了解其可靠性、效率和可扩展性。 简介 Elastic Cl…

微信小程序之手机归属地查询

微信小程序之手机归属地查询 需求描述 API申请和小程序设置 API申请 第一步&#xff1a;完整账号注册 我们需要来到如下网站&#xff0c;注册账号&#xff1a;万维易源 第二步&#xff1a;账号注册完成以后&#xff0c;点击右上角的控制台信息。 第三步&#xff1a;在控制…

Agent AI: Surveying the Horizons of Multimodal Interaction---摘要、引言、代理 AI 集成

题目 智能体AI:多模态交互视野的考察 论文地址&#xff1a;https://arxiv.org/abs/2401.03568 图1&#xff1a;可以在不同领域和应用程序中感知和行动的Agent AI系统概述。Agent AI是正在成为通用人工智能&#xff08;AGI&#xff09;的一个有前途的途径。Agent AI培训已经证…

分布式光伏电站如何实现监控及集中运维管理?

安科瑞戴婷 Acrel-Fanny 前言 今年以来&#xff0c;在政策利好推动下光伏、风力发电、电化学储能及抽水蓄能等新能源行业发展迅速&#xff0c;装机容量均大幅度增长&#xff0c;新能源发电已经成为新型电力系统重要的组成部分&#xff0c;同时这也导致新型电力系统比传统的电…

SpringMVC其他扩展

一、全局异常处理机制: 1.异常处理两种方式: 开发过程中是不可避免地会出现各种异常情况的&#xff0c;例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题&#xff0c;甚至直接导致程序崩溃。因此&#xff0c;在开发过程中&#xff0c;…

Cesium 6 ,Cesium 全局注册,Cesium 常用事件解析与应用,全局注册Cesium事件

目录 前言 一. 全局注册事件 1.1 事件机制介绍 1.2 this.Cesium 和 this.viewer 1.3 全局注册 二. 常见事件分类 2.1 鼠标事件 2.1.1 点击事件 (click) 2.1.2 双击事件 (doubleClick) 2.1.3 鼠标移动事件 (mouseMove) 2.1.4 鼠标滚轮事件 (mouseWheel) 2.2 视图与摄…

matlab2024a安装

1.开始安装 2.点击安装 3.选择安装密钥 4.接受条款 5.安装密钥 21471-07182-41807-00726-32378-34241-61866-60308-44209-03650-51035-48216-24734-36781-57695-35731-64525-44540-57877-31100-06573-50736-60034-42697-39512-63953 6 7.选择许可证文件 8.找许可证文件 9.选…

在玩“吃鸡”的时候游戏崩溃要如何解决?游戏运行时崩溃是什么原因?

“吃鸡”游戏崩溃问题深度解析与解决方案&#xff1a;原因、修复与预防 在紧张刺激的“吃鸡”&#xff08;即《绝地求生》&#xff09;游戏中&#xff0c;突然遭遇游戏崩溃无疑会让玩家倍感沮丧。作为一名经验丰富的软件开发从业者&#xff0c;我深知游戏崩溃可能由多种因素引…

学习threejs,通过设置纹理属性来修改纹理贴图的位置和大小

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️Texture 贴图 二、&#x1…

【开源安全保护】如何安装JumpServer堡垒机

【开源安全保护】如何安装JumpServer堡垒机 什么是堡垒机 大家好&#xff0c;我是星哥&#xff0c;今天我以前来认识堡垒机 堡垒机&#xff08;Bastion Host&#xff09;&#xff0c;也称为跳板机&#xff08;Jump Server&#xff09;&#xff0c;是指在计算机网络中&#x…

JSP知识点总结

jsp九大对象 在jsp中内置了9个对象&#xff0c;无需创建该对象即可使用。其名称为固定名称。 1.out输出对象 - 属于JspWriter print(): 输出 flush(): 刷新 close(): 关闭 2.request请求对象 - 属于HttpServletRequest getParameter(): 获取请求的参数 setCharacterEncodin…

微信小程序全屏显示地图

微信小程序在界面上显示地图&#xff0c;只需要用map标签 <map longitude"经度度数" latitude"纬度度数"></map>例如北京的经纬度为&#xff1a;116.407004,39.904595 <map class"bgMap" longitude"116.407004" lati…

(软件测试文档大全)测试计划,测试报告,测试方案,压力测试报告,性能测试,等保测评,安全扫描测试,日常运维检查测试,功能测试等全下载

1. 引言 1.1. 编写目的 1.2. 项目背景 1.3. 读者对象 1.4. 参考资料 1.5. 术语与缩略语 2. 测试策略 2.1. 测试完成标准 2.2. 测试类型 2.2.1. 功能测试 2.2.2. 性能测试 2.2.3. 安全性与访问控制测试 2.3. 测试工具 3. 测试技术 4. 测试资源 4.1. 人员安排 4.2. 测试环境 4.2.…

2024 阿里云Debian12.8安装apach2【图文讲解】

1. 更新系统&#xff0c;确保您的系统软件包是最新的 sudo apt update sudo apt upgrade -y 2. 安装 Apache Web 服务器 apt install apache2 -y 3. 安装 PHP 及常用的扩展 apt install php libapache2-mod-php -y apt install php-mysql php-xml php-mbstring php-curl php…

golang实现单例日志对象

原文地址&#xff1a;golang实现单例日志对象 – 无敌牛 欢迎参观我的个人博客&#xff1a;无敌牛 – 技术/著作/典籍/分享等 介绍 golang有很多日志包&#xff0c;通过设置和修改都能实现日志轮转和自定义日志格式。例如&#xff1a;log、zap、golog、slog、log4go 等等。 …

git回退到某个版本git checkout和git reset命令的区别

文章目录 1. git checkout <commit>2. git reset --hard <commit>两者的区别总结推荐使用场景* 在使用 Git 回退到某个版本时&#xff0c; git checkout <commit> 和 git reset --hard <commit> 是两种常见的方式&#xff0c;但它们的用途和影响有很…

Spring Boot + MySQL 多线程查询与联表查询性能对比分析

Spring Boot MySQL: 多线程查询与联表查询性能对比分析 背景 在现代 Web 应用开发中&#xff0c;数据库性能是影响系统响应时间和用户体验的关键因素之一。随着业务需求的不断增长&#xff0c;单表查询和联表查询的效率问题日益凸显。特别是在 Spring Boot 项目中&#xff0…

人工智能学习用的电脑安装cuda、torch、conda等软件,版本的选择以及多版本切换

接触人工智能的学习三个月了&#xff0c;每天与各种安装包作斗争&#xff0c;缺少依赖包、版本高了、版本低了、不兼容了、系统做一半从头再来了。。。这些都是常态。三个月把单位几台电脑折腾了不下几十次安装&#xff0c;是时候总结一下踩过的坑和积累的经验了。 以一个典型的…

Vue工程化开发中各文件的作用

1.main.js文件 main.js文件的主要作用&#xff1a;导入App.vue&#xff0c;基于App.vue创建结构渲染index.html。

本地运行打包好的dist

首先输入打包命令 每个人设置不一样 一般人 是npm run build如果不知道可以去package.json里去看。 打包好文件如下 命令行输入 :npm i -g http-server 进入到dist目录下输入 命令cmd 输入 http-server 成功