C++-----stack和queue

本期我们来学习stack和queue

目录

stack介绍

栈的使用

栈的模拟实现

queue介绍

队列的使用 

 队列的模拟实现

deque

优先级队列

 模拟实现

仿函数

全部代码


stack介绍

1. stack 是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
2. stack 是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部( 即栈顶 ) 被压入和弹出。
3. stack 的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:
empty :判空操作
back :获取尾部元素操作
push_back :尾部插入元素操作
pop_back :尾部删除元素操作
4. 标准容器 vector deque list 均符合这些需求,默认情况下,如果没有为 stack 指定特定的底层容器,默认情况下使用deque

 我们发现,stack模板参数和我们之前的容器是不同的,我们现在只要知道,我们的栈和队列,是容器适配器,是用容器转换出来的,适配器的本质是一种复用

栈的使用

 这些接口根据我们目前的水平是可以轻松看懂的

并且我们发现,它是没有迭代器的,不支持我们随便进行遍历的,因为要保持后进先出,队列也是一样的

 

 使用起来是非常简单的

栈的模拟实现

我们前面说过适配,我们来完成一个链式栈和数组栈秒切换

namespace bai
{
	template<class T,class Container>
	class stack
	{
	public:
		void push(const T& x)
		{

		}
	private:
		Container _con;
	};
	void test() {
		stack<int, vector<int>> st1;
		stack<int, list<int>> st2;

	}
}

我们加一个模板参数就可以了

        void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_back();
		}
		T& top()
		{
			return _con.back();
		}
		size_t size()
		{
			return _con.size();
		}
		bool empty()
		{
			return _con.empty();
		}

这些接口我们全部复用即可,这就是适配器,非常方便

其中top我们使用back接口

list和vector都有back和front接口,我们直接使用

容器适配器就是,数据是容器管理,我们对容器进行封装,管理,改造等等

测试一下,也没有问题

不过此时我们还有一个问题,我们每次都要传两个模板参数,非常麻烦,库里面是不需要的

 它给的是类型,给了一个deque,deque我们后面解释

我们先加一个缺省参数,给一个vector即可

此时不写默认就是vector的栈

 另外,我们的栈是不需要构造函数,拷贝构造这些,因为我们是自定义类型,回去调用自己的构造,拷贝构造等等

queue介绍

1. 队列是一种容器适配器,专门用于在 FIFO 上下文 ( 先进先出 ) 中操作,其中从容器一端插入元素,另一端提取元素。
2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类, queue 提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
empty :检测队列是否为空
size :返回队列中有效元素的个数
front :返回队头元素的引用
back :返回队尾元素的引用
push_back :在队列尾部入队列
pop_front :在队列头部出队列
4. 标准容器类 deque list 满足了这些要求。默认情况下,如果没有为 queue 实例化指定容器类,则使用标准容器deque

队列的使用 

同样的,这些接口都很简单,这里就不再演示

 队列的模拟实现

队列用vector适配是不好的,但我们也可以强制适配

vector是没有提供头删的

不过我们可以使用erase来强制适配

        void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			//_con.pop_front();
			_con.erase(_con.begin());
		}
		T& front()
		{
			return _con.front();
		}
		T& back()
		{
			return _con.back();
		}
		size_t size()
		{
			return _con.size();
		}
		bool empty()
		{
			return _con.empty();
		}

修改一下top,为front,再加一个back接口即可

测试一下也没有问题 

但是这样强制适配是不好的

库里面是支持list,但不支持vector的 

因为顺序表的头删效率是很低的

所以queue的默认容器我们使用list 

deque

deque是一个双端队列,不过它虽然看起来是队列,但其实不是

队列的特点是先进先出,但deque不是,它是一个双向开口的,两边都可以插入删除,而且他是一个随机迭代器

它就像一个vector和list合集的六边形战士,可以像数组一样访问,还有头插头删,尾插尾删

 

我们简单用一下 

那deque这么强,为什么我们之前没怎么听说过呢?为什么不用它代替vector和list呢?

其实只看头插头删,尾插尾删这些,deque还是可以的,但论综合性,是无法代替vector和list的

这是100w数据量下,第一个deque是将数据拷贝到vector里,然后进行排序,再拷贝回来,第二个是直接对deque进行排序,差距是很明显的 

deque其实是一个很一般的设计,这也想要,那也想要,最后啥也干不了

vector直接下标随机访问,但是扩容,中间以为头部的插入删除效率不高,list则是支持任意位置插入删除,但不支持下标随机访问,是按需要求申请释放的

vector是连续的,list的一个一个的节点,所以就有人想,我们开辟一段一段的空间,然后用一个中控数组指向他们(这个中控数组不是从头开始指向的), 中控数组就是一个指针数组,这样一看感觉和vector<vector<int>>差不多,但其实差距是很大的,vector指向的vector是可以扩容的,每一段的大小都可以不一样,但是中控数组指向的空间是一样大的

指针数组满了,要进行扩容,不过扩容的代价是非常底的,它指向的空间是不用动的,只需拷贝指针,不需要拷贝原数据,我们上面说,中控数组是从中间开始指向的,所以头插时,是可以从前面的空间插入的

就像这样,而且它的插入是倒着往前走的 ,先插入的-1,再插入的-2

相比vector,它缓解了扩容的问题,头插头删问题,但是 [ ] 不够极致,[ ] 要去计算在哪个buff(指向的数组)的第几个

 它要先计算在不在第一个buff,第一个buff不是满的,不是从零开始的,第一个buff是倒着往前走的,如果在的话就找位置访问,如果不在,i -=第一个buff数组的size,第几个buff = i / buff,在这个buff的第几个 = i % buffsize

所以是不够极致的,vector是直接指针解引用访问就行了,而这里是需要计算的,这也是为什么上面效率比对时差距明显的原因了,在大量访问时,这个计算是很麻烦的

相比list,它支持下标随机访问,cpu高速缓存效率不错,头尾插入删除不错,但是中间的插入删除就不好了,中间插入的话一个是挪动数据,这个效率太低,另一个就是扩容,扩容中间这个buff,是不好的,库里面也没有这么做,这样做会影响上面的计算,会使[ ]的效率进一步下降

只有高频的头尾的插入删除,才适合deque,所以deque就用来适配栈和队列的默认容器

deque的底层是非常复杂的

底层是一段假象的连续空间,实际是分段连续的,为了维护其 整体连续 以及随机访问的假象,落 在了 deque 的迭代器身上 因此 deque 的迭代器设计就比较复杂,迭代器就封装了四个指针,如下图所示

 我们再看一张图

start就相当于begin,finish就相当于end,要进行一遍迭代器遍历,把begin给it,然后解引用,解引用返回*cur 

这是源码 ,我们再看看++,有两个操作,first和last是指向这段数组的起始和末尾,cur!=last 说明没有走完,++cur即可,如果走完了,迭代器要指向下一个buff,里面的node就要++,指向下一个buff的地址,再解引用就可以指向下一个buff,然后再让first和last指向这个buff,cur再指向这个buff,我们再看什么适合结束,当it和end在同一个buff时,cur还没走完,当cur等于end的cur时就结束

 

 

 

 deque我们稍微看看就行

优先级队列

 优先级队列和deque一样,都不能算队列,因为队列是先进先出的,这两个只是占了个名

我们看到它也是容器适配器,默认使用了vector

这些接口也都很简单

 优先级队列底层就是二叉树的堆,默认大的优先级高

默认是大堆,我们仔细看默认容器适配器传的是小于(第三个模板参数),但默认又是大堆 ,是反过来的

传一个greater才能变为小堆 ,而默认的less是大堆,是相反的,大家要牢记

 不一样的地方是构造函数给了迭代器区间构造,可以给一个区间,用这些值构造堆

这里还可以换成deque,不过是不太好的,因为deque的随机访问要计算的,不如vector

 模拟实现

下面我们模拟实现一下优先级队列

namespace bai {
	template<class T,class Container = vector<T>>
	class priority_queue
	{
	private:
		void AdjustDown(int parent)
		{
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && _con[child + 1] > _con[child])
				{
					++child;
				}
				if (_con[child] > _con[parent])
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else 
				{
					break;
				}
			}
		}
		void AdjustUp(int child)
		{
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_con[child] > _con[parent])
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}	
		}
	public:
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				_con.push_back(*first);
				++first;
			}
			//建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--) 
			{
				AdjustDown(i);
			}
		}
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);
		}
		void push(const T& x)
		{
			_con.push_back(x);
			AdjustUp(_con.size() - 1);
		}
		const T& top()
		{
			return _con[0];
		}
		bool empty()
		{
			return _con.empty();
		}
		size_t size()
		{
			return _con.size();
		}
	private:
		Container _con;
	};
}

这里逻辑其实都不难,用到了以前我们堆的知识,如果对堆还不够理解的同学可以看我之前对堆的详解

(20条消息) 堆及其多种接口与堆排序的实现_KLZUQ的博客-CSDN博客

我们测试一下,没有问题 

仿函数

仿函数,也叫做函数对象

如果不看其他的,只看cout输出这里,我们会认为lessfunc是一个函数

实际上这个类重载了括号,本质等价于lessfunc.operator()

本质是把这个类的对象像函数一样使用,作用是替代C语言里的函数指针

 我们将它变为类模板,使用范围就变的很广阔了 

那仿函数到底是干什么的呢?

像我们这样的比较,是写死的 ,我们可以用函数指针来让它变得灵活,但C++不使用函数指针,因为函数指针的可读性很差,所以就开发出了仿函数

 

我们把这些先全改为<的比较,然后使用com来进行控制 

这样我们的比较就变得灵活了,传入less或者greater,就可以切换大于和小于的比较,从而调整我们是要建大堆,还是建小堆 ,我们可以根据需求写出自己的less和greater,对各种类型都可以进行比较

简单来说,仿函数就是通过模板参数,调用比较类型,调用operator(),operator()是我们自己实现的,或者我们用库里面的less或者greater也可以

 我们把之前写过的Date类拿过来,都进行排序

有些场景下我们需要自己写仿函数

 

这个情况非常诡异,每一次运行结果都不一样 

因为我们的类型是一个指针,new出来的地址大小是不确定的,带有随机性,所以会有这种结果

 我们写一个这样的仿函数,就可以解决问题

我们可以通过仿函数来控制比较规则,而不是被优先级队列给写死了

全部代码

//stack.h
using namespace std;
#include<iostream>
#include<list>
#include<vector>

namespace bai
{
	//容器适配器
	template<class T,class Container = vector<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_back();
		}
		T& top()
		{
			return _con.back();
		}
		size_t size()
		{
			return _con.size();
		}
		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
	void test() {
		stack<int> st1;
		st1.push(1);
		st1.push(2);
		st1.push(3);
		st1.push(4);
		while (! st1.empty()) {
			cout << st1.top() << " ";
			st1.pop();
		}
		cout << endl;
		stack<int, list<int>> st2;
		st2.push(10);
		st2.push(20);
		st2.push(30);
		st2.push(40);
		while (!st2.empty()) {
			cout << st2.top() << " ";
			st2.pop();
		}
		cout << endl;
	}
}
//queue.h
#include<iostream>
#include<list>
#include<vector>
using namespace std;
namespace bai
{
	//容器适配器
	template<class T, class Container = list<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			//_con.pop_front();
			_con.erase(_con.begin());
		}
		T& front()
		{
			return _con.front();
		}
		T& back()
		{
			return _con.back();
		}
		size_t size()
		{
			return _con.size();
		}
		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
	void test_queue() {
		//std::queue<int,vector<int>> q;
		queue<int, list<int>> q2;
		q2.push(10);
		q2.push(20);
		q2.push(30);
		q2.push(40);
		while (!q2.empty()) {
			cout << q2.front() << " ";
			q2.pop();
		}
		cout << endl;
	}
}
//priority_queue.h
using namespace std;
#include<vector>
#include<iostream>
namespace bai {
	template<class T,class Container = vector<T>,class Comapre = less<T>>
	class priority_queue
	{
	private:
		void AdjustDown(int parent)
		{
			Comapre com;
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() &&  com(_con[child] , _con[child + 1]))
				{
					++child;
				}
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else 
				{
					break;
				}
			}
		}
		void AdjustUp(int child)
		{
			Comapre com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[parent] , _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}	
		}
	public:
		priority_queue()
		{}
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				_con.push_back(*first);
				++first;
			}
			//建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--) 
			{
				AdjustDown(i);
			}
		}
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);
		}
		void push(const T& x)
		{
			_con.push_back(x);
			AdjustUp(_con.size() - 1);
		}
		const T& top()
		{
			return _con[0];
		}
		bool empty()
		{
			return _con.empty();
		}
		size_t size()
		{
			return _con.size();
		}
	private:
		Container _con;
	};

	class Date
	{
	public:
		Date(int year = 1900, int month = 1, int day = 1)
			: _year(year)
			, _month(month)
			, _day(day)
		{}

		bool operator<(const Date& d)const
		{
			return (_year < d._year) ||
				(_year == d._year && _month < d._month) ||
				(_year == d._year && _month == d._month && _day < d._day);
		}

		bool operator>(const Date& d)const
		{
			return (_year > d._year) ||
				(_year == d._year && _month > d._month) ||
				(_year == d._year && _month == d._month && _day > d._day);
		}

		friend ostream& operator<<(ostream& _cout, const Date& d);
	private:
		int _year;
		int _month;
		int _day;
	};

	ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
	struct LessPDate
	{
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 < *p2;
		}
	};
	void test() {
		priority_queue<Date*, vector<Date*>,LessPDate> pq;
		pq.push(new Date(2021, 7, 28));
		pq.push(new Date(2021, 6, 28));
		pq.push(new Date(2021, 8, 28));
		while (!pq.empty()) {
			cout << *pq.top() << " ";
			pq.pop();
		}
		cout << endl;
	}
}

以上即为本期全部内容,希望大家可以有所收获

如有错误,还请指正

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

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

相关文章

3D工厂模拟仿真 FACTORY I/O 2.55 Crack

FACTORY I/O 提供超过20个典型的工业应用场景让您如身临其境般地练习控制任务。选择一种场景直接使用或以其作为一个新项目的开端。学生可以利用内嵌的可编辑的典型工业系统模板&#xff0c;也可以自由搭建并编辑工业系统。同时该系统具有全方位3D视觉漫游&#xff0c;可随意放…

在Vue-Element中引入jQuery的方法

一、在终端窗口执行安装命令 npm install jquery --save执行完后&#xff0c;npm会自动在package.json中加上jquery 二、在main.js中引入&#xff08;或者在需要使用的页面中引入即可&#xff09; import $ from jquery三、使用jquery

5、Kubernetes核心技术 - Controller控制器工作负载

目录 一、Deployments - 控制器应用 二、Deployment升级回滚和弹性收缩 2.1、创建一个 1.14 版本的 pod 2.2、应用升级 2.3、查看升级状态 2.4、查看历史版本 2.5、应用回滚 2.6、弹性伸缩 三、StatefulSet - 有状态应用 四、DaemonSet - 守护进程 五、Job - 单次任…

Centos7 安装tomcat9

去官网下载 数据包 ps: wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.78/bin/apache-tomcat-9.0.78.tar.gz检查Java环境 [tomcatlocalhost bin]$ java -version java version "1.8.0_121" Java(TM) SE Runtime Environment (build 1.8.0_121-b13) Java H…

js的变量

目录 变量 var和let 1.for循环中的声明 2.暂时性死区 3.全局声明 4.条件声明 const声明 变量 java是一种强数据类型语言,对数据类型要求高&#xff0c;要声明清楚变量的类型 数据类型 变量名 值 -----> int a 10 而javaScrit是一种弱类型语言&#xff0c;在声明变…

【图像去噪】基于进化算法——自组织迁移算法(SOMA)的图像去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

k8s Webhook 使用java springboot实现webhook 学习总结

k8s Webhook 使用java springboot实现webhook 学习总结 大纲 基础概念准入控制器&#xff08;Admission Controllers&#xff09;ValidatingWebhookConfiguration 与 MutatingWebhookConfiguration准入检查&#xff08;AdmissionReview&#xff09;使用Springboot实现k8s-Web…

一文谈谈Git

"And if forever lasts till now Alright" 为什么要有git&#xff1f; 想象一下&#xff0c;现如今你的老师同时叫你和张三&#xff0c;各自写一份下半年的学习计划交给他。 可是你的老师是一个极其"较真"的人&#xff0c;发现你俩写的学习计划太"水&…

深度剖析APP开发中的UI/UX设计

作为一个 UI/UX设计师&#xff0c;除了要关注 UI/UX设计之外&#xff0c;还要掌握移动开发知识&#xff0c;同时在日常工作中也需要对用户体验有一定的认知&#xff0c;在本次分享中&#xff0c;笔者就针对自己在工作中积累的一些经验来进行一个总结&#xff0c;希望能够帮助到…

软件兼容性测试中需注意的关键问题

在进行软件兼容性测试时&#xff0c;有一些关键问题需要特别注意&#xff0c;以确保测试的准确性和全面性。本文将介绍一些在软件兼容性测试中需注意的关键问题&#xff0c;帮助测试人员更好地进行兼容性测试工作。 首先&#xff0c;测试范围&#xff0c;测试人员需要明确测试的…

pycharm 远程连接服务器并且debug, 支持torch.distributed.launch debug

未经允许&#xff0c;本文不得转载&#xff0c;vx&#xff1a;837007389 文章目录 step1&#xff1a;下载专业版本的pycharmstep2 配置自动同步文件夹&#xff0c;即远程的工程文件和本地同步2.1 Tools -> Deployment -> configuration2.2 设置同步文件夹2.3 同步服务器…

火山引擎DataLeap的Data Catalog系统公有云实践 (下)

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 Data Catalog公有云遇到的挑战 Data Catalog经历了一个从0到1在火山引擎公有云部署并逐步优化和迭代发布10版本的过程&#xff0c;在这个过程中经历不少挑战&#…

SQL语句(三十二)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、SQL语句类型 二、数据库操作 ​三、数据表操作 1. 数据类型 2. 查看 3. 创建 4. 删除 5. 更改 5.1 表 5.2 列 四、数据操作 4.1 增 4.2 删 4.3 改 4.4 查…

HHDESK便捷功能介绍三

1 连接便捷显示 工作中&#xff0c;往往需要设置很多资源连接。而过多的连接设&#xff0c;往往很容易混淆。 在HHDESK中&#xff0c;当鼠标点击连接时&#xff0c;会在下方显示本连接的参数&#xff0c;方便用户查看。 2 日志查看 实际工作中&#xff0c;查看日志是一件很…

JavaSwing+MySQL的在线考试系统

点击以下链接获取源码&#xff1a; https://download.csdn.net/download/qq_64505944/88114390?spm1001.2014.3001.5503 JDK1.8 MySQL5.7 功能&#xff1a;开始做题&#xff0c;上一题&#xff0c;下一题&#xff0c;提交&#xff0c;每题都有时间限制

真实和虚拟相撞,构造的VR展厅特点和优势有哪些?

随着科技的不断发展&#xff0c;VR展厅成为了现代展览的一种全新展现方式&#xff0c;三维立体展厅的设计&#xff0c;让企业、画展、纪念展等可以创造出令人惊叹的虚拟展览空间效果&#xff0c;为用户带来丰富的体验。观众身临其境地感受实体展厅和展品&#xff0c;这种超越了…

【3】-使用@task设置测试用例执行的权重

多个测试链路压测使测试任务按预想的比例执行 locust的task装饰器提供了入参weight&#xff0c;locust执行测试任务时&#xff0c;会根据weight的比例进行分配用户数 from locust import task, HttpUserclass MyTestUser(HttpUser):# test_01 : test_02 3 : 1task(3)def wei…

实景三维在智慧矿山中的应用

项目背景 智慧矿山是以矿山数字化、信息化为前提和基础&#xff0c;对矿山生产、职业健康与安全、技术支持与后勤保障等进行主动感知、自动分析、快速处理&#xff0c;建设智慧矿山&#xff0c;最终实现安全矿山、无人矿山、高效矿山、清洁矿山的建设。 智慧矿山的可视化管理…

postgresql四种逻辑复制的状态

准备 CreateCheckpoint&#xff0c;或者bgwriter启动时&#xff0c;或者创建logicalreplicationslot时都会调用LogStandbySnapshot 记录一个XLOG_RUNNING_XACTS类型的日志。日志中记录了所有提交的事务的xid(HistoricSnapshot) 启动&#xff08;SNAPBUILD_BUILDING_SNAPSHOT&…

linux下i2c调试神器i2c-tools安装及使用

i2c-tools介绍 在嵌入式linux开发中&#xff0c;有时候需要确认i2c硬件是否正常连接&#xff0c;设备是否正常工作&#xff0c;设备的地址是多少等等&#xff0c;这里我们就需要使用一个用于测试I2C总线的工具——i2c-tools。 i2c-tools是一个专门调试i2c的开源工具&#xff…