StarWing83 发布留言 2008-5-9 02:53 [原创]一个支持四则运算的类啊……本来想用LR文法的,但是在状态量增加到三十多个以后,只好十分沮丧地放弃了………………LR文法版本的还在想办法。先发个随便写写的版本吧……实现的功能很简单,而且自定义运算符的功能还没有测试……所以大家帮帮忙来找Bug吧………………注释是后来补的Orz……反正写得很详细啦,大家可以来看看……
使用返回值而不是异常返回错误,改掉了几个Bug,明天来分离一目操作符的逻辑,今天就这样吧…… 看样子,甚至可以把Sin,Cos这样的函数都加进去呢……
#include #include #include #include using namespace std;
//状态枚举,由主函数及StackCalc返回 enum Status { sOK =0,//正常返回 eDivByZero =1,//被零除 eNumError =2,//操作数错误 eOptError =3,//运算符错误 eInvalidToken =4,//非法字符 };
//运算符信息“配件”,用来规定所支持的运算符的数目,优先级,以及计算方法。 //所有“配件”都必须包含以下两个函数 struct CCalcInfo { //取得运算符优先级,如不是运算符,返回0,数字越大优先级越低 static int GetOpt(char op) { switch (op) { case '*': case '/':return 1; case '+': case '-':return 2; } return 0; }
//根据堆栈栈顶的操作数及运算符计算 static Status StackCalc(stack<char>& os,stack<double>& ds) { if (os.empty())return eOptError; if (ds.size() < 2)return eNumError;
double rv=ds.top();ds.pop(); switch (os.top()) { case '+':ds.top()+=rv;break; case '-':ds.top()-=rv;break; case '*':ds.top()*=rv;break; case '/':if (rv==0)return eDivByZero;ds.top()/=rv;break; default:return eOptError; } os.pop(); return sOK; }
};
//运算类 template<class TInfo>//TInfo定义了操作符和计算方法 struct CCalculateT : TInfo { using TInfo::GetOpt; using TInfo::StackCalc;
//为方便使用,定义的运算符重载 void operator()(const char* str) { double ans;
switch (DoCalc(str,ans)) { case sOK:cout; case eDivByZero:cout<<"被零除";break; case eNumError:cout<<"操作数错误";break; case eOptError:cout<<"运算符错误";break; case eInvalidToken:cout<<"非法字符";break; } cout<<endl; }
//运算主程序 static Status DoCalc(const char* str,double& ans) { stack<double> ds;//操作数栈 stack<char> os;//符号栈 istringstream iss(str);//输入流
bool bNeedNum=true;//当前是否需要数字(比如操作符和括号后) for (char ch;iss>>ch;) { if (isdigit(ch) || ch=='.')//如果为数字直接入栈 { double fv; iss.unget();iss>>fv; ds.push(fv);//数字入栈 //计算取负操作符 while (!os.empty() && os.top() == '!') ds.top()=-ds.top(),os.pop(); bNeedNum=false;//不可连续出现两个数字 } else if (ch == '(') { if (bNeedNum == false)return eOptError;//这时应该是期待操作数的 os.push(ch);//括号入栈 bNeedNum=true;//括号后允许出现数字 } else if (ch == ')') { if (bNeedNum == true)return eOptError;//这时应该不期待操作数 //遇到反括号,持续出栈直到遇到相应括号 while (!os.empty() && os.top()!='(') { Status s=StackCalc(os,ds); if (s!=sOK)return s; } if (os.empty())return eOptError;//如果找不到相应括号,出错 os.pop();//括号出栈,即消掉了一对括号 //如果有取负操作符,计算 while (!os.empty() && os.top() == '!') ds.top()=-ds.top(),os.pop(); bNeedNum=false;//反括号后不允许出现数字 } else if (GetOpt(ch))//如果是操作符 { //如果不需要出现数字,并且操作符栈非空 //并且栈顶元素不是括号,并且栈顶元素的优先级高于当前运算符 if (!bNeedNum && !os.empty() && GetOpt(os.top()) && GetOpt(os.top())<=GetOpt(ch)) { //计算前一计算 Status s=StackCalc(os,ds); if (s!=sOK)return s; } else if (bNeedNum)//如果需要数字,可以认为这时出现的是正负号 { if (ch == '-')//如果为负号 ch='!';//栈内数字取反,s是自定义的取反操作符 else if (ch == '+')//如果是正号 continue;//无视掉 else return eOptError;//其他字符,出错。 } bNeedNum=true;//操作符后期待操作数 os.push(ch);//压入操作符 } else if (!isspace(ch))//非空白字符,出错 return eInvalidToken; } while (!os.empty()) { //计算栈内存留数字,直到操作符栈为空 Status s=StackCalc(os,ds); if (s != sOK)return s; } if (ds.size()!=1) return eNumError;//如果操作数栈内还存有数字,出错。 ans = ds.top();//返回计算结果 return sOK; } };
int main(void) { char str[1001]; typedef CCalculateT CCalc;//使用四则运算符的运算类 while (putchar('>'),gets(str))CCalc()(str); return 0; } [/quote]
[ 本帖最后由 StarWing83 于 2008-6-14 00:51 编辑 [/it]]中学者 发布留言 2008-5-9 07:56 异常抛出最好不要用内建类型,可能会出现捕获的值意义模糊///学习下,准备建立自己的数据结构库/// PS:仅次建议,见笑了..[tk03]中学者 发布留言 2008-5-9 08:59 没编译器,再漫漫看看
[ 本帖最后由 中学者 于 2008-5-9 09:01 编辑 [/it]]秋之爱 发布留言 2008-5-9 10:34 我 用 c写了 一 个………… 计算器得+,-,*,/都可以用得 还可以用()雨中飛燕 发布留言 2008-5-9 11:14 我不喜欢用异常,比较喜欢return ERROR_NUMBER
 StarWing83 发布留言 2008-5-9 16:11 异常有个好处,第一个是和返回值完全分开。所以就算是throw 0 都绝不会意义模糊:因为只有你扔异常嘛。然后可以在一个调用树里面随便扔,这样就不需要一层层的if(...)return ....了,最后,异常作为内部的消息传送使用(就是这种啦),再有上面的好处的同时,因为对外封闭,也不太容易看出来。当然,上面的程序不是很好,异常捕获应该在类的内部被封装,而不应该暴露在外面。这个我等会儿改改…… 其实异常效率并不算很低。异常的实现方式是注册一个中断向量,然后在throw的时候触发中断。而且如果只是在错误的时候才异常的话,是不会影响到通常工作的效率的~~~雨中飛燕 发布留言 2008-5-9 16:46 那是内存泄露的危机
 ★红狼 发布留言 2008-5-9 16:53 [原创]一个支持四则运算的类 啊……本来想用LR文法的,但是在状态量增加到三十多个以后,只好十分沮丧地放弃了………………LR文法版本的还在想办法。先发个随便写写的版本吧……实现的功能很简单,而且自定义运算符的功能还没有测试……
LZ提到 LR 是什么啊.? 四则运算 是不是这样? 1+2*3/4+5-6...这样的连续运算?sunkaidong 发布留言 2008-5-9 16:55 翅膀兄弟我帮你顶个我帮你弄个栈,用我们自己的啊..呵呵 #include #include #include using namespace std; template 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::size_type _top; int _capacity; vector _stack; }; template inline int istack::size(){return _capacity;};
template inline bool istack::empty() {return _top?false:true;}
template inline bool istack::full() {return _top template bool istack::pop() { if(empty()) return false; T i=_stack.at(_top-1); _stack.pop_back(); cout<<"istack:pop():"< _top--; return true; }
template bool istack::push(T value) { if(full()) return false; _top++; _stack.push_back(value); cout<<"istack::push()("< return true; } template void istack::display() { if(!size()) {cout<<"(0)\n";return;} cout<<"("< for(vector::size_type ix=_top-1;ix>0;ix--) cout<<_stack.at(ix)<<" "; cout<<":top)\n"; }中学者 发布留言 2008-5-9 17:18 [quote]以下是引用 [un]StarWing83[/un] 在 2008-5-9 16:11 的发言:[/bo]
异常有个好处,第一个是和返回值完全分开。所以就算是throw 0 都绝不会意义模糊:因为只有你扔异常嘛。然后可以在一个调用树里面随便扔,这样就不需要一层层的if(...)return ....了,最后,异常作为内部的消息传送使用(就是这种 ... |
顶一下,对于我说 的问题,那是因为你即是设计者又是使用者,所以体现不出来....呵呵//StarWing83 发布留言 2008-5-9 17:26 不,中学者我知道你的意思。你的意见是对的,直接扔int的确含义模糊。我正在改。我的想法是只是在类内部扔异常。这个异常的过程对使用者来说是透明的,他只需要cout< 飞燕是什么意思呢?不是很明白……扔异常本身会造成内存泄露吗?如果是的话我就改成返回值好了……因为设计了接口,如果要返回值的话会很麻烦……中学者 发布留言 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"<~cat(){cout<<"destroy cat"<}; 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!"< } 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] 下一页 特别说明:如网页特效代码中有引用图片文件等,请自己下载到本地调试! |