StarWing83 发表于 2008-5-9 02:53

[原创]一个支持四则运算的类

啊……本来想用LR文法的,但是在状态量增加到三十多个以后,只好十分沮丧地放弃了………………LR文法版本的还在想办法。先发个随便写写的版本吧……实现的功能很简单,而且自定义运算符的功能还没有测试……所以大家帮帮忙来找Bug吧………………注释是后来补的Orz……反正写得很详细啦,大家可以来看看……

使用返回值而不是异常返回错误,改掉了几个Bug,明天来分离一目操作符的逻辑,今天就这样吧……
看样子,甚至可以把Sin,Cos这样的函数都加进去呢……

[quote][font=新宋体][size=2][color=#FF0000]#include <iostream>
[/color][color=#FF0000]#include <string>
[/color][color=#FF0000]#include <stack>
[/color][color=#FF0000]#include <sstream>
[/color][color=#0000FF]using namespace [/color][color=#FF0000]std[/color];

[color=#008000]//状态枚举,由主函数及StackCalc返回
[/color][color=#0000FF]enum [/color]Status
[color=#800000]{
    [/color]sOK                                =[color=#800080]0[/color],[color=#008000]//正常返回
    [/color]eDivByZero                =[color=#800080]1[/color],[color=#008000]//被零除
    [/color]eNumError                =[color=#800080]2[/color],[color=#008000]//操作数错误
    [/color]eOptError                =[color=#800080]3[/color],[color=#008000]//运算符错误
    [/color]eInvalidToken        =[color=#800080]4[/color],[color=#008000]//非法字符
[/color][color=#800000]}[/color];

[color=#008000]//运算符信息“配件”,用来规定所支持的运算符的数目,优先级,以及计算方法。
//所有“配件”都必须包含以下两个函数
[/color][color=#0000FF]struct [/color]CCalcInfo
[color=#800000]{
    [/color][color=#008000]//取得运算符优先级,如不是运算符,返回0,数字越大优先级越低
    [/color][color=#0000FF]static int [/color][color=#008080]GetOpt[/color]([color=#0000FF]char [/color]op)
    [color=#800000]{
        [/color][color=#0000FF]switch [/color](op)
        [color=#800000]{
        [/color][color=#0000FF]case [/color][color=#FF8000]'*'[/color]:
        [color=#0000FF]case [/color][color=#FF8000]'/'[/color]:[color=#0000FF]return [/color][color=#800080]1[/color];
        [color=#0000FF]case [/color][color=#FF8000]'+'[/color]:
        [color=#0000FF]case [/color][color=#FF8000]'-'[/color]:[color=#0000FF]return [/color][color=#800080]2[/color];
        [color=#800000]}
        [/color][color=#0000FF]return [/color][color=#800080]0[/color];
    [color=#800000]}

    [/color][color=#008000]//根据堆栈栈顶的操作数及运算符计算
    [/color][color=#0000FF]static [/color]Status [color=#008080]StackCalc[/color]([color=#FF0000]stack[/color]<[color=#0000FF]char[/color]>& os,[color=#FF0000]stack[/color]<[color=#0000FF]double[/color]>& ds)
    [color=#800000]{
        [/color][color=#0000FF]if [/color](os.[color=#008080]empty[/color]())[color=#0000FF]return [/color]eOptError;
        [color=#0000FF]if [/color](ds.[color=#FF8000]size[/color]() < [color=#800080]2[/color])[color=#0000FF]return [/color]eNumError;

        [color=#0000FF]double [/color]rv=ds.[color=#008080]top[/color]();ds.[color=#FF8000]pop[/color]();
        [color=#0000FF]switch [/color](os.[color=#008080]top[/color]())
        [color=#800000]{
        [/color][color=#0000FF]case [/color][color=#FF8000]'+'[/color]:ds.[color=#008080]top[/color]()+=rv;[color=#0000FF]break[/color];
        [color=#0000FF]case [/color][color=#FF8000]'-'[/color]:ds.[color=#008080]top[/color]()-=rv;[color=#0000FF]break[/color];
        [color=#0000FF]case [/color][color=#FF8000]'*'[/color]:ds.[color=#008080]top[/color]()*=rv;[color=#0000FF]break[/color];
        [color=#0000FF]case [/color][color=#FF8000]'/'[/color]:[color=#0000FF]if [/color](rv==[color=#800080]0[/color])[color=#0000FF]return [/color]eDivByZero;ds.[color=#008080]top[/color]()/=rv;[color=#0000FF]break[/color];
        [color=#0000FF]default[/color]:[color=#0000FF]return [/color]eOptError;
        [color=#800000]}
        [/color]os.[color=#FF8000]pop[/color]();
        [color=#0000FF]return [/color]sOK;
    [color=#800000]}

}[/color];

[color=#008000]//运算类
[/color][color=#0000FF]template[/color]<[color=#0000FF]class [/color]TInfo>[color=#008000]//TInfo定义了操作符和计算方法
[/color][color=#0000FF]struct [/color]CCalculateT : TInfo
[color=#800000]{
    [/color][color=#0000FF]using [/color]TInfo::GetOpt;
    [color=#0000FF]using [/color]TInfo::StackCalc;

    [color=#008000]//为方便使用,定义的运算符重载
    [/color][color=#0000FF]void operator[/color]()([color=#0000FF]const char[/color]* str)
    [color=#800000]{
        [/color][color=#0000FF]double [/color]ans;

        [color=#0000FF]switch [/color]([color=#008080]DoCalc[/color](str,ans))
        [color=#800000]{
        [/color][color=#0000FF]case [/color]sOK:[color=#FF0000]cout[/color]<<ans;[color=#0000FF]break[/color];
        [color=#0000FF]case [/color]eDivByZero:[color=#FF0000]cout[/color]<<[color=#FF00FF]"被零除"[/color];[color=#0000FF]break[/color];
        [color=#0000FF]case [/color]eNumError:[color=#FF0000]cout[/color]<<[color=#FF00FF]"操作数错误"[/color];[color=#0000FF]break[/color];
        [color=#0000FF]case [/color]eOptError:[color=#FF0000]cout[/color]<<[color=#FF00FF]"运算符错误"[/color];[color=#0000FF]break[/color];
        [color=#0000FF]case [/color]eInvalidToken:[color=#FF0000]cout[/color]<<[color=#FF00FF]"非法字符"[/color];[color=#0000FF]break[/color];
        [color=#800000]}
        [/color][color=#FF0000]cout[/color]<<[color=#FF0000]endl[/color];
    [color=#800000]}

    [/color][color=#008000]//运算主程序
    [/color][color=#0000FF]static [/color]Status [color=#008080]DoCalc[/color]([color=#0000FF]const char[/color]* str,[color=#0000FF]double[/color]& ans)
    [color=#800000]{
        [/color][color=#FF0000]stack[/color]<[color=#0000FF]double[/color]> ds;[color=#008000]//操作数栈
        [/color][color=#FF0000]stack[/color]<[color=#0000FF]char[/color]> os;[color=#008000]//符号栈
        [/color]istringstream [color=#008080]iss[/color](str);[color=#008000]//输入流

        [/color][color=#0000FF]bool [/color]bNeedNum=true;[color=#008000]//当前是否需要数字(比如操作符和括号后)
        [/color][color=#0000FF]for [/color]([color=#0000FF]char [/color]ch;iss>>ch;)
        [color=#800000]{
            [/color][color=#0000FF]if [/color]([color=#008080]isdigit[/color](ch) || ch==[color=#FF8000]'.'[/color])[color=#008000]//如果为数字直接入栈
            [/color][color=#800000]{
                [/color][color=#0000FF]double [/color]fv;
                iss.[color=#008080]unget[/color]();iss>>fv;
                ds.[color=#FF8000]push[/color](fv);[color=#008000]//数字入栈
                //计算取负操作符
                [/color][color=#0000FF]while [/color](!os.[color=#008080]empty[/color]() && os.[color=#008080]top[/color]() == [color=#FF8000]'!'[/color])
                    ds.[color=#008080]top[/color]()=-ds.[color=#008080]top[/color](),os.[color=#FF8000]pop[/color]();
                bNeedNum=false;[color=#008000]//不可连续出现两个数字
            [/color][color=#800000]}
            [/color][color=#0000FF]else if [/color](ch == [color=#FF8000]'('[/color])
            [color=#800000]{
                [/color][color=#0000FF]if [/color](bNeedNum == false)[color=#0000FF]return [/color]eOptError;[color=#008000]//这时应该是期待操作数的
                [/color]os.[color=#FF8000]push[/color](ch);[color=#008000]//括号入栈
                [/color]bNeedNum=true;[color=#008000]//括号后允许出现数字
            [/color][color=#800000]}
            [/color][color=#0000FF]else if [/color](ch == [color=#FF8000]')'[/color])
            [color=#800000]{
                [/color][color=#0000FF]if [/color](bNeedNum == true)[color=#0000FF]return [/color]eOptError;[color=#008000]//这时应该不期待操作数
                //遇到反括号,持续出栈直到遇到相应括号
                [/color][color=#0000FF]while [/color](!os.[color=#008080]empty[/color]() && os.[color=#008080]top[/color]()!=[color=#FF8000]'('[/color])
                [color=#800000]{
                    [/color]Status s=[color=#008080]StackCalc[/color](os,ds);
                    [color=#0000FF]if [/color](s!=sOK)[color=#0000FF]return [/color]s;
                [color=#800000]}
                [/color][color=#0000FF]if [/color](os.[color=#008080]empty[/color]())[color=#0000FF]return [/color]eOptError;[color=#008000]//如果找不到相应括号,出错
                [/color]os.[color=#FF8000]pop[/color]();[color=#008000]//括号出栈,即消掉了一对括号
                //如果有取负操作符,计算
                [/color][color=#0000FF]while [/color](!os.[color=#008080]empty[/color]() && os.[color=#008080]top[/color]() == [color=#FF8000]'!'[/color])
                    ds.[color=#008080]top[/color]()=-ds.[color=#008080]top[/color](),os.[color=#FF8000]pop[/color]();
                bNeedNum=false;[color=#008000]//反括号后不允许出现数字
            [/color][color=#800000]}
            [/color][color=#0000FF]else if [/color]([color=#008080]GetOpt[/color](ch))[color=#008000]//如果是操作符
            [/color][color=#800000]{
                [/color][color=#008000]//如果不需要出现数字,并且操作符栈非空
                //并且栈顶元素不是括号,并且栈顶元素的优先级高于当前运算符
                [/color][color=#0000FF]if [/color](!bNeedNum && !os.[color=#008080]empty[/color]() && [color=#008080]GetOpt[/color](os.[color=#008080]top[/color]())
                        && [color=#008080]GetOpt[/color](os.[color=#008080]top[/color]())<=[color=#008080]GetOpt[/color](ch))
                [color=#800000]{
                    [/color][color=#008000]//计算前一计算
                    [/color]Status s=[color=#008080]StackCalc[/color](os,ds);
                    [color=#0000FF]if [/color](s!=sOK)[color=#0000FF]return [/color]s;
                [color=#800000]}
                [/color][color=#0000FF]else if [/color](bNeedNum)[color=#008000]//如果需要数字,可以认为这时出现的是正负号
                [/color][color=#800000]{
                    [/color][color=#0000FF]if [/color](ch == [color=#FF8000]'-'[/color])[color=#008000]//如果为负号
                        [/color]ch=[color=#FF8000]'!'[/color];[color=#008000]//栈内数字取反,s是自定义的取反操作符
                    [/color][color=#0000FF]else if [/color](ch == [color=#FF8000]'+'[/color])[color=#008000]//如果是正号
                        [/color][color=#0000FF]continue[/color];[color=#008000]//无视掉
                    [/color][color=#0000FF]else return [/color]eOptError;[color=#008000]//其他字符,出错。
                [/color][color=#800000]}
                [/color]bNeedNum=true;[color=#008000]//操作符后期待操作数
                [/color]os.[color=#FF8000]push[/color](ch);[color=#008000]//压入操作符
            [/color][color=#800000]}
            [/color][color=#0000FF]else if [/color](![color=#008080]isspace[/color](ch))[color=#008000]//非空白字符,出错
                [/color][color=#0000FF]return [/color]eInvalidToken;
        [color=#800000]}
        [/color][color=#0000FF]while [/color](!os.[color=#008080]empty[/color]())
        [color=#800000]{
            [/color][color=#008000]//计算栈内存留数字,直到操作符栈为空
            [/color]Status s=[color=#008080]StackCalc[/color](os,ds);
            [color=#0000FF]if [/color](s != sOK)[color=#0000FF]return [/color]s;
        [color=#800000]}
        [/color][color=#0000FF]if [/color](ds.[color=#FF8000]size[/color]()!=[color=#800080]1[/color]) [color=#0000FF]return [/color]eNumError;[color=#008000]//如果操作数栈内还存有数字,出错。
        [/color]ans = ds.[color=#008080]top[/color]();[color=#008000]//返回计算结果
        [/color][color=#0000FF]return [/color]sOK;
    [color=#800000]}
}[/color];


[color=#0000FF]int [/color][color=#FF0000]main[/color]([color=#0000FF]void[/color])
[color=#800000]{
    [/color][color=#0000FF]char [/color]str[color=#800000][[/color][color=#800080]1001[/color][color=#800000]][/color];
    [color=#0000FF]typedef [/color]CCalculateT<CCalcInfo> CCalc;[color=#008000]//使用四则运算符的运算类
    [/color][color=#0000FF]while [/color]([color=#FF0000]putchar[/color]([color=#FF8000]'>'[/color]),[color=#FF0000]gets[/color](str))[color=#008080]CCalc[/color]()(str);
    [color=#0000FF]return [/color][color=#800080]0[/color];
[color=#800000]}
[/color][/size][/font][/quote]

[[it] 本帖最后由 StarWing83 于 2008-6-14 00:51 编辑 [/it]]

中学者 发表于 2008-5-9 07:56

异常抛出最好不要用内建类型,可能会出现捕获的值意义模糊///学习下,准备建立自己的数据结构库///
PS:仅次建议,见笑了..[tk03]

中学者 发表于 2008-5-9 08:59

没编译器,再漫漫看看

[[it] 本帖最后由 中学者 于 2008-5-9 09:01 编辑 [/it]]

秋之爱 发表于 2008-5-9 10:34

我 用 c写了 一 个…………
计算器得+,-,*,/都可以用得
还可以用()

雨中飛燕 发表于 2008-5-9 11:14

我不喜欢用异常,比较喜欢return ERROR_NUMBER

[img]http://blog.programfan.com/upfile/200804/20080430094836.gif[/img][color=white]

StarWing83 发表于 2008-5-9 16:11

异常有个好处,第一个是和返回值完全分开。所以就算是throw 0 都绝不会意义模糊:因为只有你扔异常嘛。然后可以在一个调用树里面随便扔,这样就不需要一层层的if(...)return ....了,最后,异常作为内部的消息传送使用(就是这种啦),再有上面的好处的同时,因为对外封闭,也不太容易看出来。当然,上面的程序不是很好,异常捕获应该在类的内部被封装,而不应该暴露在外面。这个我等会儿改改……
其实异常效率并不算很低。异常的实现方式是注册一个中断向量,然后在throw的时候触发中断。而且如果只是在错误的时候才异常的话,是不会影响到通常工作的效率的~~~

雨中飛燕 发表于 2008-5-9 16:46

那是内存泄露的危机

[img]http://blog.programfan.com/upfile/200804/20080430094836.gif[/img][color=white]

★红狼 发表于 2008-5-9 16:53

[原创]一个支持四则运算的类
啊……本来想用LR文法的,但是在状态量增加到三十多个以后,只好十分沮丧地放弃了………………LR文法版本的还在想办法。先发个随便写写的版本吧……实现的功能很简单,而且自定义运算符的功能还没有测试……


LZ提到 LR 是什么啊.?
四则运算 是不是这样? 1+2*3/4+5-6...这样的连续运算?

sunkaidong 发表于 2008-5-9 16:55

翅膀兄弟我帮你顶个

我帮你弄个栈,用我们自己的啊..呵呵
#include<iostream>
#include<vector>
#include<string>
using namespace std;
template <class T>
class istack
{
      public:
      istack( int cap ): _capacity( cap ), _top(0) {}

      bool pop();
      bool push(T value);

      bool full();
      bool empty();
      void display();

      int size();
      private:
          vector<T>::size_type _top;
          int _capacity;
          vector<T> _stack;
};
      template <class T>
      inline int istack<T>::size(){return _capacity;};

          template <class T>
      inline bool istack<T>::empty()
      {return _top?false:true;}

      template <class T>
      inline bool istack<T>::full()
      {return _top<size()-1?false:true;}

       template <class T>
      bool istack<T>::pop()
      {
           if(empty())
           return false;
           T i=_stack.at(_top-1);
           _stack.pop_back();
           cout<<"istack:pop():"<<i<<endl;
           _top--;
           return true;
      }

      template <class T>
      bool istack<T>::push(T value)
      {
           
           if(full())
           return false;
           _top++;
           _stack.push_back(value);
           cout<<"istack::push()("<<value<<")\n";
           return true;
      }
      template <class T>
      void istack<T>::display()
      {
           if(!size())
           {cout<<"(0)\n";return;}
           cout<<"("<<size()<<")(bot:";
           for(vector<T>::size_type ix=_top-1;ix>0;ix--)
           cout<<_stack.at(ix)<<" ";
           cout<<":top)\n";
      }

中学者 发表于 2008-5-9 17:18

[quote][bo]以下是引用 [un]StarWing83[/un] 在 2008-5-9 16:11 的发言:[/bo]

异常有个好处,第一个是和返回值完全分开。所以就算是throw 0 都绝不会意义模糊:因为只有你扔异常嘛。然后可以在一个调用树里面随便扔,这样就不需要一层层的if(...)return ....了,最后,异常作为内部的消息传送使用(就是这种 ... [/quote]
顶一下,对于我说 的问题,那是因为你即是设计者又是使用者,所以体现不出来....呵呵//

StarWing83 发表于 2008-5-9 17:26

不,中学者我知道你的意思。你的意见是对的,直接扔int的确含义模糊。我正在改。我的想法是只是在类内部扔异常。这个异常的过程对使用者来说是透明的,他只需要cout<<CCalc("12+(12+4)");。至于是错误还是结果交给类来处理了……

飞燕是什么意思呢?不是很明白……扔异常本身会造成内存泄露吗?如果是的话我就改成返回值好了……因为设计了接口,如果要返回值的话会很麻烦……

中学者 发表于 2008-5-9 17:30

抛异常会造成内存泄露的啊~~~

sunkaidong 发表于 2008-5-9 17:39

中学为什么?会有内存泄露..如果说是因为用new 产生对象返回指针,没有释放好理解...如果不是动态创建怎么泄露呢?

StarWing83 发表于 2008-5-9 17:43

是啊,不太明白,我已经没有改了,中学者先说说,估计会取消掉异常机制了……哎,那样的话接口机制也不能用了——因为要跨三层函数返回错误……

sunkaidong 发表于 2008-5-9 18:02

异常的思路无非是下面:
一,设计异常类,把可能错误都包括在异常里面;excption ;
二,设计正常类,当发现数据违例的时候,用throw exception()(1);
三,在正常使用过程try{}catch( exception e){}(2);
1和2的关系是什么?是引用还是副本?传递机制是什么呢?知道的说下..呵呵

中学者 发表于 2008-5-9 18:07

在函数内发生异常可以在两个地方处理,一个是throw然后在函数外的try块catch捕获;另一种直接在函数体内设置try块catch捕获.....往往可能出现内存泄露的就是第一种---通过throw的方式..
  一旦throw,则直接跳出函数体,这个时候会释放函数体内的对象(如果对象尚未构建完成则不会析构释放,这种往往是自己重写new的时候会发生).
下面来段小代码可以看出内存泄露了(实际编程肯定不会这样写):
[code]
class cat{
public:
cat(){ cout<<"creat cat"<<endl;}
~cat(){cout<<"destroy cat"<<endl;}
};
class fate
{
   cat* p;
public:
   fate():p(0){ p=new cat[5]; throw 2; }
   ~fate() { delete [] p; }
};
int main(void)
{
  try{
    fate f;
     }
    catch(int){
      cout<<"oh my god!"<<endl;
     }
   return 0;
}
[/code]

中学者 发表于 2008-5-9 18:10

今天刚好搞异常,sunkai兄的那个是副本....所以用异常我认为也有效率的考虑~

sunkaidong 发表于 2008-5-9 18:14

晕..呵呵..忘记异常是强行打破正常执行顺序了...后面的执行过程都被异常屏蔽了..java用习惯了...没注意这个问题

中学者 发表于 2008-5-9 18:16

现在在开始写自己的DataStruction ,慢慢更新///呵呵[tk05]

c_acceleration 发表于 2008-5-9 18:25

case 0:cout<<"被零除";break;  //这条语句一定可以捕捉么?

页: [1] 2

编程论坛