四则混合运算表达式分析程序的原理及其实现

原创 2007年09月26日 09:55:00

意义: 四则混合运算表达式可以看作一定语言中的表达式分析及求值,虽然它很小,却是一个语法分析的很好的例子!

一、目标:可以对输入的四则混合运算表达式进行分析,并求出其结果。程序须支持:整数及小数运算、支持加(+),减(-),乘(*),除(/),技术括号嵌套。

二、原理:表达式的分析过程可以看作三步,(1)分析出表达式中各个标记,包括数值、括号以及操作符,将这些标记按顺序存放起来;(2)根据步骤1所得的标记,根据四则运算规则将其转换成一棵语法树(二叉树);(3)使用后序遍历步骤2所得语法树,按操作进行求值,得出最后结果。程序的整个核心在于语法树如何建立。

三、语法树的建立:
如:10+(5+9)/3-7*3可以解析得如下语法树:
 
使用后序遍历方式对语法树进行遍历,可以得出其值; 
尽管上面的语法树是正确的,构建上面的语法树却不是那么简单,因为在此过程中,我们需要考虑:如何使得一对括号内的数值作为一个整体优先被计算? 如果只是按操作符的优先级进行比较,那么我们很可能得出错误的结论。比如上述的表达式换成“10+(5+9)+7*3”,在构建完“10+(5+9)+”的语法树后,此时所得的树根是“10+(”中的“+”,它的运算优先级比“7*3”中的“*”的优先级要低,如果只是按照运算符优先级规则,我们很可能得一棵错误的语法树!因此,我们要简化对问题的思考方式,如何将一个有括号的表达式转换成一个没有括号的表达式,这样,我们只须考虑操作符的优先级就可以创建正确的语法树了!方法很简单,我们首先将没有嵌套的括号内的表达式进行解析,然后对它求值,将其结果作为一个数值结点替换原来的括号表达式,如果括号外部有括号,则按上述方法重复进行,直到去掉所有括号为止。如上面的“10+(5+9)/3+(7*3)”可以转换成:“10+14/3+21”,这样就很容易创建语法表达式了。
测试:
 
核心代码如下:
//-----------------------------------------------------------------------------
// 语法解析器的实现:
//-----------------------------------------------------------------------------

// 对一个语法树进行求值
double SyntaxParser::Eval(SYNTAX_TREE tr, int &step, FILE* pf)
{
  
double rslt = 0.0;

  
if (0 == tr)  return rslt;

  
if (tr->token->GetType() == Token::NUM) 
  
{
    
return (static_cast<Number*>(tr->token))->value;
  }


  
double lsh = Eval(tr->lchild, step, pf);
  
double rsh = Eval(tr->rchild, step, pf);
  Operator 
*pop = static_cast<Operator*>(tr->token);
  rslt 
= Operator::Calc(lsh, rsh, pop->value);

  
// 写入计算过程
  fprintf(pf, 
    
"%04d.  %-20.6f %-10c %-20.6f = %-20.6f ",
    step,
    lsh, 
    static_cast
<char>(pop->value), 
    rsh, 
    rslt);
  
++step;
  
return rslt;
}




//----------------------------------------------------------------------------
// 对输入标记进行解析并求值
//   为简化解析过程,整个表达式可以看作是只有操作符和数值构成的标记表,
//  括号内的表达式可以在解析后进行求值,将所得的结果作为一个数值加点替
// 换原来的括号表达式。因此,整过解析过程中只需考虑操作符的优先级,遇
// 到括号时,作为一棵子树的新解析开始。表达式中只有+,-,*,/ 这个四个操作符。
// +和-以及*和/它们的优先级是分别相等的。*、/和优先级高于+、-。
//----------------------------------------------------------------------------
double SyntaxParser::Solve(
                           TokenTable 
&table,  // 标记表
                           TreeStack &stack,   // 语法树栈
                           EvalFactory &fac,   // 标记和树结点工厂
                           FILE *pf            // 计算过程记录文件
                           )

  
if (table.GetCount()==0
  
{
    
throw Exception("输入表达式不能为空!");
  }


  typedef Token::TokenType TokenType;
  SYNTAX_TREE tree 
= 0;           // 语法树
  int parSignal = 0;              // 括号配对信号量,解析结束时若parSignal须为0
  int step = 1;                   // 计算步骤
  Token* prevTok = 0;             // 前一标记
  Token* firstTok = table[0];     // 第一个标记
  Token* lastTok = table[table.GetCount()-1]; // 最后一个标记

  
// 首先检查首尾是否正确
  if ((Token::OPER==firstTok->GetType() || Token::OPER==lastTok->GetType())
    
|| ((Token::LRP==firstTok->GetType() 
    
&& (static_cast<Parenthesis*>(firstTok))->value!=Parenthesis::LP))
    
|| ((Token::LRP==lastTok->GetType() 
    
&& (static_cast<Parenthesis*>(lastTok))->value!=Parenthesis::RP)))
  
{
    
throw Exception("无效表达式! 开头或结尾有误!");
  }



  
// 从标记表中提取每个标记,生成语法树
  for (int i=0; i<table.GetCount(); ++i)
  
{
    Token
* token = table[i];

    
if (token->GetType()==Token::LRP) // 如果输入标记是括号
    {
      Parenthesis
* pp = static_cast<Parenthesis*>(token);
      
if (Parenthesis::LP == pp->value)  // 当前标记是一个左括号
      {
        
if (prevTok && prevTok->GetType() != Token::OPER
          
&& (Token::LRP==prevTok->GetType() 
          
&&(static_cast<Parenthesis*>(prevTok))->value!=Parenthesis::LP))
        
{
          
throw Exception("无效表达式:'('之前只能为操作符或'('");
        }


        
++parSignal; // 信号量递增
        stack.Push(tree);
        tree 
= 0;
      }

      
else // 当前标记是一个右括号
      {
        
if (prevTok && (prevTok->GetType()==Token::OPER
          
|| (prevTok->GetType()==Token::LRP 
          
&& (static_cast<Parenthesis*>(prevTok))->value==Parenthesis::LP)))
        
{
          
throw Exception("无效表达式:')'之前只能为数或')'");
        }


        
if (parSignal == 0// 检查左右括号是否匹配
          throw Exception("无效表达式:'('和')'不匹配!");
        }


        
// 将已解析的括号内的子树进行计算,将结果作为一个数值结点插入到语法树中
        SYNTAX_TREE_NODE *pnode = fac.CreateSyntaxTreeNode();
        Number 
*pnum = static_cast<Number*>(fac.CreateToken(Token::NUM));
        pnum
->value = Eval(tree, step, pf);
        pnode
->lchild = pnode->rchild = 0;
        pnode
->token = pnum;

        SYNTAX_TREE tr 
= (stack.IsEmpty() ? 0 : stack.Pop());
        
if (tr) 
        
{
          
if (tr->rchild) {
            tr
->rchild->rchild = pnode;
          }

          
else {
            tr
->rchild = pnode;
          }

          tree 
= tr;
        }

        
else {
          tree 
= pnode;
        }


        
--parSignal; // 信号量递减
      }

    }

    
else // 如果输入标记是操作符或数值
    {
      SYNTAX_TREE_NODE 
*pnode = fac.CreateSyntaxTreeNode();
      pnode
->lchild = pnode->rchild = 0;
      pnode
->token = token;

      
if(token->GetType() == Token::OPER) // 如果输入标记是操作符
      {
        
// 判断上下文是否有效
        if (prevTok->GetType()==Token::OPER
          
|| (prevTok->GetType()==Token::LRP 
          
&& (static_cast<Parenthesis*>(prevTok))->value!=Parenthesis::RP))
        
{
          
throw Exception("无效表达式:操作符之前只能是')'或一个数!");
        }


        Operator 
*pop = static_cast<Operator*>(token);
        
if (tree->token->GetType() == Token::NUM) // 当前树根结点的类型为数
        {
          pnode
->lchild = tree;
          tree 
= pnode;
        }

        
else  // 当前树根结点的类型操作符
        {
          
// 如果当前节点优先级高于当前树根结点的优先级, 替换右子树
          if (pop->Compare(static_cast<const Operator&>(*tree->token)) > 0)
          
{
            pnode
->lchild = tree->rchild;
            tree
->rchild = pnode;
          }

          
else
          
{
            pnode
->lchild = tree;
            tree 
= pnode;
          }

        }

      }

      
else // 如果当前标记是数值
      {
        
if (prevTok && ((prevTok->GetType()==Token::LRP
          
&& (static_cast<Parenthesis*>(token))->value==Parenthesis::RP)
          
|| prevTok->GetType() == Token::NUM))
        
{
          
throw Exception("无效表达式: 数之前只能是操作符或'('!");
        }


        
if (0 == tree)
        
{
          tree 
= pnode;
        }

        
else
        
{
          SYNTAX_TREE tr 
= tree;
          
if (tr->rchild) {
            tr
->rchild->rchild = pnode;
          }
 
          
else {
            tr
->rchild = pnode;
          }

        }

      }

    }

    
// 记录当前标记, 因为解析过程是上下文相关的
    prevTok = token;
  }


  
// 检查左右括号数是否匹配
  if (parSignal != 0
    
throw Exception("'('和')'的个数不匹配!");
  }


  
// 若树栈不为空,则依次弹出一个树tree, 将root作为tree的右孩子,
  
// 然后将tree保存到树根root;
  while(!stack.IsEmpty())
  
{
    SYNTAX_TREE tr 
= stack.Pop();
    
if (tr) {
      tr
->rchild = tree;
      tree 
= tr;
    }

  }


  
return Eval(tree, step, pf);
}
 
关于分析程序的具体实现,请下载代码:http://download.csdn.net/source/341787

实现整数四则运算表达式的词法分析程序

  • 2009年06月03日 07:37
  • 1KB
  • 下载

编译原理---四则运算表达式的计算简单实现

编译原理---四则运算表达式的计算简单实现
  • u010251278
  • u010251278
  • 2016年09月05日 19:17
  • 3438

lambda表达式的解析(四) 运算符表达式

一元、二元、三元操作符的转换大多都非常有规律,先看这个一元树: 主要就是一个操作符跟一个表达式,转换起来很简单: private Expression ProcessUnaryExp...
  • guoyichao
  • guoyichao
  • 2011年08月01日 15:02
  • 2267

c# 四则混合运算算法

对于四则混合运算其实在大三的时候就接触过,但是由于当时自己太菜,只顾着玩游戏跳课了。所以这个算法一直没有用代码实现过。当时学霸还给我简单的讲了一下方法,只因身为学渣的我,只想对学霸说一句,道理我都懂,...
  • u012058778
  • u012058778
  • 2015年12月13日 02:10
  • 2445

表达式设计原理

 表达式,是由操作数、运算符、分组符号(括号)、自由变量和约束变量等以能求得数值的有意义排列方法所得的组合。约束变量在表达式中已被指定数值,而自由变量则可以在表达式之外另行指定数值。根据运算...
  • star_of_light
  • star_of_light
  • 2016年05月13日 11:42
  • 251

UML中的四种关系

依赖(Dependency) 表示一个类使用(Use )另一个类,是四种关系中最弱的一种关系 依赖关系是单向的 例如动物依赖水,汽车依赖汽油等 具体代码有三种形式(被依赖的类作为局部变量) wat...
  • yuan13826915718
  • yuan13826915718
  • 2016年09月10日 13:35
  • 371

JAVA实现简单四则混合运算

java实现加减乘除混合运算, http://www.oschina.net/code/snippet_189899_36597 这个算法很好, 加法 加号前面的加上加号后面的  减法 减...
  • su12345su
  • su12345su
  • 2015年09月16日 11:59
  • 2726

抽象语法树

转自:http://blog.chinaunix.net/uid-26750235-id-3139100.html (一)简介 抽象语法树(abstract syntax code,AST)是源代...
  • xiaolei1982
  • xiaolei1982
  • 2016年09月01日 11:18
  • 1602

本人转载 在此申明 语法树 c 实现 四则运算语法树

  • 2010年03月31日 08:45
  • 102KB
  • 下载

语法树解析

何为语法树 什么是语法树? 你是否曾想过,这个世界存在这么多语言的意义。 假如现在你面前有一个物体,它是一个不规则的圆体,整个身体通红,头部还有一根细长稍微弯曲偏右呈棕色...
  • chao_624
  • chao_624
  • 2016年12月05日 18:53
  • 3785
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:四则混合运算表达式分析程序的原理及其实现
举报原因:
原因补充:

(最多只允许输入30个字)