利用栈先入先出这种”对称性“,我们可以自己做一个计算器(支持'+'、'-'、'*'、'/'、'^'、'()')。主要思路是维护运算栈的单调递增,运算栈的单调递增指的是完全保持从左到右由低到高的运算顺序,维护同级运算符,避免“头重脚轻”,运算错误。
比如我们要计算“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./0情况
4. 残缺表达式
就这样,我们实现了一个DIY计算器,读者是否很有成就呢,快去试试吧!
C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:
一点编程也不会写的:零基础C语言学练课程
解决困扰你多年的C语言疑难杂症特性的C语言进阶课程
从零到写出一个爬虫的Python编程课程
只会语法写不出代码?手把手带你写100个编程真题的编程百练课程
信息学奥赛或C++选手的 必学C++课程
蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程
手把手讲解近五年真题的蓝桥杯辅导课程