C++ 设计模式——解释器模式

news/2024/9/17 3:04:56/ 标签: c++, 设计模式, 解释器模式

目录

C++ 设计模式——解释器模式

解释器模式是一种特定于语言的模式,用于定义如何评估语言的句法或表达式。它适用于某些重复出现的问题,可以将一个需要解释执行的语言中的句子表示为一个抽象的语法树。这种模式通常被用于开发编程语言解释器或简单的脚本引擎。

引人“解释器”设计模式的定义(实现意图):定义一个语言的文法(语法规则),并建立一个解释器解释该语言中的句子。

1. 主要组成成分

  1. 抽象表达式(Abstract Expression):定义了解释操作的接口。这个接口通常包含一个解释(Interpret)方法,该方法接受一个上下文作为参数。
  2. 终结符表达式(Terminal Expression):实现了抽象表达式接口。这些表达式代表了语言中的终结符,如数字或变量。
  3. 非终结符表达式(Nonterminal Expression):也实现了抽象表达式接口。这些表达式代表了语言中的非终结符,通常包含其他表达式。
  4. 上下文(Context):包含解释器之外的一些全局信息。这可能包括变量的值、当前状态等。
  5. 客户端(Client):构建抽象语法树并调用解释操作。客户端通常会创建或被给予一个表示特定句子的抽象语法树,然后调用解释方法。

2. 逐步构建解释器模式

这个逐步构建的过程展示了解释器模式的核心组件如何协同工作,从定义基本的表达式接口,到实现具体的表达式类,再到构建和解释复杂的表达式树。这种方法使得添加新的表达式类型变得简单,同时保持了整体结构的灵活性和可扩展性。

步骤1: 定义抽象表达式

首先定义一个抽象基类 Expression,它是所有表达式的基础,所有的具体表达式类都必须实现这个函数,以便执行具体的解释任务。

//小表达式(节点)父类
class Expression
{
public:Expression(int num, char sign) :m_dbg_num(num), m_dbg_sign(sign) {} //构造函数virtual ~Expression() {} //做父类时析构函数应该为虚函数public://解析语法树中的当前节点virtual int interpret(map<char, int> var) = 0; //#include <map>,map容器中的键值对用于保存变量名及对应的值public://以下两个成员变量是为程序跟踪调试时观察某些数据方便而引入int m_dbg_num;   //创建该对象时的一个编号,用于记录本对象是第几个创建的char m_dbg_sign; //标记本对象的类型,可能是个字符v代表变量(终结符表达式),也可能是个加减号(非终结符表达式)
};
步骤2: 实现终结符表达式

接着,创建一个或多个终结符表达式类,例如 VarExpression,它们直接与语言的终结符相对应。这些类实现了抽象表达式中定义的 interpret() 方法,返回变量在上下文中的值。

//变量表达式(终结符表达式)
class VarExpression :public Expression
{
public:VarExpression(const char& key, int num, char sign) :Expression(num, sign) //构造函数{m_key = key;}virtual int interpret(map<char, int> var){return var[m_key];  //返回变量名对应的数值}private:char m_key; //变量名,本范例中诸如a、b、c、d都是变量名
};
步骤3: 实现非终结符表达式

创建运算符表达式基类 SymbolExpression 和非终结符表达式类如 AddExpressionSubExpression 代表语言的规则。这些类通常会持有其他 Expression 对象,并在其 interpret() 方法中递归调用这些对象的 interpret() 方法,合并其结果。

//运算符表达式(非终结符表达式)父类
class SymbolExpression :public Expression
{
public:SymbolExpression(Expression* left, Expression* right, int num, char sign) :m_left(left), m_right(right), Expression(num, sign) {} //构造函数Expression* getLeft() { return m_left; }Expression* getRight() { return m_right; }
protected://左右各有一个操作数Expression* m_left;Expression* m_right;
};//加法运算符表达式(非终结符表达式)
class AddExpression :public SymbolExpression
{
public:AddExpression(Expression* left, Expression* right, int num, char sign) :SymbolExpression(left, right, num, sign) {}//构造函数virtual int interpret(map<char, int> var){//分步骤拆开写,方便理解和观察int value1 = m_left->interpret(var); //递归调用左操作数的interpret方法int value2 = m_right->interpret(var); //递归调用右操作数的interpret方法int result = value1 + value2;return result; //返回两个变量相加的结果}
};//减法运算符表达式(非终结符表达式)
class SubExpression :public SymbolExpression
{
public:SubExpression(Expression* left, Expression* right, int num, char sign) :SymbolExpression(left, right, num, sign) {}//构造函数virtual int interpret(map<char, int> var){int value1 = m_left->interpret(var);int value2 = m_right->interpret(var);int result = value1 - value2;return result; //返回两个变量相减的结果}
};
步骤4: 构建语法树

创建一个函数来分析表达式字符串并构建语法树:

//分析—创建语法树(表达式树)
Expression* analyse(string strExp) //strExp:要计算结果的表达式字符串,比如"a-b+c+d"
{stack<Expression*>  expStack;//#include <stack>,这里用到了栈这种顺序容器Expression* left = nullptr;Expression* right = nullptr;int icount = 1;for (size_t i = 0; i < strExp.size(); ++i)//循环遍历表达式字符串中的每个字符{switch (strExp[i]){case '+'://加法运算符表达式(非终结符表达式)left = expStack.top(); //返回栈顶元素(左操作数)++i;right = new VarExpression(strExp[i], icount++, 'v'); //v代表是个变量节点//在栈顶增加元素expStack.push(new AddExpression(left, right, icount++, '+')); //'+'代表是个减法运算符节点break;case '-'://减法运算符表达式(非终结符表达式)left = expStack.top(); //返回栈顶元素++i;right = new VarExpression(strExp[i], icount++, 'v');expStack.push(new SubExpression(left, right, icount++, '-')); //'-'代表是个减法运算符节点break;default://变量表达式(终结符表达式)expStack.push(new VarExpression(strExp[i], icount++, 'v'));break;} //end switch} //end forExpression* expression = expStack.top(); //返回栈顶元素return expression;
}
步骤5: 实现内存管理

添加一个函数来释放表达式树的内存:

void release(Expression* expression)
{//释放表达式树的节点内存SymbolExpression* pSE = dynamic_cast<SymbolExpression*>(expression); //此处代码有优化空间(不使用dynamic_cast),留给读者思考if (pSE){release(pSE->getLeft());release(pSE->getRight());}delete expression;
}
步骤6: 创建上下文和客户端

main 函数中创建上下文(变量映射)并使用解释器:

int main()
{string strExp = "a-b+c+d";	 //将要求值的字符串表达式map<char, int> varmap;//下面是给字符串表达式中所有参与运算的变量一个对应的数值varmap.insert(make_pair('a', 7)); //类似于赋值语句a = 7varmap.insert(make_pair('b', 9)); //类似于赋值语句b = 9varmap.insert(make_pair('c', 3)); //类似于赋值语句c = 3varmap.insert(make_pair('d', 2)); //类似于赋值语句d = 2Expression* expression = analyse(strExp);  //调用analyse函数创建语法树int result = expression->interpret(varmap); //调用interpret接口求解字符串表达式的结果cout << "字符串表达式\"a - b + c + d\"的计算结果为:" << result << endl; //输出字符串表达式结果//释放内存release(expression);return 0;
}

3. 解释器模式 UML 图

<a class=解释器模式 UML 图" />

UML 图解析

解释器模式的 UML 图中包含如下 4 种角色:

  1. AbstractExpression (抽象表达式):声明了一个抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共基类。这里指Expression类。

  2. TerminalExpression (终结符表达式):抽象表达式的子类,实现了语言文法中与终结符表达式相关的解释操作。一个句子中的每个终结符表达式都是该类的一个实例,这些实例可以通过非终结符表达式组成更为复杂的句子。这里指VarExpression类。

  3. NonterminalExpression (非终结符表达式):同样是抽象表达式的子类,实现了语言文法中与非终结符表达式相关的解释操作。考虑到非终结符表达式既可以包含终结符表达式,也可以包含其他非终结符表达式,所以其相关的解释操作一般是通过递归调用实现的。这里指AddExpressionSubExpression

    注意,引入SymbolExpression类的目的是方便AddExpressionSubExpression作为其子类的编写(方便继承),SymbolExpression类本身并不是非终结符表达式,也并不是必须存在的。

  4. Context (环境类/上下文类):用于存储解释器之外的一些全局信息,例如变量名与值的映射关系、存储和访问表达式解释器的状态等。之后这个信息会作为参数传递到所有表达式的解释操作(interpret成员函数)中作为这些解释操作的公共对象来使用。可以根据实际情况决定是否需要使用该类。这里指varmap这个map容器(虽然上述范例并没有将该容器封装到一个类中)。

4. 解释器模式的优点

  1. 易于改变和扩展文法:每个文法规则都对应一个类,可以方便地改变或扩展文法。
  2. 实现文法较为容易:每条文法规则都可以表示为一个类,因此可以直接将规则表示为代码。
  3. 增加新的解释表达式较为方便:如果需要增加新的解释表达式,只需要添加一个新的类即可。

5. 解释器模式的缺点

  1. 对于复杂文法难以维护:当文法规则数目太多时,管理这些类会变得非常困难。
  2. 执行效率较低解释器模式使用了大量的循环和递归调用,对于复杂的句子可能会导致效率问题。
  3. 可能会引起类膨胀:每个文法规则都需要一个单独的类,可能会导致系统中类的数量急剧增加。

6. 解释器模式适用场景

  1. 简单语法的语言解释器模式非常适合用于实现一些简单的、可组合的语法规则。例如,计算器程序需要解析和计算数学表达式,可以使用解释器模式来实现。
  2. 领域特定语言(DSL):在某些领域,可能需要定义一个小型的语言来描述特定的任务或行为。例如,SQL查询、正则表达式、配置文件解析等,都可以使用解释器模式来实现相应的解析和执行。
  3. 文本处理解释器模式可以用于文本处理和编译,例如编译器或解释器中的词法分析和语法分析。它可以将输入的文本转换为抽象语法树,并基于这个树结构执行相应的操作。
  4. 命令解释: 一些应用程序可能需要解析和执行命令行输入或脚本语言。解释器模式可以用来定义这些命令的语法,并提供相应的解释和执行机制。
  5. 规则引擎:在某些业务系统中,可能需要根据一系列规则来执行不同的操作。解释器模式可以用来定义这些规则的语法,并在运行时解析和执行这些规则。
  6. 编程语言的实现:实现一种新的编程语言或脚本语言时,解释器模式可以用于解析和执行语言的语法。许多简单的脚本语言和教学语言都使用解释器模式来实现。

总结

解释器模式提供了一种灵活的方式来解释特定语言的句子。它将每个文法规则封装到单独的类中,使得语言的解释变得模块化和可扩展。然而,这种模式在处理复杂语言时可能会导致类的数量激增,并且可能存在性能问题。因此,解释器模式最适合用于简单语言的解释,或者在需要频繁修改语法规则的场景中。在实际应用中,需要权衡其优点和缺点,并根据具体需求决定是否使用此模式。

完整代码

#include <iostream>
#include <cstring>
#include <map>
#include <stack>
#include <vector>using namespace std;//小表达式(节点)父类
class Expression
{
public:Expression(int num, char sign) :m_dbg_num(num), m_dbg_sign(sign) {} //构造函数virtual ~Expression() {} //做父类时析构函数应该为虚函数public://解析语法树中的当前节点virtual int interpret(map<char, int> var) = 0; //#include <map>,map容器中的键值对用于保存变量名及对应的值public://以下两个成员变量是为程序跟踪调试时观察某些数据方便而引入int m_dbg_num;   //创建该对象时的一个编号,用于记录本对象是第几个创建的char m_dbg_sign; //标记本对象的类型,可能是个字符v代表变量(终结符表达式),也可能是个加减号(非终结符表达式)
};//-----
//变量表达式(终结符表达式)
class VarExpression :public Expression
{
public:VarExpression(const char& key, int num, char sign) :Expression(num, sign) //构造函数{m_key = key;}virtual int interpret(map<char, int> var){return var[m_key];  //返回变量名对应的数值}private:char m_key; //变量名,本范例中诸如a、b、c、d都是变量名
};//------
//运算符表达式(非终结符表达式)父类
class SymbolExpression :public Expression
{
public:SymbolExpression(Expression* left, Expression* right, int num, char sign) :m_left(left), m_right(right), Expression(num, sign) {} //构造函数Expression* getLeft() { return m_left; }Expression* getRight() { return m_right; }
protected://左右各有一个操作数Expression* m_left;Expression* m_right;
};//加法运算符表达式(非终结符表达式)
class AddExpression :public SymbolExpression
{
public:AddExpression(Expression* left, Expression* right, int num, char sign) :SymbolExpression(left, right, num, sign) {}//构造函数virtual int interpret(map<char, int> var){//分步骤拆开写,方便理解和观察int value1 = m_left->interpret(var); //递归调用左操作数的interpret方法int value2 = m_right->interpret(var); //递归调用右操作数的interpret方法int result = value1 + value2;return result; //返回两个变量相加的结果}
};//减法运算符表达式(非终结符表达式)
class SubExpression :public SymbolExpression
{
public:SubExpression(Expression* left, Expression* right, int num, char sign) :SymbolExpression(left, right, num, sign) {}//构造函数virtual int interpret(map<char, int> var){int value1 = m_left->interpret(var);int value2 = m_right->interpret(var);int result = value1 - value2;return result; //返回两个变量相减的结果}
};//分析—创建语法树(表达式树)
Expression* analyse(string strExp) //strExp:要计算结果的表达式字符串,比如"a-b+c+d"
{stack<Expression*>  expStack;//#include <stack>,这里用到了栈这种顺序容器Expression* left = nullptr;Expression* right = nullptr;int icount = 1;for (size_t i = 0; i < strExp.size(); ++i)//循环遍历表达式字符串中的每个字符{switch (strExp[i]){case '+'://加法运算符表达式(非终结符表达式)left = expStack.top(); //返回栈顶元素(左操作数)++i;right = new VarExpression(strExp[i], icount++, 'v'); //v代表是个变量节点//在栈顶增加元素expStack.push(new AddExpression(left, right, icount++, '+')); //'+'代表是个减法运算符节点break;case '-'://减法运算符表达式(非终结符表达式)left = expStack.top(); //返回栈顶元素++i;right = new VarExpression(strExp[i], icount++, 'v');expStack.push(new SubExpression(left, right, icount++, '-')); //'-'代表是个减法运算符节点break;default://变量表达式(终结符表达式)expStack.push(new VarExpression(strExp[i], icount++, 'v'));break;} //end switch} //end forExpression* expression = expStack.top(); //返回栈顶元素return expression;
}void release(Expression* expression)
{//释放表达式树的节点内存SymbolExpression* pSE = dynamic_cast<SymbolExpression*>(expression); //此处代码有优化空间(不使用dynamic_cast),留给读者思考if (pSE){release(pSE->getLeft());release(pSE->getRight());}delete expression;
}int main()
{string strExp = "a-b+c+d";	 //将要求值的字符串表达式map<char, int> varmap;//下面是给字符串表达式中所有参与运算的变量一个对应的数值varmap.insert(make_pair('a', 7)); //类似于赋值语句a = 7varmap.insert(make_pair('b', 9)); //类似于赋值语句b = 9varmap.insert(make_pair('c', 3)); //类似于赋值语句c = 3varmap.insert(make_pair('d', 2)); //类似于赋值语句d = 2Expression* expression = analyse(strExp);  //调用analyse函数创建语法树int result = expression->interpret(varmap); //调用interpret接口求解字符串表达式的结果cout << "字符串表达式\"a - b + c + d\"的计算结果为:" << result << endl; //输出字符串表达式结果//释放内存release(expression);return 0;
}

http://www.ppmy.cn/news/1522063.html

相关文章

ActiViz实战:使用Actor2D画一个二维网格

文章目录 一、效果预览二、交互三、C#源码示例一、效果预览 二、交互 1、能实现等比缩放 2、不允许平移和旋转 3、能够与三维坐标大小匹配 三、C#源码示例 private void AddCudeAxes2D() {double scale =

攻防世界 unseping

unseping 攻防世界web新手练习 -unseping_攻防世界web新手题unseping-CSDN博客 这道题对我来说还是有点难&#xff0c;什么oct绕过命令执行第一次遇到捏&#xff0c;所以基本是跟着别人的wp写的&#xff0c;一点点记录吧 先对源码进行分析 <?php highlight_file(__FILE…

【HarmonyOS NEXT】实现截图功能

【HarmonyOS NEXT】实现截图功能 【需求】 实现&#xff1a;实现点击截图按钮&#xff0c;实现对页面/组件的截图 【步骤】 编写页面UI Entry Component struct Screenshot {BuildergetSnapContent() {Column() {Image().width(100%).objectFit(ImageFit.Auto).borderRadi…

【鸿蒙南向开发】OpenHarmony自定义构建函数:@Builder装饰器

前面章节介绍了如何创建一个自定义组件。该自定义组件内部UI结构固定&#xff0c;仅与使用方进行数据传递。ArkUI还提供了一种更轻量的UI元素复用机制Builder&#xff0c;Builder所装饰的函数遵循build()函数语法规则&#xff0c;开发者可以将重复使用的UI元素抽象成一个方法&a…

项目7-音乐播放器7(测试报告)

1.项目背景 音乐播放器采用前后端分离的方法来实现&#xff0c;基于SSM框架构建&#xff0c;同时使用了数据库来存储相关的数据&#xff0c;同时将其部署到云服务器上。 用户可以轻松注册登录&#xff0c;浏览丰富的音乐库&#xff0c;搜索喜欢的歌曲。系统支持多种音频格式播…

【EI会议截稿通知】第九届计算机技术与机械电气工程国际学术论坛(ISCME 2024)

第九届计算机技术与机械电气工程国际学术论坛&#xff08;ISCME 2024&#xff09; 2024 9th International Seminar on Computer Technology, Mechanical and Electrical Engineering 会议信息 大会官网&#xff1a;www.is-cme.com 一轮截稿时间&#xff1a;2024年9月30日&a…

java把文字转MP3语音案例

一 工具下载&#xff1a; https://download.csdn.net/download/jinhuding/89723540 二代码 <dependency><groupId>com.hynnet</groupId><artifactId>jacob</artifactId><version>1.18</version> </dependency>import com.ja…

UEFI——使用标准C库

一、C标准库 C标准库是ANSL C标准为C语言定义的标准库。C标准库包含15个头文件&#xff1a;assert.h ctype.h error.h float.h limits.h locale.h math.h setjmp.h signal.h stdarg.h stddef.h stdio.h stdlib.h string.h time.h。标准库函数与C语言的紧密结合给我们开发程序带…

本地Docker部署Navidrome音乐服务器与远程访问听歌详细教程

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

springblade-JWT认证缺陷漏洞CVE-2021-44910

漏洞成因 SpringBlade前端通过webpack打包发布的&#xff0c;可以从其中找到app.js获取大量接口 然后直接访问接口&#xff1a;api/blade-log/api/list 直接搜索“请求未授权”&#xff0c;定位到认证文件&#xff1a;springblade/gateway/filter/AuthFilter.java 后面的代码…

力扣接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表…

15 用户管理

如果我们只能使用root用户&#xff0c;这样存在安全隐患。这时&#xff0c;就需要使用mysql的用户管理 张三只能操纵mytest这个库&#xff0c;李四只能操纵msg这个库&#xff0c;如果给他们root账户&#xff0c;就可以操纵所有库&#xff0c;风险太大 用户 用户信息 用户都存…

AI基础 L4 Uninformed Search I 无信息搜索

Problem-solving agent • Deterministic, fully observable ⇒ single-state problem Agent knows exactly which state it will be in; solution is a sequence • Non-observable ⇒ conformant problem Agent may have no idea where it is; solution (if any) is a sequen…

数据库系统 第40节 数据库安全策略

数据库安全策略是确保数据库系统安全、防止数据泄露和未授权访问的关键措施。以下是一些常见的数据库安全策略&#xff0c;以及它们在实际应用中的一些示例。 1. 访问控制 访问控制是数据库安全的基础&#xff0c;它确保只有授权用户才能访问数据库资源。这通常通过以下方式实…

【开源免费】基于SpringBoot+Vue.JS高校校园招聘服务系统(JAVA毕业设计)

本文项目编号 T 010 &#xff0c;文末自助获取源码 \color{red}{T010&#xff0c;文末自助获取源码} T010&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

利用Stable Diffusion AI图像模型评估智能车模型算法表现(下篇)

今天小李哥将介绍亚马逊云科技的Jupyter Notebook机器学习托管服务Amazon SageMaker上&#xff0c;通过AI图像生成模型Stable Diffusion Upscale和Depth、向量知识库和LangChain Agent&#xff0c;生成用于AI 智能车模型训练的图像数据集并评估模型表现。 本系列共分为上下两篇…

Math Reference Notes: 三角函数术语的几何学解释

在三角函数中&#xff0c;“正”、“余”、“弦”、"割"这些词汇源自古代的几何学术语&#xff0c;它们与三角形的边和角的关系密切相关。 1. 弦&#xff08;sin&#xff0c;cos的含义&#xff09;&#xff1a; “弦”字来源于圆中的“弦线”&#xff0c;即连接圆周…

VUE2.0 elementUI el-input-number 数据更新,视图不更新——基础积累

今天遇到一个问题&#xff0c;是关于el-input-number组件的&#xff0c;发现数据明明已经更改了&#xff0c;但是页面上组件输入框中还是之前的值。 比如上方输入框中&#xff0c;我输入120.5&#xff0c;就会出现下面的诡异现象 回显此值是120.779&#xff0c;但是页面上输入…

小阿轩yx-云原生存储Rook部署Ceph

小阿轩yx-云原生存储Rook部署Ceph 前言 Rook 一款云原生存储编排服务工具由云原生计算基金会&#xff08;CNCF&#xff09;孵化&#xff0c;且于2020年10月正式进入毕业阶段。并不直接提供数据存储方案&#xff0c;而是集成了各种存储解决方案&#xff0c;并通过一种自管理、…

log4j2 与 log4j使用时的几点小区别 - log4j2上手说明

虽然log4j2 目前还是beta版&#xff0c;不过OneCoder已经忍不住要尝试一下。跟使用log4j 比起来&#xff0c;上手上主要的区别有。 1、依赖的jar包。使用slf4jlog4j2 时&#xff0c;依赖的jar包如下&#xff1a;( gradle配置&#xff0c;Maven对照修改即可) dependencies{ com…