解释器模式与栈式机器

一、解释器模式

解释器模式是一种设计模式,用于定义一个语言的语法结构并为其提供解释执行的功能。它最常用于解析和执行语言的表达式,特别是嵌入式语言或自定义语言。在这个例子中,它演示了如何通过解释器模式来处理简单的算术表达式,比如:

(1 + 2) * (3 - 4)

1. 表达式树

在解释器模式中,表达式被表示为一个对象结构,这种结构通常是抽象语法树(AST)。每个表达式(如数字、运算符)都会封装成一个对象,表达式之间通过这些对象相互关联。以算术表达式为例:

  • 数字字面量(如1、2、3)是简单的值,被封装为对象。
  • 运算符(如+、*)是操作符对象,它们引用左右两个操作数(子表达式)。

这些对象形成了一棵树,树中的每个节点要么是数字,要么是操作符,整个表达式树构建出了表达式的语法结构。

2. 语法分析与解释

虽然解释器模式与语法分析器无关,但语法分析器的作用是将字符或代码解析成抽象语法树,解释器模式则专注于执行或求值这棵树。解释器模式的关键思想是让表达式对象“自己对自己求值”。

每个表达式都实现了一个通用接口,通常是evaluate()方法,用于计算它代表的值。比如:

  • 数字表达式:它直接返回存储的数字。
  • 加法表达式:它递归地计算左右子表达式的值,然后将它们相加。

代码示例:

class Expression
{
public:virtual ~Expression() {}virtual double evaluate() = 0;  // 每个表达式计算自己
};class NumberExpression : public Expression
{
public:NumberExpression(double value) : value_(value) {}virtual double evaluate() { return value_; }private:double value_;
};class AdditionExpression : public Expression
{
public:AdditionExpression(Expression* left, Expression* right) : left_(left), right_(right) {}virtual double evaluate() {// 递归计算左右操作数double left = left_->evaluate();double right = right_->evaluate();return left + right;}private:Expression* left_;Expression* right_;
};

在这个结构中,每个运算符(如加法)都依赖于子表达式先计算自己,形成递归求值的过程。这种优雅的设计允许我们轻松表示和计算复杂的算术表达式。

3. 性能问题

虽然解释器模式设计得非常优雅、灵活,但在实际应用中可能面临一些性能问题。这些问题主要体现在以下几个方面:

3.1 对象数量多

每个表达式都是一个对象,表达式树中充满了小对象(如数字、运算符),对象的创建、连接会占用大量的内存。当表达式树很大时,实例化和维护大量对象的开销可能非常高。

3.2 指针遍历与缓存效率

表达式之间通过指针连接,执行时需要频繁地遍历子表达式,而这种指针跳转会影响CPU的缓存性能。由于缓存是基于数据局部性(即访问相邻数据的效率更高)来优化的,频繁的对象跳转可能导致缓存未命中,进而降低性能。

3.3 虚函数调用开销

每个表达式的 evaluate() 方法通常是虚函数调用,虚函数会增加调用的开销。特别是在复杂的表达式树中,虚函数的频繁调用会拖慢执行速度。

4. 为什么现代编程语言不采用解释器模式

虽然解释器模式非常适合小型、嵌入式语言或自定义语言的实现,但它的内存和性能开销使得它不适合大多数主流编程语言。现代编程语言(如Ruby在1.9版本后的实现)更倾向于使用字节码编译器优化,而不是解释器模式。这是因为解释器模式:

  • :对象多,虚函数调用频繁,缓存命中率低。
  • 耗内存:每个表达式都需要占用内存,且指针连接会进一步增加内存消耗。

总结

解释器模式优雅、简单,特别适用于构建简单的领域特定语言(DSL)或表达式求值系统。然而,在涉及大量计算或需要高性能的场景中,它的内存和性能问题使它不太适用。主流编程语言通常会选择更高效的编译或字节码执行方式来代替解释器模式。

二、栈式机器

栈式机器是一种虚拟机架构,使用栈作为中间存储来执行指令。每当计算表达式或执行指令时,操作数和结果都通过栈传递。相比解释器模式中将表达式显式地构造成对象树,栈式机器使用扁平的指令序列来操作值,效率更高。下面是对栈式机器的工作原理以及相关概念的解释。

1. 栈的概念

栈是一种“后进先出”(LIFO)的数据结构,数据可以通过“压栈”和“弹栈”进行存储和读取。栈式机器依赖栈来存储中间结果与操作数。每当需要执行某个操作时,操作数会从栈中弹出,结果再被压回栈中。

2. 指令执行流程

在栈式机器中,程序是由一系列指令组成的。每条指令依次执行,可能会对栈进行操作,比如压栈、弹栈、运算等。

以下是一个简单的虚拟机类,其中的栈用于保存指令之间传递的值:

class VM
{
public:VM() : stackSize_(0) {}private:static const int MAX_STACK = 128;int stackSize_;int stack_[MAX_STACK];void push(int value) {// 压栈assert(stackSize_ < MAX_STACK);stack_[stackSize_++] = value;}int pop() {// 弹栈assert(stackSize_ > 0);return stack_[--stackSize_];}
};

3. 指令的工作方式

每个指令会对栈进行特定的操作。比如INST_SET_HEALTH指令,从栈中弹出两个参数,表示巫师编号和健康值,然后调用setHealth()函数将巫师的血量设置为相应值:

switch (instruction)
{case INST_SET_HEALTH:{int amount = pop();int wizard = pop();setHealth(wizard, amount);break;}// 其他指令类似处理
}

在此示例中,栈存储了巫师的ID和健康值,当INST_SET_HEALTH指令执行时,虚拟机会依次从栈中弹出这些值,并进行相应的逻辑操作。

4. 字面量指令

在栈式机器中,字面量(如整数值)通过指令压入栈中。这可以通过INST_LITERAL指令实现:

case INST_LITERAL:
{int value = bytecode[++i]; // 从字节码中读取字面量值push(value); // 压入栈break;
}

例如,假设字节码流中有一条指令0x05 123,表示字面量指令和它的值为123,虚拟机会将123压入栈中。这个机制使虚拟机能够执行由多个字面量和操作符构成的表达式。

5. 栈式执行流程

假设有一串指令用于将某个巫师的血量设为10。它的执行步骤如下:

  1. 执行字面量指令,将0压入栈,表示巫师编号。
  2. 执行字面量指令,将10压入栈,表示健康值。
  3. 执行INST_SET_HEALTH指令,弹出健康值和巫师编号,并调用setHealth()

整个过程通过栈管理操作数与中间结果,避免了复杂的树形结构。

6. 组合与状态读取

栈式机器不仅可以设置状态,还可以读取状态并进行运算。比如,你可以通过INST_GET_HEALTH指令读取巫师的当前血量:

case INST_GET_HEALTH:
{int wizard = pop(); // 弹出巫师编号push(getHealth(wizard)); // 获取血量并压入栈break;
}

这让虚拟机可以根据现有状态进行运算。例如,设计师可以设计一个法术,使巫师的血量等于智慧值的一半:

  1. 使用INST_GET_WISDOM指令获取巫师的智慧值并压入栈。
  2. 使用INST_LITERAL 2指令压入2。
  3. 使用除法指令,将栈顶的智慧值除以2。
  4. 使用INST_SET_HEALTH指令将结果设置为巫师的血量。

7. 栈式机器的优点

  • 指令简单高效:通过栈传递数据,避免了复杂的对象结构。
  • 灵活性:支持通过组合不同指令创建复杂的行为。
  • 扁平化数据:栈式结构使得虚拟机可以在指令序列中简单高效地执行表达式,而不必构造复杂的抽象语法树。

8. 栈式机器的局限

尽管栈式机器的结构简单有效,但它也有一些局限性:

  • 栈深度限制:栈的容量是有限的,过深的嵌套表达式可能导致栈溢出。
  • 可读性:直接使用栈操作对于复杂表达式的可读性较差,难以直观理解程序逻辑。

总结

栈式机器通过栈来管理表达式的执行,避免了树形结构带来的复杂性和性能开销。它类似于CPU的执行模型,通过指令顺序和栈操作来完成复杂计算和状态管理。然而,它在灵活性上也有一定的限制,需要通过指令的组合才能实现复杂行为。

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

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

相关文章

PyQt 入门教程(3)基础知识 | 3.2、加载资源文件

文章目录 一、加载资源文件1、PyQt5加载资源文件2、PyQt6加载资源文件 一、加载资源文件 常见的资源文件有图像与图标&#xff0c;下面分别介绍下加载资源文件的常用方法 1、PyQt5加载资源文件 2、PyQt6加载资源文件 PyQt6版本暂时没有提供pyrcc工具&#xff0c;下面介绍下在不…

雷池社区版本SYSlog使用教程

雷池会对恶意攻击进行拦截&#xff0c;但是日志都在雷池机器上显示 如何把日志都同步到相关设备进行统一的管理和分析呢&#xff1f; 如需将雷池攻击日志实时同步到第三方服务器, 可使用雷池的 Syslog 外发 功能 启用 Syslog 外发 进入雷池 系统设置 页面, 配置 Syslog 设置…

leetcode中常用的enumerate用法和常用场景

enumerate() 的用法 enumerate() 是 Python 的一个内置函数&#xff0c;它允许你在遍历可迭代对象&#xff08;如字符串、列表、元组等&#xff09;时&#xff0c;同时获得元素的索引和元素的值。enumerate() 是在需要对迭代的数据进行索引操作时非常有用的工具。 语法&#…

北京京恋在喧嚣的都市中助你邂逅自己的爱情

北京的夜晚&#xff0c;灯火璀璨&#xff0c;车水马龙。刘凡站在他位于国贸的公寓阳台上&#xff0c;望着眼前熙熙攘攘的街道&#xff0c;心中却有一丝落寞。32岁的他&#xff0c;是一家知名互联网公司的中层管理&#xff0c;事业有成&#xff0c;收入稳定&#xff0c;甚至朋友…

anaconda(jupyter)安装教程

目录 一、下载anaconda安装包 二、安装程序 三、怎么使用 四、把jupyter界面语言修改成中文 一、下载anaconda安装包 anaconda官网&#xff1a;下载 Anaconda Distribution |蟒蛇 清华大学开源软件镜像站官网&#xff1a;清华大学开源软件镜像站 | Tsinghua Open Source M…

嵌入式linux中条件变量的具体实现

大家好,今天主要给大家分享一下,如何使用条件变量以及具体实现方法。 第一:条件变量分析 条件变量是另一种逻辑稍微复杂一点点的同步互斥机制,他必须跟互斥锁一起配合使 他的应用场景也是非常常见的,先来看一个例子: 用,小楠是一名在校学生,每个月都会从父母那里得到一笔…

考研C语言程序设计_语法相关(持续更新)

目录 一、语法题strlen转义字符内置数据类型字符串结束标志局部变量和全局变量名字冲突 局部优先switch语句中的关键字数组初始化是否正确注意define不是关键字C语言中不能用连等判断switch( )的括号里可以是什么类型?关于if关于switch关于while 二、程序阅读题有关static有关…

【openGL学习笔记】----GLFW、GLAD环境配置

glew、glad、freeglut、glfw的区别&#xff1f; glew&#xff08;The OpenGL Extension Wrangler Library&#xff09;是对底层OpenGL接口的封装&#xff0c;可以让你的代码跨平台。glad与glew作用相同&#xff0c;可以看作它的升级版。Freeglut&#xff08;OpenGL Utility To…

Torch常用函数

博主看开源遇到的torch常用函数&#xff0c;记录方便查阅 clamp()Conv1d()expand()tril()Parameter()Identity()flatten()repeat()contiguous()narrow()view() 与 reshape()expand()squeeze()和unsqueeze()transpose()permute()size()和shapemasked_fill()new_zeros() clamp() …

熟练使用Spring Boot、Spring Cloud Alibaba微服务开发框架,并深入理解其原理 学习要求

1. Spring Boot 核心理解 自动配置&#xff1a;了解 Spring Boot 的自动配置原理&#xff08;EnableAutoConfiguration&#xff09;&#xff0c;包括如何查看和定制自动配置的内容。需要能解释 Spring Boot 如何减少样板代码。Spring Boot Starter&#xff1a;熟悉各种 starte…

Synchronized锁的升级流程详解

在Java多线程编程中&#xff0c;synchronized关键字用于确保在同一时刻只有一个线程可以访问被锁定的资源&#xff0c;从而维护数据的一致性和安全性。然而&#xff0c;在多线程环境中&#xff0c;锁的频繁获取和释放会带来性能开销。为了提高性能&#xff0c;Java虚拟机&#…

计算机组成原理(笔记7高速缓冲存储器Cache,计算机组成原理的重难点全、直接、组相连)

为什么要设立高速缓冲存储器 &#xff08;Cache&#xff09;&#xff1f; Cache是介于CPU和主存之间的小容量存储器&#xff0c;存取速度比主存快。它能高速地向CPU提供指令和数据&#xff0c;加快程序的执行速度。它是为了解决CPU和主存之间速度不匹配而采用的一项重要技术。…

10月15日,每日信息差

第一、《哈利・波特与魔法石》在中国内地总票房突破 3 亿元&#xff0c;包括 2002 年首映的 5600 万&#xff0c;2020 年重映的 1.923 亿&#xff0c;以及 2024 年重映的 5170 万。 第二、全国铁路实施新货物列车运行图&#xff0c;增开城际班列至 131 列&#xff0c;多式联运…

qiankun-前端接入微服务vue3项目应用

背景 由于在 《吐槽一次qiankun微前端的框架》 这篇博客中&#xff0c;初次使用qiankun&#xff0c;然后接入了原生项目作为微服务的应用到主应用&#xff0c;所以就想着出个系列篇 目的 介绍利用qiankun框架&#xff0c; vue3 项目应用作为微应用&#xff0c;怎么接入到主应…

节点+镜像

节点、镜像: 在服务器领域&#xff0c;"节点" 和 "镜像" 是两个关键的概念&#xff0c;常与分布式系统、云计算或集群相关联。以下是对它们的详细解释&#xff1a; 1. 服务器节点 在分布式系统或云计算环境中&#xff0c;节点指的是网络中执行特定任务的…

使用Mockaroo生成测试数据

使用Mockaroo生成测试数据 最近在学习【Spring Boot & React】Spring Boot和React教程视频的P51.Generating 1000 students一课中&#xff0c;看到了https://www.mockaroo.com/网站可以用来模拟生成测试数据&#xff0c;觉得还不错&#xff0c;特此记录一下。感觉每次看老…

centOS部署Jenkins实现项目可持续自动化部署

个人看的是尚硅谷的视频&#xff0c;跟着实战&#xff0c;但因为视频是21年的&#xff0c;所以很容易出现jenkins插件不适配问题。 因而个人直接用较新版的jdk和jenkins. 先切换到root用户 sudo su一、安装jdk 先查询可安装版本 yum list java*安装jdk&#xff08;只复制圈…

【Python爬虫实战】正则:中文匹配与贪婪非贪婪模式详解

&#x1f308;个人主页&#xff1a;https://blog.csdn.net/2401_86688088?typeblog &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/2401_86688088/category_12797772.html 目录 前言 一、匹配中文 &#xff08;一&#xff09;匹配单个中文字符 &#xff08;二…

数据结构常考基础代码题-顺序表有序插入

顺序表递增有序&#xff0c;插入元素 x&#xff0c;仍递增有序 第一步&#xff1a;定义顺序表结构体 根据题目中的“顺序表递增有序”&#xff0c;我们需要定义一个顺序表结构体&#xff0c;用于存储元素和顺序表的相关信息。 typedef struct {int *data; // 动态数组存储元…

DeepFM模型代码详解

直到看到这篇文章&#xff0c;我才搞明白类别特征怎么做lookup的&#xff0c;也看明白了代码逻辑。如果你看完没懂&#xff0c;私信留下wx&#xff0c;给你讲懂。 1、Deepfm 的原理&#xff0c;DeepFM 是一个模型还是代表了一类模型&#xff0c;DeepFM 对 FM 做了什么样的改进…