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

原创 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

相关文章推荐

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

中缀/后缀表达式转换-使用四则混合运算表达式生成树

很多资料讨论了四则混合运算的中缀表达式和后缀表达式的变换方法,有的很简单,有的很复杂,然而这种简单和复杂只是表现在程序上的,肉眼观察加人工操作完成这样的任何实际上十分简单,因为人脑习惯于于用自己的方式...
  • dog250
  • dog250
  • 2011年12月10日 14:52
  • 5794

前中后缀表达式与表达式树

以下是一个简单的表达式直接转二叉树的算法//表达式树的节点类 typedef struct _node_{ union{ char opt; double va...

一个非常酷的Js计算器(加减乘除,浮点,以及括号运算,四则混合运算)

这是一个功能比较强大的计算机 运算法 包括了 加减乘除以及括号 整个过程是面向对象实现的 支持四则,浮点,括号,复合运算,可扩展性高,可定制性高...

JAVA实现简单四则混合运算

java实现加减乘除混合运算, http://www.oschina.net/code/snippet_189899_36597 这个算法很好, 加法 加号前面的加上加号后面的  减法 减...

四则混合运算C++代码(中缀表达式)

在复习算法的时候,栈的应用举例中有一项就是计算中缀表达式的四则混合运算。根据算法自己写了一下程序。 1、程序分为两块,一部分中缀转成后缀,一部分计算后缀。其中使用到了堆栈,我自己做了一个简单地模板栈...

四则运算在计算机中的实现原理

一、中缀表达式需要转换成后缀表达式,转换算法: 1、遇到操作数:直接输出(添加到后缀表达式中) 2、栈为空时,遇到运算符:直接入栈 3、遇到左括号:将其入栈 4、遇到右括号:执行出栈操作,并将出栈的元...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

利用堆栈进行四则运算

我们正常的四则运算在计算机里是并不被识别的,因此想要利用程序实现四则运算就需要先将我们熟悉的中缀表达式转化为无需括号的后缀表达式。这个后缀表达式是由波兰的逻辑学家JAN提出,也称为逆波兰式。 ...

四则运算表达式求值(栈的应用)

1.前/中/后缀表达式的转换(首先需要明白三者之间的转换)      自然表达式转换为前/中/后缀表达式,其实是很简单的。首先将自然表达式按照优先级顺序,构造出与表达式相对应的二叉树,然后对二叉树...
  • yzl_rex
  • yzl_rex
  • 2012年07月14日 10:22
  • 14203
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:四则混合运算表达式分析程序的原理及其实现
举报原因:
原因补充:

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