目录
计算逻辑
思路
- 可以考虑将算术表达式先转为后缀,再用后缀进行结果的计算
- 也可以直接在算术表达式中求值
(但因为老师的要求,要用中缀转后缀的方法,所以这里就只介绍第一种)
(悄咪咪)其实第二种我也没研究过,不知道难不难(跪)
确定优先级
因为中缀转后缀是需要按照优先级先后来确定对运算符号的处理的,所以,首先先把优先级确定好
这里我用的是map,数字越高,优先级越高
- 这样可以通过传入的符号,不用判断,可以直接拿到它对应的优先级
- (当然同一优先级中还是得一个一个判断)
代码
void Widget::confirm_priority(map<string, int>& comp) { comp.insert(make_pair("=", -1)); comp.insert(make_pair("||", 1)); comp.insert(make_pair("&&", 2)); comp.insert(make_pair("==", 3)); comp.insert(make_pair("!=", 3)); comp.insert(make_pair(">", 4)); comp.insert(make_pair("<", 4)); comp.insert(make_pair("<=", 4)); comp.insert(make_pair(">=", 4)); comp.insert(make_pair("+", 5)); comp.insert(make_pair("-", 5)); comp.insert(make_pair("*", 6)); comp.insert(make_pair("/", 6)); comp.insert(make_pair("%", 6)); comp.insert(make_pair("!", 7)); }
中缀转后缀
这个在之前栈里提到过,但只是写了思路,当时没有写代码
基本上就是这么个思路,遇到同/低优先级的,就开始出栈中存放的运算符号,存进后缀表达式中
除此之外,还要注意,因为要实现的运算还挺多的,里面就有两个字符的运算符号,需要特殊处理
为什么要用栈?
我个人感觉是为了方便进行括号匹配
- 因为括号是从左到右的,如果是栈,就可以从右到左完成括号内的遍历
- 但要是队列?似乎不太对劲
- 如果有多个括号,那就会让外层的括号先出去,这样不符合运算逻辑了
- 但也可以用队列,只不过要倒腾一下
括号的处理
- 左括号不用管
- 遇到右括号就开始处理括号里面的符号
- (可以把括号内的表达式看作一个独立的中缀,遇到右括号就相当于遍历完成,所以要把栈中符号出完)
代码
vector<string> Widget::to_suffix(string arr, map<string, int>& comp,string& bug) { string numbers("0123456789"); vector<string> ans; //存放后缀 stack<string> cal;//存放符号 for (auto i = 0; i < arr.size(); ++i) { if (arr[i] >= '0' && arr[i] <= '9') { auto pos = arr.find_first_not_of(numbers, i); string tmp_arr(arr.begin() + i, arr.begin() + pos); ans.push_back(tmp_arr); i = pos - 1; //因为这次用掉的是数字,pos位置不是数字, 且for要i++ } else { // 符号 if (arr[i]=='-'&&arr[i + 1] >= '0' && arr[i + 1] <= '9') { //处理负数 string tmp;tmp += arr[i]; auto pos = arr.find_first_not_of(numbers, i+1); //i+1是为了跳过那个负号 string tmp_arr(arr.begin() + i+1, arr.begin() + pos); ans.push_back(tmp+tmp_arr); //负号也要添加进去 i = pos-1; continue; } string sym; sym += arr[i]; if (((arr[i] == '>' || arr[i] == '<' || arr[i] == '=' || arr[i] == '!') && arr[i + 1] == '=')||(arr[i] == '|'&&arr[i + 1] == '|')|| (arr[i] == '&' && arr[i + 1] == '&')) //有些符号是两个字符,需要处理 { ++i; sym += arr[i]; } if (sym == "(") //左括号直接进cal,不需要其他步骤 { cal.push(sym); continue; } else if (sym == ")") //右括号就需要开始处理了 { while (cal.top() != "(") { ans.push_back(cal.top());//把括号内的符号直接进(因为是符合规则的) cal.pop(); } cal.pop(); //记得把左括号出掉 } else { while (!cal.empty()) { string top = cal.top(); if (comp[sym] > comp[top]) { break; } else { ans.push_back(top); cal.pop(); } } cal.push(sym);//栈为空,直接进 } } } while (!cal.empty()) //将剩余的符号直接插入ans { ans.push_back(cal.top()); cal.pop(); } return ans; }
结果计算
就着后缀计算就很简单了,优先级已经被排好了,只需要遇到符号就取栈顶两个数字(或者一个,!是单目运算符)
还有就是要处理一下负数
代码
int Widget::work(vector<string>& tokens, map<string, int>& comp) { std::stack<int> s; int num = 0; for (auto c : tokens) { if (c == "=") { break; } if (comp[c] != 0) // 是符号 { int ans = 0; int ret = comp[c]; if (comp[c] != 7) { // 拿到运算数据 int a = s.top(); s.pop(); // 先拿到的是右 int b = s.top(); s.pop(); // 然后是左 if (ret == 1) { ans = b || a; } else if (ret == 2) { ans = b && a; } else if (ret == 3) { if (c == "==") { ans = b == a; } else { ans = b != a; } } else if (ret == 4) { if (c == ">") { ans = b > a; } else if (c == ">=") { ans = b >= a; } else if (c == "<") { ans = b < a; } else { ans = b <= a; } } else if (ret == 5) { if (c == "+") { ans = b + a; } else { ans = b - a; } } else if (ret == 6) { if (c == "*") { ans = b * a; } else if (c == "/") { if(a!=0) ans = b / a; else{ ui->lineEdit_2->insert("除数不能为0"); return (size_t)-1; } } else { ans = b % a; } } } else { // 拿到运算数据 int a = s.top(); s.pop(); // 单目 ans = !a; } s.push(ans); // 结果入栈 } else { // 是数字 -- 要string转int,然后数字入栈 if (c[0] == '-') { c.erase(0,1); num = stoi(c); num = -num; } else { num = stoi(c); } s.push(num); } } return s.top(); // 最后一个元素就是结果 }
界面
思路
qt我没系统学过,就是看了一下别人写的代码,然后照猫画虎写出来
感觉挺简单的,界面是直接做好的,你只需要在界面后面加上一点需要的代码就行
比如:我在界面上按了数字1:
void Widget::on_pushButton_clicked() { ui->lineEdit->insert("1"); Q += '1'; }
它里面是要定义一个qstring类型的变量,用来保存输入的字符
按了1之后,就在输入框里显示1,然后把"1"插入到Q里面就行
其他字符也是一样的逻辑
清除
void Widget::on_pushButton_23_clicked() { ui->lineEdit_2->clear();//清空输出框 }
回退
void Widget::on_pushButton_12_clicked() { ui->lineEdit->backspace();//删除lineEdit中一个元素 Q=Q.left(Q.length()-1); }
=
void Widget::on_pushButton_22_clicked() { ui->lineEdit->insert("="); if(Q.size() == 0) { ui->lineEdit_2->insert("你还未输入数字,无法计算");//异常处理,提醒用户输入错误 } else { Q += '='; char *s = Q.toLocal8Bit().data();//QString 转 char*; map<string, int> comp; confirm_priority(comp); string arr,bug; arr+=s; vector<string> ret = to_suffix(arr, comp,bug); //ui->lineEdit_2->insert(bug.c_str()); int ans=work(ret, comp); if(ans==(size_t)-1){ Q=""; } else{ QString str = QString::number(ans,10);//int 转化成 QString ui->lineEdit_2->insert(str); Q=""; } } }
(里面那个bug变量是我用来调试的,因为我不会在qt里面调试(呜呜呜))
=的时候,就说明表达式已经输入完毕,就要开始进行计算逻辑了
最后把结果插入到计算器的输出框即可
完整的代码已经上传到资源绑定那里了,有需要的自己拿就行