命令行计算器(一)

习惯在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;
}

进行了一些测试,基本正确,也有可能有些地方有问题,希望大家指正。

测试过程中有个问题不知道怎么解决,就是输入的时候,不能使用方向键,因为一般习惯输入一对括号,然后用方向键继续输入括号中的内容,在这里却没有办法使用方向键,只能使用退格键。希望那个大家指教。

原文地址:https://www.cnblogs.com/Frandy/p/simple_command_line_calculator_v1.html