命令行计算器(一)
习惯在Matlab里面计算,不太喜欢系统自带的计算器,但Matlab每次启动都比较慢。对于简单的计算,用不着Matlab,一直希望有一个简单版本的Matlab。一次看到OSChina上有分享一个简单的命令行计算器,觉得这个不错,不过功能仅限于数字的四则运算,于是想着可不可以写一个功能在稍微强一点的命令行计算器。于是就有了这个项目,称之为Command Line Calculator(CLC)。
希望实现的功能,
1. 数值运算,整数和浮点数;
2. 变量赋值,表达式运算;
3. 数学函数;
4. 其他。
网上已经有些计算器或者是数学表达式运算的程序了,上面的功能也有人实现了的。所以我在做一边,一是练习一下编程,另一方面实现自己喜欢的风格。
当然,也没打算一下子就把它做完,计划是三个阶段,逐步实现上面的功能。目前已经实现了最基本的功能,数值运算。下面总结一下实现过程。
我觉得实现过程中有两个问题,一个是数值的提取,整数和浮点数。这里采用stringstream提取数值。读取输入表达式的时候,运算符或者括号是很容易识别的,就是字符比较,当字符不是运算符的时候,把字符放入stringstream,当遇到运算符的时候,就可以把stringstream里面的数值取出来,并把stringstream清空。另一个问题是数值的计算,因为运算符有优先级,还包括括号。网上有两种方式实现,一种是二叉树表示数学表达式,另一种是使用堆栈。我的实现过程中使用堆栈。用两个堆栈,一个存放数值,一个存放运算符。如果当前字符是(,那么直接将其放入运算符堆栈,如果是),那么将运算符堆栈进行计算,直到遇到(,两个括号匹配相消,如果是其它运算符,则比较这个运算符和上一个运算符之间的优先级关系,如果这次优先,则把这次的运算法放入堆栈,因为它还有一个操作数在右边还没有读到,如果上一个运算符的优先级高,那么可以完成上一次的运算。
需要注意的是,因为是对输入表达式逐个字符处理,当处理最后一个字符时,如果是数字,需要将最后一个数字放入数值堆栈。并且一般情况下,数值堆栈和运算符堆栈不为空,需要继续进行计算。
最终代码如下,
/* cmd_calculator.cpp * a simple implementation of cmd calculator using stack * author: Frandy.CH * data: 2012-05-03 */ /* ref: * http://kb.cnblogs.com/a/1211638/ * http://www.oschina.net/code/snippet_190747_6535 * more advanced: * http://lony1107.blog.51cto.com/374424/78727 * http://blog.sina.com.cn/s/blog_67a1dd830100of48.html * http://www.chinaitpower.com/A/2002-02-12/13734.html */ /* * operator allowed: ( ) + - * / * assign operator different level, ( ) -1, + - 0, * / 1. * evaluation condition: * the current operator is not "", which is the end sign * the num in the num stack more than 2 * the level of operator at the stack top is higher than the operatot to be push into stack * so there are two stacks, one is for number, the other is for operator */ #include <iostream> #include <string> #include <sstream> #include <stack> using namespace std; // 输出程序的简单描述 void description() { cout << " Command Line Calculator v1.0" << endl; cout << " Author: Frandy.CH" << endl; cout << " This is a very simple command line calculator,\n which only support +,-,*,/,(,) and numbers, including int and float." << endl; cout << " You can input expression after the promt \'>\'" << endl; cout << " And you can exit by input \'q\' or \"exit\"" << endl; } // 输出提示符 void promt() { cout << ">"; } // 输出推出程序提示信息 void bye() { cout << "Bye." << endl; } // 比较当前运算符和上一次运算符的优先级 int cmpop(char c1,char c2) { switch(c1) { case '-': case '+': return (c2=='(')?0:1; break; case '/': case '*': if(c2=='*' || c2=='/') return 1; else return 0; break; default: cout << "error:unknown op" << endl; } } // 利用stringstream提取数值放入数值堆栈 void getNum(stringstream& ss, stack<double>& value) { double st; ss >> st; ss.clear(); value.push(st); } // 取数值堆栈前两个数,和运算符堆栈的运算符进行一次运算,运算结果仍放回数值堆栈 void calcuOnce(stack<char>& op,stack<double>& value) { if(value.size()<2 || op.empty()) return; double v1 = value.top(); value.pop(); double v2 = value.top(); value.pop(); char a = op.top(); op.pop(); double v3 = 0; // cout << "to calculate once char " << c << ":" << v2 << a << v1 << endl; switch(a) { case '+': v3 = v2 + v1; break; case '-': v3 = v2 - v1; break; case '*': v3 = v2 * v1; break; case '/': v3 = v2 / v1; break; default: cout << "error:unknown op" << endl; } value.push(v3); } // 对输入的数值表达式进行计算 double calculate(string inexp) { int i = 0; char c; stack<char> op; stack<double> value; stringstream ss; double st = 0; for(i=0;i<inexp.length();i++) { c = inexp[i]; // 如果当前字符是运算符 if(c=='(' || c==')' || c=='+' || c=='-' || c=='*' || c=='/') { // 提取运算符前的数值 if(!ss.eof()) getNum(ss,value); // 左括号,将(放入运算符堆栈 if(c=='(' || op.empty()) op.push(c); // 右括号,进行计算,直到与左括号匹配相消 else if(c==')') { while(op.top()!='(') calcuOnce(op,value); op.pop(); } // 一般运算符,比较与上一次运算符的优先级关系,如果上一次优先级高,完成上一次运算 else if(cmpop(c,op.top())) { calcuOnce(op,value); op.push(c); } // 将运算符放入运算符堆栈 else op.push(c); } // 当前字符不是运算符,应该是数字或小数点 else { ss << c; // 如果是处理最后一个字符,并且最后一个字符是数字,将最后一个数值提取出来,并完成剩余所有运算 if(i==inexp.size()-1) { getNum(ss,value); while(!op.empty()) calcuOnce(op,value); } } } return value.top(); } int main() { description(); while(1) { promt(); string inexp; // 读取一行数值表达式 getline(cin,inexp,'\n'); // 如果没有输入,继续提示输入 if(inexp.empty()) continue; // 在输入q或者exit时推出程序 else if(inexp=="q" || inexp=="exit") break; // 对输入的数值表达式进行计算 else { double res = calculate(inexp); cout << "\t" << res << endl; } } bye(); return 0; }
进行了一些测试,基本正确,也有可能有些地方有问题,希望大家指正。
测试过程中有个问题不知道怎么解决,就是输入的时候,不能使用方向键,因为一般习惯输入一对括号,然后用方向键继续输入括号中的内容,在这里却没有办法使用方向键,只能使用退格键。希望那个大家指教。