STL复习-序列式容器和容器适配器部分

STL复习

1. 常见的容器

如何介绍这些容器,分别从常见接口,迭代器类型,底层实现

在这里插入图片描述


序列式容器

string

string严格来说不属于stl,它是属于C++标准库

**底层实现:**string本质是char类型的顺序表,因为不同编译器下的具体实现不同,这里只提供一个我的简答框架

class string
{
public:
    typedef char* iterator;
    typedef const char* const_iterator; 
private:
    char* _str; 		// 堆上开辟的顺序表空间
    size_t _size; 		// 有效字符个数
    size_t _capacity; 	// _str的空间大小

    static const size_t npos; // 最大字符串大小
};

const size_t string::npos = -1;

实际在VS系列下,string是包含一个指针,一个联合体(一个数组和一个指针,如果字符串长度小于16就会使用提前开辟好的数组,如果大于16字节再去堆上申请空间使用指针指向),size和capacity

在g++下string只包含一个指针,指向堆上的一段空间,包含一个指针指向为字符串开辟的空间,引用计数,size和capacity,这个引用计数可以让赋值拷贝这些对象只需要浅拷贝增加引用计数即可

迭代器类型: 随机访问迭代器

常用接口:

函数名功能
size / length返回字符串有效字符个数
clear / reserver / resize清空有效字符 / 预留空间 / 将有效字符的个数该成n个,多出的空间用字符c填充
operator[]返回pos位置的字符
push_back / append / operator+=在字符串后尾插字符c / 字符串 / 字符串
c_str返回C格式字符串
find / rfind + npos从字符串pos位置开始往(后 / 前)找字符c,返回该字符在字符串中的位置,没有返回npos
substr在str中从pos位置开始,截取n个字符,然后将其返回
operator<< / operator>> / getline从iostream中写 / 读 (空格/回车),getline可自定义分隔符
// 剪去报头 length\r\n*******\r\nlength\r
std::string Decode(std::string& str)
{
	// 判断条件是否满足,不满足返回空串
	size_t pos = str.find(SEP);
	if (pos == std::string::npos)
		return "";
	size_t lenght = atoi(str.substr(0, pos).c_str());

	if (lenght > str.size() - pos - SEP_LEN * 2)
		return "";

	str.erase(0, pos + SEP_LEN);
	std::string ret = str.substr(0, lenght);
	str.erase(0, lenght + SEP_LEN);
	return ret;
}
// 简单实现

class string
{
	friend std::ostream& operator<<(std::ostream& out, const string& str);
	//friend istream& operator>>(istream& in, const string& str);
public:
	string() : _str(nullptr), _size(0), _capacity(0)
	{}

	string(const char* str) :string()
	{
		size_t sz = strlen(str);
		_str = new char[sz + 1] {0};
		strcpy(_str, str);
		_size = sz;
		_capacity = sz + 1;
	}

	string(const string& str)
		: string(str.c_str())
	{}

	string& operator==(string str)
	{
		if(this != &str)
			swap(str);
		return *this;
	}

	~string()
	{
		delete[] _str;
	}

	string& operator+=(const string& str)
	{
		size_t sz = str._size;
		if (sz + _size + 1 > _capacity)
		{
			reserve(sz > _capacity ? _capacity + sz : 2 * _capacity);
		}
		strcpy(_str + _size, str.c_str());
		_size += sz;
		return *this;
	}

	char& operator[](size_t pos)
	{
		return *(_str + pos);
	}

	void reserve(size_t newsize)
	{
		if (newsize > _capacity)
		{
			char* str = new char[newsize] {0};
			strcpy(str, _str);
			delete[] _str;
			_str = str;
			_capacity = newsize;
		}
	}

	void swap(string& str)
	{
		std::swap(_str, str._str);
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);
	}

	const char* c_str() const 
	{
		return _str;
	}

	size_t size() const
	{
		return _size;
	}

private:
	char* _str;
	size_t _size;
	size_t _capacity;

	static size_t npos;
};

size_t string::npos = -1;

std::ostream& operator << (std::ostream& out, const string& d)
{
	out << d.c_str();
	return out;
}

vector

底层实现:

vector本质是模板类型的顺序表,对于底层数据结构是顺序表类型的容器在每次增容都需要极大的代价,需要开辟更大空间,并对之前的数据进行拷贝,,vs下capacity是按1.5倍增长的,g++是按2倍增长的

reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解string和vector增容的代价缺陷问题

与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list 统一的迭代器和引用更好

template<class T>
class vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;
private:
	iterator _start = nullptr;
	iterator _finish = nullptr;
	iterator _end_of_storage = nullptr;
};

迭代器类型: 随机访问迭代器

常用接口:

函数名功能
size / capacity / empty数据个数 / 容量大小 / 是否为空
reserve / resize / clear改变vector的capacity / size / 清空
push_back / pop_back尾插 / 尾删
find + v.end() (不常用)查找,注意这个是算法模块实现,不是vector的成员接口
insert / erase在pos位置插入删除
emplace / empalce_back构造和插入
operator[]返回pos位置的对象

深拷贝问题,在拷贝构造时如果对像是自定义类型,就会调用自定义类型的拷贝构造

// 简单实现

template<class T>
class vector
{
	typedef T* iterator;
public:
	iterator begain() { return _start; }
	iterator end() { return _finish; }

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

	vector(size_t n, T val = T()) :vector()
	{
		reserve(n);
		for (size_t i = 0; i < n; ++i)
		{
			_start[i] = val;
		}
		_finish = _start + n;
	}

	vector(const vector& v) :vector()
	{
		reserve(v.size());
		_finish = _start + v.size();
		for (size_t i = 0; i < v.size(); ++i)
		{
			operator[](i) = v[i];
		}
	}

	~vector(){delete[] _start; }

	void push_back(T val) {insert(_finish, val); }

	void pop_back() {erase(_finish - 1); }

	void insert(iterator pos, T val = T())
	{
		assert(pos >= _start && pos <= _finish);
		if (_finish + 1 > _end)
		{
			size_t pos_size = pos - _start;
			reserve(size() + 1);
			pos = _start + pos_size;
		}
		iterator cur = _finish;
		while (cur != pos)
		{
			*cur = *(cur - 1);
			--cur;
		}
		*pos = val;
		_finish += 1;
	}

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

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

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

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

	void reserve(size_t newcapacity)
	{
		size_t capacity = _end - _start;
		size_t size = _finish - _start;
		if (capacity < newcapacity)
		{
			newcapacity = newcapacity < 2 * capacity ? 2 * capacity : newcapacity;
			T* newv = new T[newcapacity];
			if(_start)
				memcpy(newv, _start, size * sizeof(T));
			delete[] _start;
			_start = newv;
			_end = newv + newcapacity;
			_finish = _start + size;
		}
	}

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


private:
	iterator _start;
	iterator _finish;
	iterator _end;
};

list

底层实现:

list的底层是带头双向循环链表结构,链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素

与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好

最大的缺陷是不支持任意位置的随机访问,list还需要一些额外的空间,以保存每个节点的相关联信息

template<class T>
struct list_node
{
	typedef list_node<T> node;
	node* _next;
	node* _prev;
	T _date;
};

template<class T, class Ref, class Ptr>
struct __list_iterator
{
	typedef list_node<T> node;
	typedef __list_iterator<T, Ref, Ptr> self;
	node* _it;
};

template<class T>
class list
{
	typedef list_node<T> node;
public:
	typedef __list_iterator<T, T&, T*> iterator;
	typedef __list_iterator<T, const T&, const T*> const_iterator;
private:
	node* _head;
};

迭代器类型: 双向访问迭代器

常用接口:

函数名功能
empty / size判空 / 返回节点个数
front / back返回最后 / 最前节点值的引用
push_front / pop_front头插 / 头删
push_front / pop_back尾插 / 尾删
insert / erase插入 / 删除
emplace / emplace_front / emplace_back构造 + 插入
sort链表的排序
template<class T>
struct list_node
{
	list_node* _prev;
	list_node* _next;
	T _val;

	list_node(T val = T()) :_prev(nullptr), _next(nullptr), _val(val)
	{}
};

template<class T, class Ref, class Ptr>
struct list_iterator
{
	typedef list_node<T> node;
	typedef list_iterator<T, Ref, Ptr> Self;
	node* _it;

	list_iterator(node* it) :_it(it) 
	{}

	list_iterator(const list_iterator<T, T&, T*>& iterator) :_it(iterator._it)
	{}

	Ptr operator->() {return &(_it->_val);}
	Ref operator*() {return _it->_val; }
	Self operator++() {return _it = _it->_next; }
	Self operator--() {return _it = _it->_prev; }
	bool operator!=(const Self& iterator) const {return _it != iterator._it; }
};

template<class T>
struct list
{
	typedef list_node<T> node;
	typedef list_iterator<T, T&, T*> iterator;
	typedef list_iterator<T, const T&, const T*> const_iterator;
public:
	iterator begin() { return iterator(_head->_next); }
	const_iterator begin() const { return const_iterator(_head->_next); }
	iterator end() { return iterator(_head); }
	const_iterator end() const { return const_iterator(_head); }

	list():_head(new node)
	{
		_head->_next = _head;
		_head->_prev = _head;
	}

	~list()
	{
		while (!empty())
		{
			pop_front();
		}
		delete _head;
	}

	void insert(iterator pos, const T& val)
	{
		node* next = pos._it;
		node* prev = next->_prev;
		node* newnode = new node(val);
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = next;
		next->_prev = newnode;

		_size++;
	}

	void erase(iterator pos)
	{
		assert(pos._it != _head);
		node* next = pos._it->_next;
		node* prev = pos._it->_prev;
		delete pos._it;
		next->_prev = prev;
		prev->_next = next;

		_size--;
	}

	void push_back(const T& val) { insert(end(), val);  }
	void push_front(const T& val) { insert(begin(), val); }

	void pop_back(){erase(_head->_prev); }
	void pop_front(){erase(begin()); }

	bool empty() const
	{
		return _size == 0;
	}
private:
	list_node<T>* _head;
	size_t _size = 0;
};

deque

底层实现:

双端队列由一个指针数组和多个相同长度的定长数组组成

在这里插入图片描述

双端队列,头插尾插,头删尾删效率高,这点比vector要好,但是不如list在任意位置都可以O(1)时间复杂度插入删除(增删)

通过迭代器支持operator[]随机访问,在随机访问上比list要好,但是不如vector(查改)

因为头尾的插入删除效率高,所以他就非常适合stack和queue

迭代器类型: 随机访问迭代器

常用接口:

函数名功能
size / resize / empty大小 / 改变大小 / 判空
operator[] / front / back读数据
push_front / pop_front头插 / 头删
push_front / pop_back尾插 / 尾删
insert / erase插入 / 删除

关联式容器

map/set
unordered_map/unordered_set
bitset

容器适配器

stack

stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。

stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。

stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:

  • empty:判空操作
  • back:获取尾部元素操作
  • push_back:尾部插入元素操作
  • pop_back:尾部删除元素操作

标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器, 默认情况下使用deque

在这里插入图片描述

函数名功能
stack()构造空的栈
empty检测stack是否为空
size返回stack中元素的个数
top返回栈顶元素的引用
push将元素val压入stack中
pop将stack中尾部的元素弹出

queue

队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。

队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。

底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:

  • empty:检测队列是否为空
  • size:返回队列中有效元素的个数
  • front:返回队头元素的引用
  • back:返回队尾元素的引用
  • push_back:在队列尾部入队列
  • pop_front:在队列头部出队列

标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque

在这里插入图片描述

函数名功能
queue()构造空的队列
empty检测queue是否为空
size返回queue中元素的个数
front返回queue顶元素的引用
back返回queue尾元素的引用
push将元素val压入queue中
pop将queue中尾部的元素弹出

priority_queue

优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的

类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)

优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。

底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:

  • empty():检测容器是否为空
  • size():返回容器中有效元素个数
  • front():返回容器中第一个元素的引用
  • push_back():在容器尾部插入元素
  • pop_back():删除容器尾部元素

标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指 定容器类,则使用vector(模板参数第二位)

需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数 make_heap、push_heap和pop_heap 来自动完成此操作

默认情况下priority_queue是大堆,使用less(模板参数第三位)

在这里插入图片描述

一句话,少的点挪动的次数多 时间复杂度约等于 T(N)

在这里插入图片描述

多的点挪动次数多 时间复杂度 T(N*LogN)

若是pop n次用来排序,时间复杂度 T(N*LogN)

优先级队列,使用迭代器建堆,比遍历push,要好

函数名功能
priority_queue()构造一个空的优先级队列
priority_queue(first,last)根据迭代器区间构造优先级队列
empty()检测优先级队列是否为空
top()返回优先级队列中最大(最小元素),即堆顶元素
size()返回优先级队列里的元素个数
push(x)在优先级队列中插入元素x
pop()删除优先级队列中最大(最小)元素,即堆顶元素

杂度 T(N*LogN)

优先级队列,使用迭代器建堆,比遍历push,要好

函数名功能
priority_queue()构造一个空的优先级队列
priority_queue(first,last)根据迭代器区间构造优先级队列
empty()检测优先级队列是否为空
top()返回优先级队列中最大(最小元素),即堆顶元素
size()返回优先级队列里的元素个数
push(x)在优先级队列中插入元素x
pop()删除优先级队列中最大(最小)元素,即堆顶元素

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

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

相关文章

CC2530寄存器编程学习笔记_点灯

下面是我的CC2530的学习笔记之点灯部分。 第一步&#xff1a;分析原理图 找到需要对应操作的硬件 图 1 通过这个图1我们可以找到LED1和LED2连接的引脚&#xff0c;分别是P1_0和P1_1。 第二步 分析原理图 图 2 通过图2 确认P1_0和P1_1引脚连接到LED&#xff0c;并且这些引…

项目/代码规范与Apifox介绍使用

目录 目录 一、项目规范&#xff1a; &#xff08;一&#xff09;项目结构&#xff1a; &#xff08;二&#xff09;传送的数据对象体 二、代码规范&#xff1a; &#xff08;一&#xff09;数据库命名规范&#xff1a; &#xff08;二&#xff09;注释规范&#xff1a; …

【0基础学爬虫】爬虫框架之 feapder 的使用

前言 大数据时代&#xff0c;各行各业对数据采集的需求日益增多&#xff0c;网络爬虫的运用也更为广泛&#xff0c;越来越多的人开始学习网络爬虫这项技术&#xff0c;K哥爬虫此前已经推出不少爬虫进阶、逆向相关文章&#xff0c;为实现从易到难全方位覆盖&#xff0c;特设【0…

mac|Mysql WorkBench 或终端 导入 .sql文件

选择Open SQL Script导入文件 在第一行加入use 你的schema名字&#xff0c;相当于选择了这个schema 点击运行即可将sql文件导入database 看到下面成功了即可 这时候可以看看左侧的目标database中有没有成功导入table&#xff0c;如果没有看到的话&#xff0c;可以点一下右上角的…

Bert入门-使用BERT(transformers库)对推特灾难文本二分类

Kaggle入门竞赛-对推特灾难文本二分类 这个是二月份学习的&#xff0c;最近整理资料所以上传到博客备份一下 数据在这里&#xff1a;https://www.kaggle.com/competitions/nlp-getting-started/data github&#xff08;jupyter notebook&#xff09;&#xff1a;https://gith…

【JavaEE】多线程进阶

&#x1f921;&#x1f921;&#x1f921;个人主页&#x1f921;&#x1f921;&#x1f921; &#x1f921;&#x1f921;&#x1f921;JavaEE专栏&#x1f921;&#x1f921;&#x1f921; 文章目录 1.锁策略1.1悲观锁和乐观锁1.2重量级锁和轻量级锁1.3自旋锁和挂起等待锁1.4可…

AI大模型的智能心脏:向量数据库的崛起

在人工智能的飞速发展中,一个关键技术正悄然成为AI大模型的智能心脏——向量数据库。它不仅是数据存储和管理的革命性工具,更是AI技术突破的核心。随着AI大模型在各个领域的广泛应用,向量数据库的重要性日益凸显。 01 技术突破:向量数据库的内在力量 向量数据库以其快速检索…

TypeError: Cannot read properties of null (reading ‘nextSibling‘)

做项目用的Vue3Vite, 在画静态页面时&#xff0c;点击菜单跳转之后总是出现如下报错&#xff0c;百思不得其解。看了网上很多回答&#xff0c;也没有解决问题&#xff0c;然后试了很多方法&#xff0c;最后竟然发现是template里边没有结构的原因。。。 原来我的index.vue是这样…

自注意力 公式解释

公式 (\mathbf{y}_i f(\mathbf{x}_i, (\mathbf{x}_1, \mathbf{x}_1), \ldots, (\mathbf{x}_n, \mathbf{x}_n)) \in \mathbb{R}^d) 描述了自注意力机制中单个词元的输出表示如何生成。我们来逐步解释这个公式&#xff1a; 输入序列 (\mathbf{x}_1, \mathbf{x}_2, \ldots, \math…

2024吉他手的超级助手Guitar Pro8中文版本发布啦!

亲爱的音乐爱好者们&#xff0c;今天我要来和你们分享一款让我彻底沉迷的软件—Guitar Pro。如果你是一名热爱吉他的朋友&#xff0c;那么接下来的内容你可要瞪大眼睛仔细看哦&#xff01;&#x1f440;&#x1f3b6; Guitar Pro免费绿色永久安装包下载&#xff1a;&#xff0…

《昇思 25 天学习打卡营第 11 天 | ResNet50 图像分类 》

《昇思 25 天学习打卡营第 11 天 | ResNet50 图像分类 》 活动地址&#xff1a;https://xihe.mindspore.cn/events/mindspore-training-camp 签名&#xff1a;Sam9029 计算机视觉-图像分类&#xff0c;很感兴趣 且今日精神颇佳&#xff0c;一个字&#xff0c;学啊 上一节&…

【数据结构】经典链表题目详解集合(反转链表、相交链表、链表的中间节点、回文链表)

文章目录 一、反转链表1、程序详解2、代码 二、相交链表1、程序详解2、代码 三、链表的中间节点1、程序详解2、代码 四、回文链表1、程序详解2、代码 一、反转链表 1、程序详解 题目&#xff1a;给定单链表的头节点 head &#xff0c;请反转链表&#xff0c;并返回反转后的链…

STM32实现硬件IIC通信(HAL库)

文章目录 一. 前言二. 关于IIC通信三. IIC通信过程四. STM32实现硬件IIC通信五. 关于硬件IIC的Bug 一. 前言 最近正在DIY一款智能电池&#xff0c;需要使用STM32F030F4P6和TI的电池管理芯片BQ40Z50进行SMBUS通信。SMBUS本质上就是IIC通信&#xff0c;项目用到STM32CubeMXHAL库…

基于ROS的智能网联车远程交互软件,全UI无需记忆指令,剑指核心原理。

基于ROS的智能网联车远程交互软件&#xff0c;全UI无需记忆指令&#xff0c;剑指核心原理。 服务于中汽恒泰&#xff0c;伟大的项目&#xff0c;希望看官点赞&#xff0c;谢谢~~ 进程&#xff08;节点&#xff09;列表化&#xff0c;参数面板化&#xff0c;实现快速机器人配置…

SpringMVC(2)——controller方法参数与html表单对应

controller方法参数与html表单对应 0. User实体类 import org.springframework.format.annotation.DateTimeFormat;import java.io.Serializable; import java.util.Date; import java.util.List; import java.util.Map;public class User implements Serializable {private …

CosyVoice - 阿里最新开源语音克隆、文本转语音项目 支持情感控制及粤语 本地一键整合包下载

近日&#xff0c;阿里通义实验室发布开源语音大模型项目FunAudioLLM&#xff0c;而且一次包含两个模型&#xff1a;SenseVoice和CosyVoice。 CosyVoice专注自然语音生成&#xff0c;支持多语言、音色和情感控制&#xff0c;支持中英日粤韩5种语言的生成&#xff0c;效果显著优于…

apk反编译修改教程系列-----修改apk 解除软件限制功能 实例操作步骤解析_3【二十二】

在前面的几期博文中有过解析去除apk中功能权限的反编译步骤。另外在以往博文中也列举了修改apk中选项功能权限的操作方法。今天以另外一款apk作为演示修改反编译去除软件功能限制的步骤。兴趣的友友可以参考其中的修改过程。 课程的目的是了解apk中各个文件的具体作用以及简单…

JavaWeb—Servlet

概述 Javaweb的核心就是围绕servlet Servlet就是一个接口&#xff0c; 定义了java类 被浏览器访问到&#xff08;tomcat识别&#xff09;的接口 将来就是自己写一个类 &#xff0c;实现servlet接口 &#xff0c;重写方法 执行过程 当服务器接收到客户端浏览器的请求后&#xff…

【机器学习】机器学习与时间序列分析的融合应用与性能优化新探索

文章目录 引言第一章&#xff1a;机器学习在时间序列分析中的应用1.1 数据预处理1.1.1 数据清洗1.1.2 数据归一化1.1.3 数据增强 1.2 模型选择1.2.1 自回归模型1.2.2 移动平均模型1.2.3 长短期记忆网络1.2.4 卷积神经网络 1.3 模型训练1.3.1 梯度下降1.3.2 随机梯度下降1.3.3 A…

C# 编程中互斥锁的使用

C# 中的互斥锁 互斥锁是 C# 中使用的同步原语&#xff0c;用于控制多个线程或进程对共享资源的访问。其目的是确保在任何给定时间只有一个线程或进程可以获取互斥锁&#xff0c;从而提供互斥。 C# 中互斥锁的优点 可以使用互斥锁 (Mutex) 并享受其带来的好处。 1. 共享资源…