Windows环境下实现设计模式——解释器模式(JAVA版)

news2025/1/21 15:41:06

我是荔园微风,作为一名在IT界整整25年的老兵,今天总结一下Windows环境下如何编程实现解释器模式(设计模式)。

不知道大家有没有这样的感觉,看了一大堆编程和设计模式的书,却还是很难理解设计模式,无从下手。为什么?因为你看的都是理论书籍。

我今天就在Windows操作系统上安装好JAVA的IDE编程工具,并用JAVA语言来实现一个解释器模式,真实的实现一个,你看懂代码后,自然就明白了。

解释器模式Interpreter Pattern  (行为型设计模式)

定义:给定一个语言,定义这个语言的文法表示,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。

上面定义听懂了吗?莫名其妙看不懂对吧。所以我们还是来看看实现生活中的例子。

解释器模式主要用于描述如何构成一个简单的语言解释器,主要应用于使用面向对象语言开发的解释器的设计。当需要开发一个新的语言时可以考虑使用解释器模式。所以这种解释器模式用的比较少,但是要理解它却很难,是最难的一种行为型设计模式。

有时候我们希望输入一个计算公式给计算机就能得到结果,但是计算机并不认识加号和减号,它怎么计算呢?能用一些简单的语言来实现一些特定的操作吗?我们必须自己定义一套文法规则来实现对这些语句的解释,即设计一个自定义语言。

在实际开发中,这些简单的自定义语言可以基于现有的编程语言来设计,如果所基于的编程语言是面向对象语言,此时可以使用解释器模式来实现自定义语言。我们只要向计算机输入一个句子或文件,它就能够按照预先定义的文法规则来对句子或文件进行解释,从而实现相应的功能。如果能做到这一点不是很好?

在前面所提到的加法/减法解释器中,每一个输入表达式,例如“1 + 2 + 3”,都包含了三个语言单位,可以使用如下文法规则来定义:

expression ::= value | operation

operation ::= expression '+' expression | expression '-'  expression

value ::= an integer //一个整数值

现在我们来看一下上面三条分别是什么意思。

第一条表示表达式的组成方式,其中value和operation是后面两个语言单位的定义,每一条语句所定义的字符串如operation和value称为语言构造成分或语言单位,符号“::=”表示“定义为”的意思,其左边的语言单位通过右边来进行说明和定义,语言单位对应终结符表达式和非终结符表达式。如本规则中的operation是非终结符表达式,它的组成元素仍然可以是表达式,可以进一步分解,而value是终结符表达式,它的组成元素是最基本的语言单位,不能再进行分解。在文法规则定义中可以使用一些符号来表示不同的含义,如使用“|”表示或,使用“{”和“}”表示组合,使用“*”表示出现0次或多次等,其中使用频率最高的符号是表示“或”关系的“|”。在解释器模式中还可以通过一种称之为抽象语法树(Abstract Syntax Tree, AST)的图形方式来直观地表示语言的构成,每一棵抽象语法树对应一个语言实例,如加法/减法表达式语言中的语句“1+ 2 + 3”,可以通过如图所示抽象语法树来表示:

在该抽象语法树中,可以通过终结符表达式value和非终结符表达式operation组成复杂的语句,每个文法规则的语言实例都可以表示为一个抽象语法树,即每一条具体的语句都可以用类似的抽象语法树来表示,在图中终结符表达式类的实例作为树的叶子节点,而非终结符表达式类的实例作为非叶子节点,它们可以将终结符表达式类的实例以及包含终结符和非终结符实例的子表达式作为其子节点。抽象语法树描述了如何构成一个复杂的句子,通过对抽象语法树的分析,可以识别出语言中的终结符类和非终结符类。

由于表达式可分为终结符表达式和非终结符表达式,因此解释器模式的结构如图所示:

 在解释器模式结构图中包含如下几个角色:

 AbstractExpression(抽象表达式):在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。TerminalExpression(终结符表达式):终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。

具体代码

在解释器模式中,每一种终结符和非终结符都有一个具体类与之对应,正因为使用类来表示每一条文法规则,所以系统将具有较好的灵活性和可扩展性。对于所有的终结符和非终结符,首先需要抽象出一个公共父类,即抽象表达式类,代码如下所示:

public abstract class AbstractExpression {
       public  abstract void interpret(Context ctx);
}

 终结符表达式和非终结符表达式类都是抽象表达式类的子类,对于终结符表达式,其代码很简单,主要是对终结符元素的处理,代码如下所示:

public class TerminalExpression extends  AbstractExpression {
       public  void interpret(Context ctx) {
              //终结符表达式的解释操作
       }
}

对于非终结符表达式,其代码相对比较复杂,因为可以通过非终结符将表达式组合成更加复杂的结构,对于包含两个操作元素的非终结符表达式类,其代码如下:

public class NonterminalExpression extends  AbstractExpression {
       private  AbstractExpression left;
       private  AbstractExpression right;

       public  NonterminalExpression(AbstractExpression left,AbstractExpression right) {
              this.left=left;
              this.right=right;
       }

       public void interpret(Context ctx) {
              //递归调用每一个组成部分的interpret()方法
              //在递归调用时指定组成部分的连接方式,即非终结符的功能
       }     
}

 除了上述用于表示表达式的类以外,通常在解释器模式中还提供了一个环境类Context,用于存储一些全局信息,通常在Context中包含了一个HashMap或ArrayList等类型的集合对象(也可以直接由HashMap等集合类充当环境类),存储一系列公共信息,如变量名与值的映射关系(key/value)等,用于在进行具体的解释操作时从中获取相关信息。其代码片段如下:

public class Context {
     private HashMap<String,String> map = new HashMapp<String,String>();


     public void assign(String key, String value) {
         //往环境类中设值
         map.put(key,value);
     }

     public String  lookup(String key) {
         //获取存储在环境类中的值
         return map.get(key);   
     }
}

环境类Context的对象通常作为参数被传递到所有表达式的解释方法interpret()中,可以在环境类对象中存储和访问表达式解释器的状态,向表达式解释器提供一些全局的、公共的数据,此外还可以在环境类中增加一些所有表达式解释器共有的功能,以减轻解释器的职责。当系统无须提供全局公共信息时可以省略环境类,根据实际情况决定是否需要环境类。

应用实例

好,我们现在来一个实例,可以加深理解。我们今天来看看实例是什么

我们来讲讲无人驾驶汽车。

汽车公司要开发一套无人驾驶汽车的控制程序,在该控制程序中包含一些简单的控制指令,每一个指令对应一个表达式(expression),该表达式可以是简单表达式也可以是复合表达式,每一个简单表达式由移动方向(direction),移动方式(action)和移动距离(distance)三部分组成,其中移动方向包括前(front)、后(back)、左(left)、右(right);移动方式包括移动(move)和快速移动(run);移动距离为一个正整数。两个表达式之间可以通过与(and)连接,形成复合(composite)表达式。用户通过对图形化的设置界面进行操作可以创建一个控制指令,机器人在收到指令后将按照指令的设置进行移动,例如输入控制指令:front move 5,则“向前移动5个单位”;输入控制指令:back  run 10 and left move 20,则“向后快速移动10个单位再向左移动20个单位”。

根据上述需求描述,用形式化语言来表示该简单语言的文法规则如下:

expression ::= direction action distance | composite //表达式
composite ::= expression 'and' expression //复合表达式
direction ::= 'front' | 'back' | 'left' | 'right' //移动方向
action ::= 'move' | 'run' //移动方式
distance ::= an integer //移动距离

上述语言一共定义了五条文法规则,对应五个语言单位,这些语言单位可以分为两类,一类为终结符(也称为终结符表达式),例如direction、action和distance,它们是语言的最小组成单位,不能再进行拆分;另一类为非终结符(也称为非终结符表达式),例如expression和composite,它们都是一个完整的句子,包含一系列终结符或非终结符。

 我们根据上述规则定义出的语言可以构成很多语句,计算机程序将根据这些语句进行某种操作。为了实现对语句的解释,可以使用解释器模式,在解释器模式中每一个文法规则都将对应一个类,扩展、改变文法以及增加新的文法规则都很方便,下面就让我们正式进入解释器模式的学习,看看使用解释器模式如何来实现对控制指令的处理。

针对五条文法规则,分别提供五个类来实现,其中终结符表达式direction、action和distance对应DirectionNode类、ActionNode类和DistanceNode类,非终结符表达式expression和composite对应SentenceNode类和AndNode类。

(1)AbstractNode 抽象结点类,充当抽象表达式角色

package designpatterns.interpreter;

//抽象表达式
public abstract class AbstractNode {
	public abstract String interpret();
}

(2)AndNode 结点类

package designpatterns.interpreter;

//And解释:非终结符表达式
public class AndNode extends AbstractNode {
	private AbstractNode left; //And的左表达式
	private AbstractNode right; //And的右表达式
 
	public AndNode(AbstractNode left, AbstractNode right) {
		this.left = left;
		this.right = right;
	}
    
    //And表达式解释操作
	public String interpret() {
		return left.interpret() + "再" + right.interpret();
	}
}

(3)SentenceNode 简单句子结点类

package designpatterns.interpreter;

//简单句子解释:非终结符表达式
public class SentenceNode extends AbstractNode {
	private AbstractNode direction;
	private AbstractNode action;
	private AbstractNode distance;
 
	public SentenceNode(AbstractNode direction,AbstractNode action,AbstractNode distance) {
		this.direction = direction;
		this.action = action;
		this.distance = distance;
	}
    
    //简单句子的解释操作
	public String interpret() {
		return direction.interpret() + action.interpret() + distance.interpret();
	}	
}

(4)DirectionNode 方向结点类

package designpatterns.interpreter;

//方向解释:终结符表达式
public class DirectionNode extends AbstractNode {
	private String direction;
	
	public DirectionNode(String direction) {
		this.direction = direction;
	}
	
    //方向表达式的解释操作
	public String interpret() {
		if (direction.equalsIgnoreCase("front")) {
			return "向前";
		}
		else if (direction.equalsIgnoreCase("back")) {
			return "向后";
		}
		else if (direction.equalsIgnoreCase("left")) {
			return "向左";
		}
		else if (direction.equalsIgnoreCase("right")) {
			return "向右";
		}
		else {
			return "无";
		}
	}
}

(5)ActionNode 动作结点类

package designpatterns.interpreter;

//动作解释:终结符表达式
public class ActionNode extends AbstractNode {
	private String action;
	
	public ActionNode(String action) {
		this.action = action;
	}
	
    //动作(移动方式)表达式的解释操作
	public String interpret() {
		if (action.equalsIgnoreCase("move")) {
			return "移动";
		}
		else if (action.equalsIgnoreCase("run")) {
			return "快速";
		}
		else {
			return "无";
		}
	}
}

(6)DistanceNode 距离结点类

package designpatterns.interpreter;

//距离解释:终结符表达式
public class DistanceNode extends AbstractNode {
	private String distance;
	
	public DistanceNode(String distance) {
		this.distance = distance;
	}
	
    //距离表达式的解释操作
	public String interpret() {
		return this.distance;
	}	
}

(7)InstructionHandler 指令处理类

这段代码很复杂,我要重点解释一下。这个类提供相应的方法对输人指令进行处理,它将输人指令分割为字符串数组,将第一个、第二个和第三个单词组合成一个句子,并存入栈中;如果发现有单词“and”,则将“and”后的第一个、第二个和第三个单词组合成一个新的句子作为“and”的右表达式,并从栈中取出原先所存的句子作为左表达式,然后组合成一个And结点存入栈中。依此类推,直到整个指令解析结束。

package designpatterns.interpreter;
import java.util.*

public class InstructionHandler {
	private AbstractNode node;
    
    public void handle(String instruction) {
    	AbstractNode left = null, right = null;
    	AbstractNode direction = null, action = null, distance = null;
    	Stack<AbstractNode> stack = new Stack<AbstractNode>(); //声明一个栈对象用于存储抽象语法树
    	String[] words = instruction.split(" "); //以空格分隔指令字符串
    	for (int i = 0; i < words.length; i++) {
//本实例采用栈的方式来处理指令,如果遇到“and”,则将其后的三个单词作为三个终结符表达式连成一个简单句子SentenceNode作为“and”的右表达式,而将从栈顶弹出的表达式作为“and”的左表达式,最后将新的“and”表达式压入栈中。    		       
         if (words[i].equalsIgnoreCase("and")) {
    			left = (AbstractNode)stack.pop(); //弹出栈顶表达式作为左表达式
    		    String word1= words[++i];
    		    direction = new DirectionNode(word1);
    		    String word2 = words[++i];
    		    action = new ActionNode(word2);
    		    String word3 = words[++i];
    		    distance = new DistanceNode(word3);
    		    right = new SentenceNode(direction,action,distance); //右表达式
    			stack.push(new AndNode(left,right)); //将新表达式压入栈中
    		}
            //如果是从头开始进行解释,则将前三个单词组成一个简单句子SentenceNode并将该句子压入栈中
    		else {
    		    String word1 = words[i];
    		    direction = new DirectionNode(word1);
    		    String word2 = words[++i];
    		    action = new ActionNode(word2);
    		    String word3 = words[++i];
    		    distance = new DistanceNode(word3);
    		    left = new SentenceNode(direction,action,distance);
    		    stack.push(left); //将新表达式压入栈中
    		}
    	}
    	this.node = (AbstractNode)stack.pop(); //将全部表达式从栈中弹出
    }
	
	public String output() {
		String result = node.interpret(); //解释表达式
		return result;
	}
}

(8)Client 客户端


package designpatterns.interpreter;

public class Client {
	public static void main(String args[]) {
		String instruction = "back run 10 and left move 5";
		InstructionHandler handler = new InstructionHandler();
		handler.handle(instruction);
		String outString;
		outString = handler.output();
		System.out.println(outString);
	}
}

由于这个模式比较难,所以要再别说一下。

解释器模式为自定义语言的设计和实现提供了一种解决方案,它用于定义一组文法规则并通过这组文法规则来解释语言中的句子。解释器模式在正则表达式、XML文档解释等领域还是得到了广泛使用。与解释器模式类似,目前还诞生了很多基于抽象语法树的源代码处理工具,例如Eclipse中的Eclipse AST,它可以用于表示Java语言的语法结构,用户可以通过扩展其功能,创建自己的文法规则。

解释器模式的主要优点如下:

 (1) 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。 (2) 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。 (3) 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。 (4) 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。

解释器模式的主要缺点如下:

(1) 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。 (2) 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

解释器模式适用场景:

 (1) 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。 (2) 一些重复出现的问题可以用一种简单的语言来进行表达。  (3) 一个语言的文法较为简单。  (4) 执行效率不是关键问题。高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高。

各位小伙伴,这次我们就说到这里,下次我们再深入研究windows环境下的各类设计模式实现。

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。
 

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

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

相关文章

巧用千寻位置GNSS软件|CAD功能全解析

千寻位置GNSS软件中的CAD功能&#xff0c;用于已有 CAD的图形的导入和编辑&#xff0c;并且可以对 CAD图形已有线条进行线放样&#xff0c;在日常测绘工作中十分常见。下面向各位介绍CAD功能的使用技巧。点击【测量】->【CAD】&#xff0c;进入 CAD功能如图 5.3-1所示。以下…

三、线程状态【3/12】【多线程】

线程的状态3. 线程的状态3.1 观察线程的所有状态3.2 线程状态和状态转移的意义3.3 观察线程的状态和转移3. 线程的状态 3.1 观察线程的所有状态 线程的状态是一个枚举类型 Thread.State public class ThreadState {public static void main(String[] args) {for (Thread.State…

项目7:(aliyun)实现短信的发送和验证微服务和上传文件删除文件微服务

项目7&#xff1a;实现短信的发送和验证 1.对gulimall-common补充 2.短信验证的流程&#xff08;aliyun的sms&#xff09; 3.具体接口的编写&#xff08;新建微服务service-sms&#xff09; 4.上传和删除文件流程&#xff08;aliyun的oss&#xff09; 5.具体接口的编写&am…

区块链智能合约开发学习

最近正在肝区块链知识学习&#xff0c;入手学习智能合约的开发&#xff0c;由于网上资料实在是太少了&#xff0c;好不容易东拼西凑完成了智能合约的开发、编译、部署、web3js调用&#xff08;网页页面&#xff09;和web3j调用&#xff08;java调用&#xff09;&#xff0c;赶紧…

Linux 内存回收,思维导图记录

最近天天跟内存斗智斗勇&#xff0c;整理下学习的记录 一些图片 参考 Tuning Linux Kernel Parameters For PostgreSQL Optimization PostgreSQL recommendations - Documentation for BMC Client Management 12.6 - BMC Documentation PostgreSQL load tuning on Red Hat E…

【vSphere | Python】vSphere Automation SDK for Python Ⅵ—— VM Guest Processes APIs

目录12. VM APIs12.1 VM Guest Processes APIsProcesses 进程Operations 操作&#xff08;1&#xff09;List Guest Processes&#xff08;2&#xff09;Get Guest Processes&#xff08;3&#xff09;Create Guest Processes&#xff08;4&#xff09;Delete Guest Processes参…

PaddleHub 更改模型默认下载位置

文章目录1.PaddleHub介绍2.PaddleHub安装3.PaddleHub使用中出现的问题4.更改PaddleHub模型的默认下载位置5. PaddleHub的简单使用1.PaddleHub介绍 PaddleHub 是基于 PaddlePaddle 开发的预训练模型管理工具&#xff0c;可以借助预训练模型更便捷地开展迁移学习工作&#xff0c…

docker内部执行nvidia-smi无任何显示的解决方法

docker内部执行nvidia-smi无任何显示的解决方法 贺志国 2023.4.11 今天在Docker内部编译程序&#xff0c;发现与CUDA相关的代码居然没有编译&#xff0c;于是在Docker内部执行Nvidia显卡驱动检测命令nvidia-smi&#xff0c;执行完毕后&#xff0c;无任何输出&#xff0c;也没…

计算机视觉面试题-网络结构相关问题总结-未完待续

VGG卷积核为什么取33 &#xff1f; VGG使用33卷积核的优势是什么? Resnet 主要解决什么问题 为什么会有ResNet&#xff1f; 深度网络退化的原因 Resnet的针对网络退化提出的残差网络 Resnet网络结构 Resnet网络结构中如何实现的下采样 Resnet50网络结构Resnet特点 vgg16与 res…

Java并发篇二

ForkJoin 在JDK1.7&#xff0c;并行执行任务&#xff0c;提高效率&#xff0c;大数据量才会使用 特点&#xff1a;大任务拆分成小任务&#xff0c;工作窃取&#xff0c;里面维护的是双端队列 package com.kuang.forkjoin;import java.util.concurrent.RecursiveTask;/*** 如…

(PCB系列三)AD六层板布线经验累积

目录 1、布局&#xff1a; 2、创建电源类PWR 3、高速部分可以加屏蔽罩&#xff0c; 4、EMMC和NANDFLASH采取兼容放置&#xff08;创建联合&#xff09; 5、HDMI设计 6、就近原则摆放 7、AV端口 8、模拟信号&#xff08;1字型或L型走线&#xff09; 9、WIFI模块 10、局…

研究生,但是一直在摆烂学不进去

好的&#xff0c;我来为您创作一首歌曲&#xff0c;希望能够帮助您每天保持自律&#xff0c;专注学习。 《自律之歌》 第1节&#xff1a; 每天都要努力 学习不停歇 独自一人也要坚持 不放弃自己的梦想 读文献 写论文 我们不停探索 穷孩子的荣耀 就在不远处等候 合唱&#xf…

面试手撕算法题--下一个排列

前言 面试官描述这个题的时候&#xff0c;我就感觉似曾相识似乎做过&#xff0c;面完以后到leetcode找到原题恨不得给自个儿来一下子&#xff0c;的确&#xff0c;当时调api爽了&#xff0c;然后呢面试被拷打了啊&#xff0c;我想不起来这个api具体怎么解决这个题目的底层原理…

【非递归】手搓快速排序

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; 前言&#xff1a; 快速排序已经带大家实现过了&#xff0c;我们用到的方法是递归法&#xff0c;你知道吗&#xff0c;用循环也可以实现快速排序&#xff0c;来看看吧。 注&#xff1a; 这篇博客属于数据结构…

FE_CSS 基础选择器 字体属性 文本属性 综合案例

1 CSS 基础选择器 选择器分为基础选择器和复合选择器两个大类&#xff0c;基础选择器是由单个选择器组成的&#xff0c;基础选择器又包括&#xff1a;标签选择器、类选择器、id 选择器和通配符选择器。 1.1 标签选择器 标签名{属性1: 属性值1; 属性2: 属性值2; 属性3: 属性…

从0-1搭建交付型项目管理体系流程-- 项目启动篇【宝芝林5】

一. 目标及作用 本阶段主要的目标是签订合同及SOW工作说明书&#xff0c;其里程碑事件为甲乙双方完成合同及SOW工作说明书签字及盖章&#xff0c;以及召开项目启动会。 主要作用是明确项目甲乙双方的权利和义务&#xff0c;以及与甲方及其他实施团队共同制定项目章程&#xf…

有趣的Hack-A-Sat黑掉卫星挑战赛——被破坏的阿波罗计算机(解法一)

国家太空安全是国家安全在空间领域的表现。随着太空技术在政治、经济、军事、文化等各个领域的应用不断增加&#xff0c;太空已经成为国家赖以生存与发展的命脉之一&#xff0c;凝聚着巨大的国家利益&#xff0c;太空安全的重要性日益凸显[1]。而在信息化时代&#xff0c;太空安…

面试官:你做过什么有亮点的项目吗?

前言 面试中除了问常见的算法网络基础&#xff0c;和一些八股文手写体之外&#xff0c;经常出现的一个问题就是&#xff0c;你做过什么项目吗&#xff1f; 面试官其实是想看看你做过什么有亮点的项目, 其实大家日常做的项目都差不多&#xff0c;增删改查&#xff0c;登录注册&…

如何压缩照片到30kb以下?三个方法

如何压缩照片到30kb以下&#xff1f;随着网络的发展&#xff0c;我们经常要上传一些照片到网上&#xff0c;如公务员考试&#xff0c;教师招聘等&#xff0c;而且要求上传的照片大小不超过30kb&#xff0c;我们如何把照片压缩到30kb以下呢&#xff1f;现在很多平台上传图片时都…

【Arduino机器人手臂和麦克纳姆轮平台自动操作】

【Arduino机器人手臂和麦克纳姆轮平台自动操作】 1. 概述2. 构建 Arduino 机器人3. Arduino机器人电路图4. Arduino Code在本教程中,我将向您展示我如何从我以前的视频中制作我的 Mecanum Wheels 机器人平台,以便与我的 3D 打印机械臂协同工作并自动操作,这也是我之前视频之…