利用栈先入先出这种”对称性“,我们可以自己做一个计算器(支持'+'、'-'、'*'、'/'、'^'、'()')。主要思路是维护运算栈的单调递增,运算栈的单调递增指的是完全保持从左到右由低到高的运算顺序,维护同级运算符,避免“头重脚轻”,运算错误。

比如我们要计算“1+2-3/4^5-6",从人的视角,我们先给4取指数为“1+2-3/1024-6”,然后由于'/'的优先级比'+'、'-'高,我们后进行除法操作为”1+2-0.0029296875-6“,发现运算栈操作符都是同级,最后计算得到结果”-3.9970703125"。但是从栈的视角来看:我们需要先维护两个栈,一个是数字栈,存数字;一个是运算栈,存操作符。数字栈存入1、2、3后,运算栈先后存入'+'、'-',从第二个运算符'-'开始判断左右两边运算符等级,发现高级'/',于是继续压栈,只要高级,那就压栈,直到发现第二个'-'后,为了维护运算栈的单调递增性,不能压栈了只能弹栈,依此进行合并直到左边运算符等级<=右边运算符等级,周而复始,遍历整个输入表达式。

了解了运算栈原理,我们可以创建一个Calc计算器类,来实现生活中的计算器。思路就是输入一个表达式input,判断表达式有没有非法字符,非法的话就错误输入,然后就是截取数字压进数字栈,配合运算栈遍历表达式,获取结果最后刷新界面。考虑到数字类型的多样性,决定使用double来统一数字,初始化显示输入和显示输出为0,里面有很多辅助函数,需读者自行理解。

#include<iostream>
#include<string>
#include<stack>   //双栈维护计算器 
#include<map>    //运算符等级 
#include<cctype> //判断是否为字符 
#include<cmath>  // pow
using namespace std;

/*设计一个计算机类*/ 
class Calc
{
public:
	/*简单的显示界面*/ 
    void UI()
    {
        cout << '\n';	
        cout << "=========================================" << endl;
        cout << "|              Dotcpp科学计算器         |" << endl;
        cout << "=========================================" << endl;
        cout << "|                                       |" << endl;
        cout << "|输入为:【"<< input << "】";
        for(int i = 0; i < 27 - input.size(); ++i) cout << " ";
        cout << "|\n";
        cout << "|结果为:【"<< output << "】";
        for(int i = 0; i < 27 - output.size(); ++i) cout << " ";
        cout << "|\n";
        cout << "|                                       |" << endl;
        cout << "|                                0:EXIT|" << endl;
        cout << "=========================================" << endl;
    }
    
    /*-------------辅助函数-------------begin*/ 
    
    /*是不是运算符*/
    bool is_op(char c) 
    {
    	/*string::npos是string的无效位置*/ 
        return oper.find(c) != string::npos; 
    }
    
    /*获取输入*/
    void get_input(const string& s)
    {
        input = s;
        f(); // 获取输入后自动计算
    }
    
     /*判断是否应该计算*/
    bool is_OK(char op, char stack_op) {
        if(stack_op == '(') return false;  // 左括号不计算
        return mp[op] <= mp[stack_op];  // 当前运算符优先级小于等于栈顶运算符时计算
    }
    
    /*执行计算*/
    /*保证数字栈至少有两个数&&至少有一个操作符*/ 
    bool compute(stack<double>& s1, stack<char>& s2) {
        if(s1.size() < 2 || s2.empty()) {
            output = "ERROR: 错误输入";
            return false;
        }
        
        char op = s2.top();
        s2.pop();
        
        double b = s1.top();
        s1.pop();
        double a = s1.top();
        s1.pop();
        
        double result = 0;
        switch(op) {
            case '+': result = a + b; break;
            case '-': result = a - b; break;
            case '*': result = a * b; break;
            case '/': 
                if(b == 0) {
                    output = "ERROR: 被除数不能为0";
                    return false;
                }
                result = a / b; 
                break;
            case '^': result = pow(a, b); break;
            default: 
                output = "ERROR: 错误输入";
                return false;
        }
        
        s1.push(result);
        return true;
    }
    
    /*-------------辅助函数-------------end*/ 
    
    /*双栈计算主函数*/
    void f() 
    {
        stack<double> s1;    // 数字栈
        stack<char> s2;       // 运算符栈
        
        // 验证输入字符是否合法
        for(char c : input) {
            if(c == ' ') continue;
            if(!isdigit(c) && !is_op(c) && c != '.') {
                output = "ERROR: 错误输入";
                return;
            }
        }
        
        /*开始遍历输入*/ 
        for(int i = 0; i < input.size(); ++i) 
        {
            char c = input[i];
            
            // 跳过空格
            if(c == ' ') continue;
            
            // 如果是数字,读取完整数字 
            if(isdigit(c) || c == '.') 
            {
                string num_str = "";
                while(i < input.size() && (isdigit(input[i]) || input[i] == '.')) {
                    num_str += input[i];
                    i++;
                }
                i--;  // 回退一个字符
                /*转数字并压栈*/
        double num = stod(num_str);
        s1.push(num);
            }
            // 如果是左括号
            else if(c == '(') 
            {
                s2.push(c);
            }
            // 如果是右括号
            else if(c == ')') 
            {
                while(!s2.empty() && s2.top() != '(') {
                    if(!compute(s1, s2)) return; // 计算失败直接返回
                }
                if(!s2.empty()) s2.pop();  // 弹出左括号
                else {
                    output = "ERROR: 括号不对称";
                    return;
                }
            }
            // 如果是运算符
            else if(is_op(c)) 
            {
                while(!s2.empty() && is_OK(c, s2.top())) {
                    if(!compute(s1, s2)) return; // 计算失败直接返回
                }
                s2.push(c);
            }
        }
        
        // 处理剩余的运算符
        while(!s2.empty()) {
            if(s2.top() == '(') {
                output = "ERROR:输入错误";
                return;
            }
            if(!compute(s1, s2)) return; // 计算失败直接返回
        }
        
        // 获取结果
        if(!s1.empty() && s1.size() == 1) {
            ans = s1.top();
            output = to_string(ans);
            // 移除末尾多余的0
            while(output.back() == '0') output.pop_back();
            if(output.back() == '.') output.pop_back();
        } else {
            output = "ERROR:输入错误";
        }
    }
    
private:
    string input = "0";
    double ans = 0;
    string output = "0";
    string oper = "+-*/^()";
    
    /*对运算符进行分级*/ 
    map<char, int> mp{
        {'+', 1}, {'-', 1}, 
        {'*', 2}, {'/', 2}, 
        {'^', 3}, 
        {'(', 0}, {')', 0}//特殊字符 
    };
};

void test()
{
    Calc calc;
    string s;//输入 
    bool key = true;//控制循环 
    
    while(key) 
    {
        calc.UI();
        getline(cin, s); 
        
        if(s == "0") {
            key = false;
            cout << "欢迎下次使用";
        } else {
            system("cls");
            calc.get_input(s);
        }
    }
}

int main() {
    test();
    return 0;
}

简单输入几个表达式试试结果:

输入“1+2-3/4^5-6”后:

1+2-3/4^5-6

让我们来检测一下报错情况:

1. 违规字符

违规字符'`'

2. 不对称括号

不对称括号

3./0情况

/0

4. 残缺表达式

残缺表达式

就这样,我们实现了一个DIY计算器,读者是否很有成就呢,快去试试吧!

点赞(0)

C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:

一点编程也不会写的:零基础C语言学练课程

解决困扰你多年的C语言疑难杂症特性的C语言进阶课程

从零到写出一个爬虫的Python编程课程

只会语法写不出代码?手把手带你写100个编程真题的编程百练课程

信息学奥赛或C++选手的 必学C++课程

蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程

手把手讲解近五年真题的蓝桥杯辅导课程

Dotcpp在线编译      (登录可减少运行等待时间)