11 vector的实现

注意

实现仿cplus官网的的string类,对部分主要功能实现

实现

文件

#pragma once
#include <string>
#include <assert.h>

namespace myvector
{
	template <class T>
	class vector
	{
	public:

		//iterator
		typedef T* iterator;
		typedef const T* const_iterator;

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		//constructor
			//default
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}
			//fill
		//匿名对象加const引用生命周期延长至变量结束
		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++)
			{
				PushBack(val);
			}
				
		
		}
		//重载int版,防止调用迭代器区间
		vector(int n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			Reserve(n);
			for (int i = 0; i < n; i++)
			{
				PushBack(val);
			}

		}
			//range
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr) //可以使用默认值,不用初始化列表
		{
			//迭代器必须用不等于,因为list可能后边的指针更小
			while (first != last)
			{
				PushBack(*first);
				first++;
			}
		}
			//copy 实现深拷贝,不然会浅拷贝
		vector(const vector<T>& x)
		{
			//Reserve(x.Capacity());
			_start = new T[x.Capacity()];
			//深拷贝,不能用memcpy,避免T是结构仍浅拷贝
			//memcpy(_start, x._start, sizeof(T) * x.Size());
			for (size_t i = 0; i < x.Size(); i++)
			{
				_start[i] = x._start[i];
			}
			_finish = _start + x.Size();
			_end_of_storage = _start + x.Capacity();
		}

		//add
		void PushBack(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				size_t capacity = Capacity() == 0 ? 4 : Capacity() * 2;
				Reserve(capacity);
			}

			*_finish = x;
			_finish++;
		}

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

			if (_finish == _end_of_storage)
			{
				size_t sz = pos - _start;
				//扩容后的迭代器失效问题,更新pos
				Reserve(Capacity() == 0 ? 4 : Capacity() * 2);
				pos = _start + sz;
			}

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

			*pos = x;
			_finish++;

			return pos;
		}

		//de
		void PopBack()
		{
			assert(!Empty());
			_finish--;
		}

		iterator Erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

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

			return pos;
		}

		//search
		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 capacity
		size_t Size() const
		{
			return _finish - _start;
		}

		bool Empty() const
		{
			return Size() == 0;
		}
		size_t Capacity() const
		{
			return _end_of_storage - _start;
		}

		void Reserve(size_t n)
		{

			if (n > Capacity())
			{
				T* tmp = new T[n];
				size_t size = Size();
				if (_start)
				{
					//memcpy(tmp, _start, Size() * sizeof(T));
					for (size_t i = 0; i < Size(); i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + size;
				_end_of_storage = _start + n;
			}


		}
		//类型()调默认构造,内置类型也可以
		//首先判断设置的大小是否大于当前size,大于就需要设置参数的数据,同时还考虑扩容
		void Resize(size_t n, T x = T())
		{
			if (n > Size())
			{
				if (n > Capacity())
				{
					Reserve(n);
				}

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

			}
			_finish = _start + n;
		}

		//destructor
		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}
	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}

测试

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include "vector.h"

using namespace myvector;
//void fun(const vector<int>& x)
//{
//	for (auto ch : x)
//	{
//		std::cout << ch << std::endl;
//	}
//}

class Solution {
public:
	vector<vector<int>> generate(int numRows) {
		vector<vector<int>> vv;
		vv.Resize(numRows, vector<int>());
		for (size_t i = 0; i < vv.Size(); ++i)
		{
			vv[i].Resize(i + 1, 0);
			vv[i][0] = vv[i][vv[i].Size() - 1] = 1;
		}

		for (size_t i = 0; i < vv.Size(); ++i)
		{
			for (size_t j = 0; j < vv[i].Size(); ++j)
			{
				if (vv[i][j] == 0)
				{
					vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
				}
			}
		}

		return vv;
	}
};

//vector<int> fun2()
//{
//	vector<int> s;
//	return s;
//}
int main()
{
	vector<int> v1;
	v1.PushBack(1);
	v1.PushBack(2);
	v1.PushBack(8);
	v1.PushBack(3);
	v1.PushBack(10);
	v1.PushBack(6);
	v1.PushBack(1);
	v1.PushBack(2);
	v1.PushBack(8);
	v1.PushBack(3);
	v1.PushBack(10);
	v1.PushBack(6);

	/*std::vector<int> v2;
	v2.push_back(1);
	v2.push_back(2);
	v2.push_back(8);
	v2.push_back(3);*/
	//v1.PushBack(6);
	/*vector<int>::iterator pos = std::find(v1.begin(), v1.end(), 2);
	v1.Insert(pos, 3);*/
	/*vector<int>::iterator pos = v1.begin() + 2;
	v1.Erase(pos);
	for (int i = 0; i < v1.Size(); i++)
	{
		std::cout << v1[i] << " ";
	}*/
	/*std::vector<int>::iterator pos = v2.begin() + 2;
	for (int i = 0; i < v2.size(); i++)
	{
		std::cout << v2[i] << " ";
	}
	std::cout << std::endl;*/
	/*fun(v1);*/
	/*v1.PopBack();
	v1.PopBack();
	v1.PopBack();*/
	//v1.Resize(8, 4);
	//vector<int>::iterator pos = std::find(v1.begin(), v1.end(), 5);
	/*if (pos != v1.end())
	{
		pos = v1.Insert(pos, 8);
	}*/
	//vs insert和erase后迭代器都不能再使用,强制检查
	//(*pos)++;
	/*std::vector<int>::iterator it = v2.begin();
	while (it != v2.end())
	{
		if (*it % 2 == 0)
		{
			it = v2.erase(it);
		}
		else
		{
			it++;
		}
	}*/
	/*std::vector<int>::iterator pos1 = std::find(v2.begin(), v2.end(), 2);
	v2.erase(pos1);
	pos1++;*/
	/*vector<int>::iterator it2 = v1.begin();
	while (it2 != v1.end())
	{
		std::cout << *it2 << " ";
		it2++;
	}*/

	/*vector<int> v3(10, 5);
	vector<int>::iterator it2 = v3.begin();
	while (it2 != v3.end())
	{
		std::cout << *it2 << " ";
		it2++;
	}*/
	
	//std::cout << std::endl;
	//int ary[] = { 5,4,3,2,1 };
	//vector<int> v4(ary, ary + 3);
	//排序algorithm头文件的排序
	//template <class RandomAccessIterator>
	// sort(RandomAccessIterator first, RandomAccessIterator last);
	//std::sort(v4.begin(), v4.end());
	/*vector<int>::iterator it3 = v4.begin();
	while (it3 != v4.end())
	{
		std::cout << *it3 << " ";
		it3++;
	}*/
	//降序,要传模板
	//template <class T> struct greater;
	/*std::greater<int> g;
	std::sort(ary, ary + sizeof(ary)/sizeof(ary[0]), g);
	for (auto ch : ary)
	{
		std::cout << ch << " ";
	}*/

	//vector<std::string> v2(3, "111");
	vector<vector<int>> v3 = Solution().generate(5);
	for (size_t i = 0; i < v3.Size(); ++i)
	{
		for (size_t j = 0; j < v3[i].Size(); ++j)
		{
			std::cout << v3[i][j] << " ";
		}
		std::cout << std::endl;
	}

	vector<int> v4;
	v4.Insert(v4.begin(), 4);;

	return 0;
}

注意事项

vector可以存储任意类型的元素,所以用模板,用三个指针,一个记录数组的首地址,finish记录最后一个元素的下一个地址,end_of_storage记录数组范围的地址,类型是模板T类型指针

vector(size_t n, const T& val = T())

对于T类型的值给默认值,不能默认给0,有可能是自定义结构,所以需要调用对应的构造函数,内置类型也可以这样构造

拷贝构造不能用memcpy,如果对象涉及资源管理就会浅拷贝,内存泄露或崩溃,所以要用赋值走运算符重载
在这里插入图片描述

赋值运算符重载,避免T是结构体的浅拷贝

Insert中元素的长度在扩容前需要记录,不然会失效

迭代器失效

迭代器的作用是让算法不用关心底层数据结构,实际就是一个指针,或者是对指针封装。比如:vector的迭代器就是原生态指针T*。因此迭代器失效,实际就是迭代器对应指针的指向的空间销毁了,使用一块已经被释放的空间,造成的后果就是程序崩溃

1.会引起底层空间改变的操作,都有可能是迭代器失效,如:resize、reserve、insert、assign、pushback等

#include <iostream>
using namespace std;
#include <vector>
int main()
{
 vector<int> v{1,2,3,4,5,6};
 
 auto it = v.begin();
 
 // 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
 // v.resize(100, 8);
 
 // reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
 // v.reserve(100);
 
 // 插入元素期间,可能会引起扩容,而导致原空间被释放
 // v.insert(v.begin(), 0);
 // v.push_back(8);
 
 // 给vector重新赋值,可能会引起底层容量改变
 v.assign(100, 8);
 
 /*
 出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
空间,而引起代码运行时崩溃。
 解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
赋值即可。
 */
 while(it != v.end())
 {
 cout<< *it << " " ;
 ++it;
 }
 cout<<endl;
 return 0;
}

插入时扩容会导致原pos的迭代器失效,所以要记录长度更新pos

2.指定位置元素的删除操作 erase

#include <iostream>
using namespace std;
#include <vector>
int main()
{
 int a[] = { 1, 2, 3, 4 };
 vector<int> v(a, a + sizeof(a) / sizeof(int));
 // 使用find查找3所在位置的iterator
 vector<int>::iterator pos = find(v.begin(), v.end(), 3);
 // 删除pos位置的数据,导致pos迭代器失效。
 v.erase(pos);
 cout << *pos << endl; // 此处会导致非法访问
 return 0;
}

erase删除pos位置的元素后,pos位置之后的元素会往前移,没有导致底层空间改变,理论上迭代器不会失效,但如果pos刚好是最后一个元素,删完之后就是end的位置,end是没有元素的,pos就失效了。因此删除任意位置元素vs就认为迭代器已经失效,会报错

下面是删除所有偶数的功能,第2个是正确的,通过返回值更新pos,迭代器就不会失效

#include <iostream>
using namespace std;
#include <vector>
int main()
{
 vector<int> v{ 1, 2, 3, 4 };
 auto it = v.begin();
 while (it != v.end())
 {
 if (*it % 2 == 0)
 v.erase(it);
 ++it;
 }
 
 return 0;
}
int main()
{
 vector<int> v{ 1, 2, 3, 4 };
 auto it = v.begin();
 while (it != v.end())
 {
 if (*it % 2 == 0)
 it = v.erase(it);
 else
 ++it;
 }
 return 0;
}

3.liux下,g++编译器对迭代器的检测不严格,没有vs极端

// 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main()
{
 vector<int> v{1,2,3,4,5};
 for(size_t i = 0; i < v.size(); ++i)
 cout << v[i] << " ";
 cout << endl;
 auto it = v.begin();
 cout << "扩容之前,vector的容量为: " << v.capacity() << endl;
 // 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效 
 v.reserve(100);
 cout << "扩容之后,vector的容量为: " << v.capacity() << endl;
 
 // 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会
 // 虽然可能运行,但是输出的结果是不对的
 while(it != v.end())
 {
 cout << *it << " ";
 ++it;
 }
 cout << endl;
 return 0;
}
程序输出:
1 2 3 4 5
扩容之前,vector的容量为: 5
扩容之后,vector的容量为: 100
0 2 3 4 5 409 1 2 3 4 5
// 2. erase删除任意位置代码后,linux下迭代器并没有失效
// 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的
#include <vector>
#include <algorithm>
int main()
{
 vector<int> v{1,2,3,4,5};
 vector<int>::iterator it = find(v.begin(), v.end(), 3);
 v.erase(it);
  cout << *it << endl;
 while(it != v.end())
 {
 cout << *it << " ";
 ++it;
 }
 cout << endl;
 return 0;
}
程序可以正常运行,并打印:
4
4 5
 
// 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
// 此时迭代器是无效的,++it导致程序崩溃
int main()
{
 vector<int> v{1,2,3,4,5};
 // vector<int> v{1,2,3,4,5,6};
 auto it = v.begin();
 while(it != v.end())
 {
 if(*it % 2 == 0)
 v.erase(it);
 ++it;
 }
 for(auto e : v)
 cout << e << " ";
 cout << endl;
 return 0;
}
========================================================
// 使用第一组数据时,程序可以运行
[@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[@VM-0-3-centos 20220114]$ ./a.out
1 3 5
=========================================================
// 使用第二组数据时,程序最终会崩溃
[@VM-0-3-centos 20220114]$ vim testVector.cpp
[@VM-0-3-centos 20220114]$ g++ testVector.cpp -std=c++11
[@VM-0-3-centos 20220114]$ ./a.out
Segmentation fault

从上面是三个例子可以看出:SGI STL迭代器失效并不一定崩溃,但运行结果肯定不对,it不在begin和end范围,肯定会崩溃

4.与vector类似,string插入+扩容+erase之后,迭代器也会失效

#include <string>
void TestString()
{
 string s("hello");
 auto it = s.begin();
 // 放开之后代码会崩溃,因为resize到20会string会进行扩容
 // 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了
 // 后序打印时,再访问it指向的空间程序就会崩溃
 //s.resize(20, '!');
 while (it != s.end())
 {
 cout << *it;
 ++it;
 }
 cout << endl;
 it = s.begin();
 while (it != s.end())
 {
 it = s.erase(it);
 // 按照下面方式写,运行时程序会崩溃,因为erase(it)之后
 // it位置的迭代器就失效了
 // s.erase(it); 
 ++it;
 }
}

迭代器失效,对迭代器重新赋值

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

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

相关文章

【Leetcode每日一题】 位运算 - 面试题 01.01. 判定字符是否唯一(难度⭐)(33)

1.题目解析 题目链接&#xff1a;面试题 01.01. 判定字符是否唯一 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 核心在于判断题目所给字符串是否存在相同字母&#xff0c;存在返回false即可&#xff0c;不存在返回true即可。 …

光电容积脉搏波PPG信号分析笔记

1.脉搏波信号的PRV分析 各类分析参数记参数 意义 公式 参数意义 线性分析 时域分析 均值MEAN 反应RR间期的平均水平 总体标准差SDNN 评估24小时长程HRV的总体变化&#xff0c; SDNN &#xff1c; 50ms 为异常&#xff0c;SDNN&#xff1e;100ms 为正常&#xff1b;…

灵魂指针,教给(三)

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 目录 一、 字符指针变量 二、数组指针变量 2.1 数组指针变量是什么 2.2 数组指针变量如何初始化 三、二维数组传参本质 四、函数…

如何在Linux系统安装SVN并配置固定公网地址远程访问【内网穿透】

文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件 3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口 5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6…

qsort函数

目录 1.qsort函数是什么 1.1qsort函数的原型 2.qsort函数的使用 2.1使用qsort函数排序整型数据 2.2使用qsort排序结构数据 3.qsort函数的模拟实现 1.qsort函数是什么 很多小伙伴们都没有听说过qsort这个函数&#xff0c;qsort函数是C语言标准库中的一个排序函数&#xf…

前端精准测试调用链路分析

精准测试在评估需求的测试范围时&#xff0c;需要评估一下代码的影响范围&#xff0c;这个范围有两部分&#xff1a;一是需求直接修改的代码&#xff1b;二是修改代码影响到的功能模块。代码影响到的功能一般是通过调用链路分析来实现的&#xff0c;java和kotlin代码可以由java…

【Java从入门到精通】Java异常处理

异常是程序中的一些错误&#xff0c;但并不是所有的错误都是异常&#xff0c;并且错误有时候是可以避免的。 比如说&#xff0c;你的代码少了一个分号&#xff0c;那么运行出来结果是提示是错误 java.lang.Error&#xff1b;如果你用System.out.println(11/0)&#xff0c;那么…

每日OJ题_路径dp②_力扣63. 不同路径 II

目录 力扣63. 不同路径 II 解析代码 力扣63. 不同路径 II 63. 不同路径 II 难度 中等 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;…

week06 day04 (数据库高级函数 procedure 、sql写函数)

一. ER模型 矩形&#xff1a; 代表实体椭圆&#xff1a;代表实体的属性菱形&#xff1a;relation 代表实体之间的关系 二. 存储过程&#xff08;procedure&#xff09; 1. 语法 语法: create procedure 存储过程名(参数,…) begin//代码 end// 注意&#xff1a; 因为在存储…

C语言 —— 图形打印

题目1&#xff1a; 思路&#xff1a; 如果我们要打印一个实心正方形&#xff0c;其实就是一个二维数组&#xff0c;i控制行&#xff0c;j控制列&#xff0c;行列不需要控制&#xff0c;arr[i][j]直接打印星号即可。 对于空心正方形&#xff0c;我们只需要控制行和列的条件&…

MyBatis学习笔记|2024最新版Mybatis

Mybatis简介 MyBatis历史 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到GithubiBatis一词来源于“internet”和“aba…

应对电子邮件危害:垃圾邮件的特征、影响和预防策略

垃圾邮件是一种侵入性的未经请求的电子通信形式&#xff0c;主要通过电子邮件传播。它将大量无关、欺骗性或恶意内容发送给邮箱用户&#xff0c;内容涵盖令人讨厌的广告和欺诈行为、以及网络钓鱼链接。 截至 2022 年 12 月&#xff0c;垃圾邮件占电子邮件流量的 45% 以上。除了…

数据结构---C语言栈队列

知识点&#xff1a; 栈&#xff1a; 只允许在一端进行插入或删除操作的线性表&#xff0c;先进后出LIFO 类似一摞书&#xff0c;按顺序拿&#xff0c;先放的书只能最后拿&#xff1b; 顺序栈&#xff1a;栈的顺序存储 typedef struct{Elemtype data[50];int top; }SqStack; SqS…

Window部署Oracle并实现公网环境远程访问本地数据库

文章目录 前言1. 数据库搭建2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射 3. 公网远程访问4. 配置固定TCP端口地址4.1 保留一个固定的公网TCP端口地址4.2 配置固定公网TCP端口地址4.3 测试使用固定TCP端口地址远程Oracle 前言 Oracle&#xff0c;是甲骨文公司的一款关系…

掌握Redis,看完这篇文章就够了!

目录 1.Redis介绍 2.Redis服务器与客户端 3.Redis配置文件 4.Redis数据类型操作 4.1使用python连接数据库 4.2 字符串 4.3 哈希 4.4 键对应操作 4.5 列表 4.6 集合 4.7 有序集合 1.Redis介绍 Redis 是一个开源的内存数据库&#xff0c;它提供了一个高性能的 key-val…

基于PyTorch深度学习实战入门系列-Numpy基础全

Numpy的使用 导入Numpy模块 import numpy as np创建数组&#xff08;一维数组、小数数组、二维数组&#xff09; # 创建一个一维数组 n1 np.array([1, 2, 3]) # 创建一个含有小数的一维数组 n2 np.array([0.1, 0.2, 0.3]) # 创建一个简单的二维数组 n3 np.array([[1, 2], [3…

基于Java+SpringBoot+vue+element实现前后端分离牙科诊所管理系统详细设计

基于JavaSpringBootvueelement实现前后端分离牙科诊所管理系统详细设计 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 ** 作者主页 央顺技术团队** 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式…

【阿里云系列】-基于云效构建部署NodeJS项目到ACK

准备工作 01、编写Dockerfile文件可以根据不同的环境&#xff0c;新建不同的Dockerfile文件&#xff0c;比如Dockerfile-PROD # Deliver the dist folder with NginxFROM nginx:stable-alpine ENV LANGC.UTF-8 ENV TZAsia/ShanghaiCOPY dist/ /usr/share/nginx/html COPY ngi…

字节跳动也启动春季校园招聘了(含二面算法原题)

字节跳动 - 春招启动 随着各个大厂陆续打响春招的响头炮&#xff0c;字节跳动也官宣了春季校园招聘的正式开始。 还是那句话&#xff1a;连互联网大厂启动校招计划尚且争先恐后&#xff0c;你还有什么理由不马上行动&#xff1f;&#xff01; 先来扫一眼「春招流程」和「面向群…

怎么恢复删除的文件?三个实用数据恢复方法分享

在日常工作和生活中&#xff0c;我们经常会遇到误删文件的情况&#xff0c;这可能会带来不小的麻烦。幸运的是&#xff0c;随着科技的发展&#xff0c;现在有很多方法可以帮助我们恢复被删除的文件。下面让我来分享三种常用的文件恢复方法&#xff0c;一起来看看吧&#xff01;…