[问题描述]
一个算术表达式是由操作数(operand)、运算符(operator)和界限符(delimiter)组成的。假设操作数是正实数,运算符只含加减乘除等四种运算符,界限符有左右括号和表达式起始、结束符“#”,如:#6+15*(21-8/4)#。引入表达式起始、结束符是为了方便。编程利用“运算符优先法”求算术表达式的值。
[基本要求]
(1)从键盘或文件读入一个合法的算术表达式,输出正确的结果。
(2)显示输入序列和栈的变化过程。
(3)考虑算法的健壮性,当表达式错误时,要给出错误原因的提示。
(4)实现非整数的处理(*)。
[基本思路]
有一说一,这道题的计算部分并不难,只要跟着书上的思路写就可以了,由于我偷懒,选择采用系统栈函数(主要是我一开始没看见需要遍历),这就导致我无法遍历栈内元素,所以我还建立了两个同步数组用于遍历。
主要麻烦的是要求(3)考虑算法的健壮性,当表达式错误时,要给出错误原因的提示。这意味着你要将所有的输入错误找出来,就很麻烦,我是找到了以下几种:
bool check(string s)
{
switch (check_(s))
{
case -1:return true;
case 0: {
cout << "请按格式输入‘#’!!!" << endl;//#1+1
break;
}
case 1: {
cout << "表达式禁止为空!!!" << endl;//##
break;
}
case 2: {
cout << "操作符两端必须有数!!!" << endl;//#*1#,#1*#
break;
}
case 3: {
cout << "反扣号前没有与之匹配的正括号!!!" << endl;//#)1+1(#
break;
}
case 4: {
cout << "禁止多个操作符相连!!!" << endl;//#1++1#
break;
}
case 5: {
cout << "禁止含有非数非运算符的符号!!!(中文括号也不行)" << endl;//###,#ad#
break;
}
case 6: {
cout << "小数点后禁止接操作符!!!" << endl; //#1.+1#
break;
}
case 7: {
cout << "一个数内不能有多个小数点!!!" << endl;//#1.1.1#
break;
}
case 8: {
cout << "括号个数不匹配!!!" << endl;//#(1+1#
break;
}
case 9: {
cout << "小数点前必须要有数字!!!" << endl;//#.1#
break;
}
case 10: {
cout << "小数点后必须要有数字!!!" << endl;//#1..1#
break;
}
case 11: {
cout << "括号内禁止为空!!!" << endl;//#()#
break;
}
}
system("pause");
system("cls");
return false;
}
再就是除数不能为0,因为括号的原因,这个只能在运算过程中判断,所以上面的代码中没有,例子是#5/((2+1)-3)#。值得一提的是,由于计算机对数的存储方式,这里的3-3!= 0,而是等于一个极小的数,所以在判断的时候需要改一下。
if (x <= 8.88178e-15 &&x>= -8.88178e-15) {
cout << "除数不能为零!!!" << endl;
result = 8.88178e-15;
}
其实说上述的判断有重合,也不一定全覆盖,但目前来讲,我是没发现错误,如果真有错,可以告我一声。
[代码实现]
判断表达式是否错误(除除数为0的情况)
int check_(string s)
{
if (s[0] != '#' || s[s.length() - 1] != '#')
{
return 0;
}
if (s.length() == 2)
{
return 1;
}
if (isOperator(s[1]))
{
return 2;
}
int nq = 0, nh = 0;
bool point = 0;
for (int i = 1; i < s.length() - 1; i++)
{
if (nh > nq)
{
return 3;
}
if (isOperator(s[i]))
{
if (s[i - 1] == '.')
{
return 6;
}
if (!isdigit_(s[i + 1]) && s[i + 1] != '(')
{
if(s[i+1]=='#')
return 2;
return 4;
}
point = 0;
}
else
{
if (s[i] == '(')
nq++;
else if (s[i] == ')')
{
nh++;
if (s[i - 1] == '(')
return 11;
}
else if (s[i] == '.')
{
if (point == 1)
{
return 7;
}
if (!isdigit_(s[i - 1]))
{
return 9;
}
if (!isdigit_(s[i + 1]))
{
return 10;
}
point = 1;
}
else if (!isdigit_(s[i]))
{
return 5;
}
}
}
if (nq != nh)
{
return 8;
}
return -1;
}
bool check(string s)
{
switch (check_(s))
{
case -1:return true;
case 0: {
cout << "请按格式输入‘#’!!!" << endl;//#1+1
break;
}
case 1: {
cout << "表达式禁止为空!!!" << endl;//##
break;
}
case 2: {
cout << "操作符两端必须有数!!!" << endl;//#*1#,#1*#
break;
}
case 3: {
cout << "反扣号前没有与之匹配的正括号!!!" << endl;//#)1+1(#
break;
}
case 4: {
cout << "禁止多个操作符相连!!!" << endl;//#1++1#
break;
}
case 5: {
cout << "禁止含有非数非运算符的符号!!!(中文括号也不行)" << endl;//###,#ad#
break;
}
case 6: {
cout << "小数点后禁止接操作符!!!" << endl; //#1.+1#
break;
}
case 7: {
cout << "一个数内不能有多个小数点!!!" << endl;//#1.1.1#
break;
}
case 8: {
cout << "括号个数不匹配!!!" << endl;//#(1+1#
break;
}
case 9: {
cout << "小数点前必须要有数字!!!" << endl;//#.1#
break;
}
case 10: {
cout << "小数点后必须要有数字!!!" << endl;//#1..1#
break;
}
case 11: {
cout << "括号内禁止为空!!!" << endl;//#()#
break;
}
}
system("pause");
system("cls");
return false;
}
其实只要知道有哪些错误,相应的判断代码也很好写,也没什么难点,就稍微麻烦一点。
优先级判断
int getPriority(char ch)
{
int level = 0; // 优先级
switch (ch) {
case '(':
level = 1;
break;
case '+':
case '-':
level = 2;
break;
case '*':
case '/':
level = 3;
break;
default:
break;
}
return level;
}
数字读取
if (isdigit_(ch))
{ // 如果是数字
double num = 0;
double dec = 0;
int p = 10;//记录小数位数
int flag = 1;
do {
if (ch == '.')
{
flag--;
ch = s[i];
i++;
continue;
}
if (flag == 1)
{
num = num * 10 + (ch - '0');//计算整数位
}
else if (flag == 0)
{
dec += double((ch - '0')) / p;//计算小数位
p *= 10;
}
ch = s[i];
i++;
} while (isdigit_(ch) || ch == '.');
计算
double Calculate(char ch, double x, double y) {
double result = 0;
switch (ch) {
case '+':
result = x + y;
break;
case '-':
result = y - x;
break;
case '*':
result = y * x;
break;
case '/':
if (x <= 8.88178e-15 &&x>= -8.88178e-15) {
cout << "除数不能为零!!!" << endl;
result = 8.88178e-15;
}
else
result = y / x;
break;
default:
break;
} // switch结束
return result; // 返回计算得到的结果
}
源代码
#include<iostream>
#include<fstream>
#include<stdlib.h>
#include<stack>
using namespace std;
bool isOperator(char ch) {
if (ch == '+' || ch == '-' || ch == '*' || ch == '/')
return true;
return false; // 否则返回false
}
bool isdigit_(char ch)
{
if (ch >= '0' && ch <= '9')
return true;
return false;
}
int getPriority(char ch) {
int level = 0; // 优先级
switch (ch) {
case '(':
level = 1;
break;
case '+':
case '-':
level = 2;
break;
case '*':
case '/':
level = 3;
break;
default:
break;
}
return level;
}
double Calculate(char ch, double x, double y) {
double result = 0;
switch (ch) {
case '+':
result = x + y;
break;
case '-':
result = y - x;
break;
case '*':
result = y * x;
break;
case '/':
if (x <= 8.88178e-15 &&x>= -8.88178e-15) {
cout << "除数不能为零!!!" << endl;
result = 8.88178e-15;
}
else
result = y / x;
break;
default:
break;
} // switch结束
return result; // 返回计算得到的结果
}
int check_(string s)
{
if (s[0] != '#' || s[s.length() - 1] != '#')
{
return 0;
}
if (s.length() == 2)
{
return 1;
}
if (isOperator(s[1]))
{
return 2;
}
int nq = 0, nh = 0;
bool point = 0;
for (int i = 1; i < s.length() - 1; i++)
{
if (nh > nq)
{
return 3;
}
if (isOperator(s[i]))
{
if (s[i - 1] == '.')
{
return 6;
}
if (!isdigit_(s[i + 1]) && s[i + 1] != '(')
{
if(s[i+1]=='#')
return 2;
return 4;
}
point = 0;
}
else
{
if (s[i] == '(')
nq++;
else if (s[i] == ')')
{
nh++;
if (s[i - 1] == '(')
return 11;
}
else if (s[i] == '.')
{
if (point == 1)
{
return 7;
}
if (!isdigit_(s[i - 1]))
{
return 9;
}
if (!isdigit_(s[i + 1]))
{
return 10;
}
point = 1;
}
else if (!isdigit_(s[i]))
{
return 5;
}
}
}
if (nq != nh)
{
return 8;
}
return -1;
}
bool check(string s)
{
switch (check_(s))
{
case -1:return true;
case 0: {
cout << "请按格式输入‘#’!!!" << endl;//#1+1
break;
}
case 1: {
cout << "表达式禁止为空!!!" << endl;//##
break;
}
case 2: {
cout << "操作符两端必须有数!!!" << endl;//#*1#,#1*#
break;
}
case 3: {
cout << "反扣号前没有与之匹配的正括号!!!" << endl;//#)1+1(#
break;
}
case 4: {
cout << "禁止多个操作符相连!!!" << endl;//#1++1#
break;
}
case 5: {
cout << "禁止含有非数非运算符的符号!!!(中文括号也不行)" << endl;//###,#ad#
break;
}
case 6: {
cout << "小数点后禁止接操作符!!!" << endl; //#1.+1#
break;
}
case 7: {
cout << "一个数内不能有多个小数点!!!" << endl;//#1.1.1#
break;
}
case 8: {
cout << "括号个数不匹配!!!" << endl;//#(1+1#
break;
}
case 9: {
cout << "小数点前必须要有数字!!!" << endl;//#.1#
break;
}
case 10: {
cout << "小数点后必须要有数字!!!" << endl;//#1..1#
break;
}
case 11: {
cout << "括号内禁止为空!!!" << endl;//#()#
break;
}
}
system("pause");
system("cls");
return false;
}
void run(string s)
{
stack<double>a;//数栈
double aa[100] = { 0 };//书栈的同步数组
int an = 0;
stack<char>b;//操作符栈
char bb[100] = { 0 };//操作符栈的同步数组
int bn = 0;
int i = 0;
char ch = s[i];
i++;
ch = s[i];
i++;
while (1)
{
if (ch == '#')
break;
if (isdigit_(ch))
{ // 如果是数字
double num = 0;
double dec = 0;
int p = 10;//记录小数位数
int flag = 1;
do {
if (ch == '.')
{
flag--;
ch = s[i];
i++;
continue;
}
if (flag == 1)
{
num = num * 10 + (ch - '0');//计算整数位
}
else if (flag == 0)
{
dec += double((ch - '0')) / p;//计算小数位
p *= 10;
}
else
{
cout << "数字输入错误!!!" << endl;
exit(1);
}
ch = s[i];
i++;
} while (isdigit_(ch) || ch == '.');
a.push(num + dec); // 存到数栈中
aa[an] = num + dec;
an++;
}
else if (ch == '(') { // (:左括号
b.push(ch);
bb[bn] = ch;
bn++;
ch = s[i];
i++;
}
else if (isOperator(ch)) { // 操作符
if (b.empty()) {// 如果栈空,直接压入栈
b.push(ch);
bb[bn] = ch;
bn++;
ch = s[i];
i++;
}
else {
// 比较栈op顶的操作符与ch的优先级
// 如果ch的优先级高,则直接压入栈
// 否则,推出栈中的操作符,直到操作符小于ch的优先级,或者遇到(,或者栈已空
while (!b.empty()) {
char c = b.top();
if (getPriority(ch) <= getPriority(c)) {
// 优先级低或等于
// 取出栈中操作符和数栈中两个数进行运算,再将结果放回数栈
double result = 0;
double x = a.top(); // 第二个操作数,因为栈是后进先出
a.pop(); an--;
double y = a.top(); // 第一个操作数
a.pop(); an--;
result = Calculate(c, x, y); // 计算
if (result == 8.88178e-15)
return;
a.push(result); // 把计算结果压入栈中
aa[an] = result;
an++;
b.pop(); bn--; // 操作符出栈
}
else // ch优先级高于栈中操作符
break;
} // while结束
b.push(ch); // 防止不断的推出操作符,最后空栈了;或者ch优先级高了
bb[bn] = ch;
bn++;
ch = s[i];
i++;
} // else
}
else if (ch == ')')
{ // 如果是右括号,一直推出栈中操作符,直到遇到左括号(
while (b.top() != '(') {
char c = b.top(); // 取出栈顶操作符
double result = 0;
double x = a.top(); // 第二个操作数,因为栈是后进先出
a.pop(); an--;
double y = a.top(); // 第一个操作数
a.pop(); an--;
result = Calculate(c, x, y); // 计算
if (result == 8.88178e-15)
{
return;
}
a.push(result); // 把计算结果压入栈中
aa[an] = result;
an++;
b.pop(); bn--; // 把操作符推出栈
}
b.pop(); bn--; // 把左括号(推出栈
ch = s[i];
i++;
}
if (i == s.length())
break;
cout << "当前数栈内容为:";
for (int i = 0; i < an; i++)
cout << aa[i] << ' ';
cout << endl << "当前操作符栈内容为:";
for (int i = 0; i < bn; i++)
cout << bb[i] << ' ';
cout << endl;
}
while (!b.empty())
{
// 当栈不空,继续取出操作符进行计算
char c = b.top(); // 取出栈顶操作符
double result = 0;
double x = a.top(); // 第二个操作数,因为栈是后进先出
a.pop(); an--;
double y = a.top(); // 第一个操作数
a.pop(); an--;
result = Calculate(c, x, y); // 计算
if (result == 8.88178e-15)
return;
a.push(result); // 把计算结果压入栈中
aa[an] = result;
an++;
b.pop(); bn--; // 把操作符推出栈
cout << "当前数栈内容为:";
for (int i = 0; i < an; i++)
cout << aa[i] << ' ';
cout << endl << "当前操作符栈内容为:";
for (int i = 0; i < bn; i++)
cout << bb[i] << ' ';
cout << endl;
}
cout << "最终等式为:" << endl;
for (int i = 1; i < s.length() - 1; i++)
cout << s[i];
cout << '=' << a.top() << endl;
a.pop(); an--;
}
void file_in()
{
fstream file("data.txt", ios::in);
if (file.fail())
{
cout << "data.txt打开失败!!!" << endl;
system("pause");
system("cls");
return;
}
while (!file.eof())
{
string s;
file >> s;
if (file.eof())
break;
cout << "文件读入结果为:" << s << endl;
if (!check(s))
continue;
run(s);
system("pause");
system("cls");
}
}
void board_in()
{
cout << "请输入表达式:(例:#6+15*(21-8/4)#)";
string s;
cin >> s;
if (!check(s))
return;
run(s);
system("pause");
system("cls");
}
int main()
{
int choice;
while (1)
{
cout << "1.键入表达式" << endl << "2.文件读入表达式" << endl << "3.退出" << endl;
cin >> choice;
if (choice > 3 || choice < 1)
{
cout << "请输入正确选项!!!" << endl;
system("pause");
system("cls");
getchar();
continue;
}
break;
}
switch (choice)
{
case 3:return 0;
case 1: {
board_in(); break;
}
case 2: {
file_in(); break;
}
}
}
[文件内范例]
#1+1
##
#*1#
#1*#
#)1+1(#
#1++1#
###
#ad#
#1.+1#
#1..1#
#1.1.1#
#(1+1#
#.1#
#5/((2+1)-3)#
#1.#
#()#
#(1+2.65)*5.46/3.2-1.1#
[运行结果]
错误的情况就不展示了,太多了。