C++11 智能指针

news2024/11/19 17:28:31

文章目录

    • 1. 智能指针出现的意义
      • 1.1 内存泄漏
      • 1.2 智能指针初识
    • 2. C++标准库中的智能指针
      • 2.1 auto_ptr
      • 2.2 std::unique_ptr
      • 2.3 std::shared_ptr
      • 2.4 std::weak_ptr
    • 3. 智能指针中的定制删除

前言: 智能指针,它是指针嘛?它是一个类具有指针的功能,我去,那不是还有一个迭代器嘛,迭代器不就是一个类具有指针的功能。注意这俩可不敢混淆。迭代器是自定义对象的指针,可以这么理解,迭代器的出现使得自定义对象,也可以像内置类型一般进行指针操作。那么智能指针的出现,又有什么意义呢?


1. 智能指针出现的意义

1.1 内存泄漏

内存泄漏可以分为两类:

  1. 堆空间上的内存泄漏:
    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用。
  2. 系统资源中的内存泄漏 :
    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

智能指针可以解决内存泄漏的问题,它相对于一种预防手段。因为C++没有回收机制嘛,所以内存泄漏的问题,解决起来十分困难。

比如:你虽然严格按照 new delete ,malloc free 这样写代码。但是 如果程序中途抛异常 有可能就会跳过 你写的 delete 或是 free ,这很难受。造成了内存泄漏。

怎么说呢,内存泄漏是很危险的,尤其是那种长时间运行的程序。一旦出现内存泄漏,会导致程序越来越卡,甚至导致服务器 宕机。所以 程序员再处理内存,指针之类的 都格外小心。有没有一种机制,可以帮助我们 减轻些负担呢?那就是智能指针

1.2 智能指针初识

智能指针利用的是,RALL技术:利用对象的生命周期来控制程序资源。

简单来说:构造类对象,会自动调用构造函数;对象 销毁时,会自动调用对象的析构函数。利用类对象这一特性,就不需要我们手动的释放内存空间。

对于这个大家基本上都懂,但是我也用代码演示一下:

#include<iostream>
using namespace std;

class A
{
private:
	int _a;
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "构造:A()" << endl;
	}

	~A()
	{
		cout << "析构:~A()" << endl;
	}
};

int main()
{
	A a;

	return 0;
}

构造一个A类对象a,我们来看程序运行结果:

在这里插入图片描述

嗯,那么我们来实现一个简易版本的智能指针:

template<class T>
class SmartPtr
{
private:
	T* _ptr;
public:
	SmartPtr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		if (_ptr)
		{
			delete _ptr;
			cout << "delete _ptr" << endl;
		}
	}

	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
};

智能指针原理总结:

  1. RAII特性
  2. 重载operator*和opertaor->,具有像指针一样的行为。

我们来简单的使用一下,上面的智能指针:

    int* iptr = new int(2);
	SmartPtr<int> sptr(iptr);

	*sptr = 10;

来看程序的运行:

在这里插入图片描述

可以看到,是自动释放new 出来的 空间的。


2. C++标准库中的智能指针

C++11之前是有一个:

  • auto_ptr

但由于被喷的惨,所以基本没人用。

C++11 更新后,新给出了三类智能指针:

  1. std::unique_ptr
  2. std::shared_ptr
  3. std::weak_ptr

下面我会一 一 介绍,

2.1 auto_ptr

我们上面不是写过一个简易版本的智能指针,大家可以再看一下。会发现我没有写拷贝构造和赋值重载。其实智能指针,难点就是这俩。我先用上面的简易智能指针去完成一下拷贝,看看会出现什么问题。

	SmartPtr<int> sptr(new int(1));
	SmartPtr<int>sptr1(sptr);

在这里插入图片描述
抛异常了,我们试着,捕获一下:

   try 
	{
		SmartPtr<int> sptr(new int(1));
		SmartPtr<int>sptr1(sptr);
	}

	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

这样不能捕获,因为这个异常抛的是我们自定义类型的,所以不好搞。

我直接说原因吧,sptr 赋值给 sptr1 默认的拷贝构造是 浅拷贝,所以导致同一个块资源被释放了 两次。

怎么解决这个问题呢?有多种方式,就这个问题衍生出的多类的智能指针。

auto_ptr是这样解决的:管理权转移的思想,也就是说,这一块资源我交给要拷贝我的人来管理,我自己呢撒手掌柜,不管了。

这是感性的理解,还是代码实现一下:

    template<class T>
	class auto_ptr
	{
	private:
		T* _ptr;
	public:

		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		~auto_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
			}
		}

		auto_ptr(auto_ptr<T>& tmp)
			:_ptr(tmp._ptr)
		{
			tmp._ptr = nullptr;
		}

		auto_ptr& operator=(auto_ptr<T> &tmp)
		{
		    if (_ptr)
			{
				delete _ptr;
			}
			_ptr = tmp._ptr;
			tmp._ptr = nullptr;
			return *this;
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	};

关键是这一步:

在这里插入图片描述

给段代码,通过调试帮助大家理解:

    ly::auto_ptr<int> aptr(new int(1));

	ly::auto_ptr<int> aptr1(aptr);

	ly::auto_ptr<int> aptr2(new int(2));

	aptr2 = aptr1;

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

但是有一个明显的缺陷,那就是拷贝构造,完成了权限转移,直接把我原来管理的资源置为空。虽然我不再管理了,直接把置空了,那么我很难受。我虽然没有权力去释放这块资源,但是 我连访问都成问题了。这样做是不是有点太绝了。

比如:

    ly::auto_ptr<int> aptr(new int(1));

	ly::auto_ptr<int> aptr1(aptr);

	cout << *aptr << endl;

现在就变成了堆空指针的解引用,必然抛异常。所以auto_ptr 这样的做法有点太 一刀两断了。我被别人拷贝了,把管理权交出去了,没有权利去释放资源,但是不能直接把我置空,导致我 无法访问资源。

综上:auto_ptr 用的少,但是 前人踩坑,后人才能避坑。


2.2 std::unique_ptr

怎么说呢,unique_ptr更加暴力,直接就是不允许发生智能指针的拷贝和赋值。呵呵,很强势,当然很简单,我直接实现一下:

   template<class T>
	class unique_ptr
	{
	private:
		T* _ptr;
	public:

		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				delete _ptr;
			}
		}

		unique_ptr(unique_ptr<T>& tmp) = delete;

		unique_ptr& operator=(unique_ptr<T>& tmp) = delete;

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	};
}

就是将 拷贝构造和赋值重载 delete 关键字修饰一下。

这个好理解对吧。不过多赘述了哈。要提一点就是:

在这里插入图片描述
库里面有第二个模板参数,这个模板参数是指定删除。放后面讲。


2.3 std::shared_ptr

这才是 真正意义上支持拷贝和赋值额 智能指针。利用的是引用计数的思想,也就是说 类中有用于保存 指向此块资源对象的个数,等指向此资源的对象只剩下一个时,如果要析构才会释放资源,其余情况 都是 指向资源个数 减一。

图解:

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

简易实现:

我们先来实现一个简易版本的,不考虑线程安全问题,那么想让 所有对象 共用一份 count 计数,有几种方式呢?

我给出两种:

  • 将引用计数设置为静态成员变量
  • 使用指针,引用计数 是堆上 开辟的,所有对象都可以通过指针,来访问同一块堆上空间

那么 简易点就是 第一种方式嘛:

template<class T>
	class shared_ptr
	{
	public:
		static int _count;
		T* _ptr;
	public:
		shared_ptr(T* ptr =nullptr)
			:_ptr(ptr)
		{
			_count = 1;
		}

		~shared_ptr()
		{
			if (--_count == 0 && _ptr)
			{
				delete _ptr;
			}
		}

		shared_ptr(shared_ptr& tmp)
		{
			// 防止自己赋值给自己
			if (_ptr != tmp._ptr)
			{
				_ptr = tmp._ptr;
				_count = tmp._count;
				_count++;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

	};
    template<class T>
	int shared_ptr<T>::_count = 0;

这就是支持拷贝构造,不考虑线程安全的版本,很简单哈。


其实线程安全问题的解决无非就是 加个锁。 多个线程 对 count进行 ++ - - 操作,这里是线程不安全的,所以 对count 操作的地方,都需要加锁。

template<class T>
	class shared_ptr
	{
	private:
		T* _ptr;
		int* _count;
		mutex* _mutex;
	public:
		shared_ptr(T* ptr=nullptr)
			:_ptr(ptr),
			_count(new int(1)),
			_mutex(new mutex) 
		{
		}

		~shared_ptr()
		{
			release();
		}

		shared_ptr(shared_ptr& tmp)
		{
			if (_ptr != tmp._ptr)
			{
				_ptr = tmp._ptr;
				_count = tmp._count;
				_mutex = tmp._mutex;

				tmp.addcount();
			}
		}

		
		shared_ptr<T>& operator= (shared_ptr& tmp)
		{
			if (_ptr != tmp._ptr)
			{
				release();

				_ptr = tmp._ptr;
				_count = tmp._count;
				_mutex = tmp._mutex;

				tmp.addcount();
			}

			return *this;
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
        
		T* get()
		{
			return _ptr;
		}

		int get_count()
		{
			return *_count;
		}

		void release()
		{
			_mutex->lock();
			bool flag = false;
			if (--(*_count) == 0&&_ptr)
			{
				delete _ptr;
				delete _count;
				flag = true;
			}
			
			_mutex->unlock();

			if (flag == true)
			{
				delete _mutex;
			}
		}

		void addcount()
		{
			_mutex->lock();
			(*_count)++;
			_mutex->unlock();
		}
	};

测试代码:

        shared_ptr<int> sptr(new int(1));
	    shared_ptr<int> sptr1(sptr);
	    shared_ptr<int> sptr2(new int(2));
     	sptr2 = sptr1;

shared_ptr 其实还有一个问题需要格外注意循环引用。循环引用会导致,本该释放的资源,得不到释放,也就是说 count 加多了,这解释有点牵强,大家一会看图理解:

首先给出一个例子,链表的节点:

在这里插入图片描述
节点里面的指针,我们可以用智能指针嘛?试着用一下,因为 智能指针 还是有好处的,它可以预防 内存泄漏 对吧,但是这里会出现 循环引用的问题:

struct  Node
{
	int val;
	ly::shared_ptr<Node> _next;
	ly::shared_ptr<Node> _prev;

	~Node()
	{
		cout << "~Node" << endl;
	}
};

假如我这样使用节点:

    shared_ptr<Node> n1(new Node);
	shared_ptr<Node> n2(new Node);
	cout<<n1.get_count()<<endl;
	cout << n2.get_count() << endl;

看结果,是对的:

在这里插入图片描述

假如我让它俩互相指向呢?

    shared_ptr<Node> n1(new Node);
	shared_ptr<Node> n2(new Node);

	cout<<n1.get_count()<<endl;
	cout << n2.get_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;
	cout << n1.get_count() << endl;
	cout << n2.get_count() << endl;

在这里插入图片描述

发现尽然没有析构,没释放资源。程序都退出了,这就是内存泄漏。

造成这个的原因,我们来分析一下:

刚开始 没问题:

在这里插入图片描述
但是由于n1->_next = n2; n2->_prev = n1; 所以 count ++了:

在这里插入图片描述
如果进行析构,那么 就是 count – ,它减完之后,变为1 ,所以 不会进行 资源释放。

其实问题已经分析出来了,count 如果不 ++ 那么 资源还能够释放,因为互相指向,所以它俩的count 都 ++了。那么有没有解决办法呢?那就是 std::weak_ptr ,它呢,就是 不参与 资源管理,虽然指向了某块资源,但是 count 不会 ++。


2.4 std::weak_ptr

weak_ptr 其实就是 专门用于 解决 shared_ptr 中循环指向的问题的。

废话不多说,直接就是 将节点中的 shared_ptr 换成 weak_ptr 就可以了:

在这呢 先使用 标准库中的 shared_ptr 和 weak_ptr ,之后再模拟实现weak_ptr,

struct  Node
{
	int val;
	std::weak_ptr<Node> _next;
	std::weak_ptr<Node> _prev;

	~Node()
	{
		cout << "~Node" << endl;
	}
};

int main()
{
    std::shared_ptr<Node> n1(new Node);
	std::shared_ptr<Node> n2(new Node);

	cout<<n1.use_count()<<endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;
	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

 return 0;
}

看运行结果,很明显完成了资源释放:

在这里插入图片描述
而且 发现 count的值 没有变成 2,原因很简单 weak_ptr 智能指针不参与 资源管理。

那么 我们来模拟实现一下,weak_ptr ,很简单:

template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();

			return *this;
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}


	private:
		T* _ptr;
	};

对吧,就是不让它参与资源管理就行,唯一需要注意的就是它的拷贝构造,可以是shared_ptr 也可以是 weak_ptr 。

在这里插入图片描述


3. 智能指针中的定制删除

像上面所有的智能指针模拟实现都是 new 和delete ;但是 还有别的情况 比如 delete [] , free ,fclose() 。对吧,所以呢 智能指针 提供了 定制删除,默认情况下是 delete 。

在这里插入图片描述

看 默认参数D 是 default_delete< T >:

在这里插入图片描述
看到了吧,这是啥?仿函数呀,昂,可以 。我们一会 来模拟实现一下 free 版本的

但是 shared_ptr 中 定制删除 不是给的模板参数,而是 在构造函数重载中的一个:

在这里插入图片描述

所以定制删除在智能指针中的使用,要自己去标准库中查看,但定制删除一般都是是仿函数。区别就是 模板参数 给的是类型,构造函数中 给的是 对象。

但是 定制删除 如果是在 sharde_ptr中给的不就是个对象嘛,所以也可以给 lambda表达式。


所以 我们先来 给出 两个定制删除器:

  1. delete []
template<class T>
struct DeleteArray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
	}
};
  1. fclose()
struct DeleteFile
{
	void operator()(FILE* ptr)
	{
		fclose(ptr);
	}
};

使用起来也很简单:

std::unique_ptr<A, DeleteArray<A>> up2(new A[10]);
std::unique_ptr<FILE, DeleteFile> up3(fopen("test.txt", "w"));
std::shared_ptr<A> sp2(new A[10], DeleteArray<A>());
std::shared_ptr<FILE> sp3(fopen("test.txt", "w"), DeleteFile());

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

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

相关文章

HTML期末学生大作业-拯救宠物网页作业html+css

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

Redis主从复制与Cluster集群

目录 一.Redis集群模式 二、Redis 主从复制 2.1 主从复制介绍 2.2 主从复制的作用 2.3 主从复制流程&#xff1a; 三. Redis主从复制搭建 3.1 实验步骤 ​ 3.2 修改master节点配置文件 3.3 修改slave节点的配置文件 3.4 验证主从效果 3.4.1 主节点查看日志&#xff0…

数据图表-FineReport复选按钮控件

1. 概述 1.1 版本 报表服务器版本 功能变更 11.0 -- 1.2 应用场景 1.2.1 填报控件 填报报表中可以通过该控件执行批量选中操作&#xff0c;如下图所示&#xff1a; 1.2.2 参数控件 参数面板处可以通过该控件执行查询操作&#xff0c;如下图所示&#xff1a; 1.3 如何添加…

APP上架需要的准备和流程

一上架iOS应用市场前的准备​ 1.选择适合自己的苹果开发者账号​ (1) 个人账号&#xff08;Individual&#xff09;&#xff1a;费用99美金一年, 该账号在App Store销售者只能显示个人的ID&#xff0c;比如zhitian zhang&#xff0c;单人使用。个人账号只能有一个开发者。100…

护眼灯护眼有效果吗?一文了解护眼灯到底有没有用

关于护眼灯是否对预防近视、保护眼睛有效&#xff0c;很多人都会有怀疑态度&#xff0c;会认为是智商税&#xff0c;其实这也因人而异&#xff0c;有的人眼睛天生不太敏感&#xff0c;所以对护眼灯的光线感知不强&#xff0c;很难体会到实际的作用&#xff1b;有的人也买到较差…

ccf寻宝!大冒险!python满分(敲开心~)

寻宝 大冒险 题目传送&#xff1a;http://118.190.20.162/view.page?gpidT147 思路及代码&#xff1a; 核心点就是哈希。 之前刷的是70分&#xff0c;找不到之前的代码了&#xff0c;大概是建了一个很大的表&#xff0c;然后一点点比较吧。 今天再刷&#xff0c;上来就是…

安装ADDIS

网状meta分析&#xff0c;安装ADDIS 下载ADDIS、java 百度网盘下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/15tqDxXUWZ2q3qOS2Fglvu 提取码&#xff1a;6fyw 配置java环境变量 双击系统变量中的Path 复制百度网盘下载后的jdk-11/bin的路径&#xff0c…

Apereo-cas 4.x反序列化漏洞复现

转载https://www.freebuf.com/vuls/226149.html 建议再查看https://xz.aliyun.com/t/7032#toc-7的 0x01 前言放假前看到很多文章对这个漏洞进行分析复现&#xff0c;又因为过年期间的特殊情况&#xff0c;实在是无聊至极&#xff0c;所以自己也来学习一下&#xff0c;顺便恶补一…

OceanBase-概述

文章目录背景产品家族分布式数据库社区版&#xff08;当前为V4.0&#xff09;企业版公有云总结核心功能适用场景工具体系迁移评估工具OMA迁移工具OMS开发工具ODC权限管理运维工具OCP生态产品图数据库时序数据库背景 前段时间学习了下tidb的体系架构&#xff0c;了解了其运行原…

vulhub中间件解析漏洞复现

vulhub中间件解析漏洞复现 &#x1f349; shell 此环境来自vulhub工程&#xff1a; https://github.com/vulhub/vulhub 以下测试环境为ubuntu 20.04 &#x1f349;目录vulhub中间件解析漏洞复现Apache HTTPD 换行解析漏洞&#xff08;CVE-2017-15715&#xff09;漏洞原理及…

Redis--高级篇 D4 Redis分片集群

1、分片集群的结构 2、搭建分片集群 2.1 分片集群结构 分片集群需要的节点数量较多&#xff0c;这里我们搭建一个最小的分片集群&#xff0c;包含3个master节点&#xff0c;每个master包含一个slave节点&#xff0c;结构如下&#xff1a; 这里我们会在同一台虚拟机中开启6个r…

Java多态详解

前言 多态是同一个行为具有多个不同的表现形态或形式的能力 比如&#xff1a; 小阿giao&#xff0c;他是一名主播&#xff0c;同样也是一个人&#xff1b; 小阿giao是一个对象&#xff1b; 这个对象既有主播形态&#xff0c;也有人类形态&#xff1b; 即&#xff1a;一个对象…

醛基-PEG-纤维二糖 Cellobiose-CHO 纤维二糖-醛基

醛基-PEG-纤维二糖 Cellobiose-CHO 纤维二糖-醛基 羰基中的一个共价键跟氢原子相连而组成的一价原子团&#xff0c;叫做醛基&#xff0c;醛基结构简式是-CHO&#xff0c;醛基是亲水基团&#xff0c;因此有醛基的有机物&#xff08;如乙醛等&#xff09;有一定的水溶性。 中…

Unity有哪些适合拿来练手的游戏项目?

首先推荐一篇文章&#xff0c;其中涵盖海量Unity和C#的书籍、demo等海量干货。 文章链接&#xff1a;Unity 高级程序员应该具备怎样的能力&#xff1f;要怎样成长为 Unity 高级程序员&#xff1f; Unity有哪些适合练手的游戏项目&#xff1f;还是老规矩&#xff0c;全文无付费…

【软件STM32cubeIDE下STM32F4xx使用DMA+定时器+推PWM+点亮灯带WS2812相关-进阶(全数据流版)】

2TOC &#xff08;1&#xff09;前言 &#xff08;1&#xff09;起源 最近作了有关ws2812灯带的后续测试&#xff0c;这源于一个简单的问题。 如果用STM32F4点灯带&#xff0c;每个DMA数据流都用上&#xff0c;那么可以点亮多少条灯带&#xff1f; 实际情况更加复杂些&#x…

JAVA EE,一文彻底聊明白

关于JAVA EE的论述&#xff0c;JAVA EE和Spring的论述在第二、三章节。 目录 1.JAVA的发展史 2.JAVA EE 3.JAVA EE和Spring 1.JAVA的发展史 JAVA语言于1995年面世&#xff0c;主要开发者为——James Gosline&#xff0c;后被称为JAVA语言之父。最早该语言叫Oak&#xff0c;…

写给Java应用开发看的Elasticsearch调优手册

随着数据量的增长&#xff0c;MySQL在很多场景下&#xff0c;似乎已经无能为力了&#xff0c;所以有不少应用的数据从MySQL迁移到了Elasticsearch&#xff0c;我们的应用就在其中。由于开发时间紧张&#xff0c;所以仅仅只是了解了下Elasticsearch的基本知识&#xff0c;随便写…

[HDF5]如何使用CMake一起编译自己的代码和HDF5库

以一个实际项目来举例 目录 一.目录结构 二.CMakeLists.txt内容 三.执行CMakeLists命令 一.目录结构 1.build放CMake执行后生成的所有的内容&#xff0c;包括最后的工程或可执行文件等; 2.include放工程的头文件&#xff0c;以及HDF5的头文件&#xff0c;可以直接把HDF5中…

顶象业务安全情报——企业一眼“看透”黑灰产

不可否认&#xff0c;随着互联网的发展&#xff0c;黑灰产们也变得越来越“聪明”。它们不仅熟悉业务流程以及防护逻辑&#xff0c;并且能够熟练运用自动化、智能化的新兴技术实施攻击或诈骗行为&#xff0c;且极难识别。对购物、金融、社交、出行、教育、游戏等行业造成极大业…

3 个月前被裁员了,心情跌落谷底,直到我看到了这本神书…

3个月前的某一天&#xff0c;正在愉快的打工&#xff0c;突然被喊去谈话&#xff0c;然后就被辞退了。。 加入了找工作的大军 然而&#xff0c;因为疫情&#xff0c;因为大专学历的我&#xff0c;找工作比以往都艰难了许多 很多&#xff0c;纯粹就是因为学历&#xff0c;都不…