目录
读入一个公式,在读入的时候进行一些处理,储存在str[ ]中
前置知识
c语言基础,最好看过c primer plus,因为我的很多套路都是用的这本书里的。
c++ STL了解,c++数据结构对栈和队列有基本了解,知道stack,queue的基本用法:push和pop,empty,size。
了解好了中缀转后缀(中缀转逆波兰式)。
这个随便在csdn上找就可以找到c语言的版本,流程大同小异,我稍作改动,但是基本一样。
进入设计
最好自己编几个输出函数,和辅助函数,方便检查
void outAntiBolan(std::queue<char>exp)
{
while (!exp.empty())
{
char top = exp.front();
exp.pop();
putchar(top);
}
putchar('\n');
}
puts();//这个当然少不了
//判断操作符优先级
int getPriority(char op)
{
switch (op)//因为全部return,所以不加break了
{
case '+':
case '-':
{
return 1;
}
case '*':
case '/':
case '%':
{
return 2;
}
case '^':
{
return 3;
}
default:
{
return -1;
}
}
}
读入一个公式,在读入的时候进行一些处理,储存在str[ ]中
- 左符号加正负号,在两个符号中补0.
或者开头直接一个正负号,就先加0
即可将带有正负号的全部转化成非负计算 - 统计左右括号数量,进行简单的括号匹配,漏洞是比如这种 )(情况
- 碰到 3(或者)3或者)(或者(+之类的情况,可以选择中间补*号,也可以选择报错
- 报错用bool返回值判定
- 这样就可以得到一个可以直观上看不出问题的中缀了,但是有一个问题就是,如果经过计算发现除数是0呢?所以在计算步还要加验证
所以这里有一个豪华的读入函数
其实我个人感觉,如果处理好了读入数据,内部运算会很舒服
bool Input(char str[])
{
char ch;
int p = 0;
int left = 0, right = 0;
ch = getchar();//先赋一个防止p-1越界
if (ch == '+' || ch == '-')
str[p++] = '0';//补0情况1
if (ch == '(')//匹配括号的情况1
left++;
if (ch == ')')
right++;
str[p++] = ch;
while ((ch = getchar()) != '\n')
{
//补0情况2/3,:左符号加正负号,以及左括号后加政府,简化,更改ch
if (ispunct(str[p-1])
&& (ch == '+' || ch == '-'))
{
str[p++] = '0';//补0
}
//数(,)数,)(,
if ((isalnum(str[p - 1]) && ch == '(')
|| (str[p - 1] == ')' && isalnum(ch))
|| (str[p - 1] == ')' && ch == '('))
{
while (getchar() != '\n')
continue;
return false;//报错
}
//(op
if (str[p - 1] == '('
&& (ch == '*' || ch == '/' || ch == '%' || ch == '^'))
{
while (getchar() != '\n')
continue;
return false;//报错
}
//匹配括号的情况
if (ch == '(')
left++;
if (ch == ')')
right++;
str[p++] = ch;
}
str[p] = '\0';//补上字符串结尾
if (right != left)//括号匹配
{
//这里不用清理
return false;
}
return true;//成功转化
}
逆波兰式转化
其实我的转化和他们写的稍微不同,我用的是队列储存exp,其实exp是只进不出的,所以stack和queue都行,但是考虑到输出方便,我才用了queue
我们还需要处理的就是:
- 原有数字的结尾,这个简单,加个innum判断是否处于数字状态即可,一旦结束数字状态就压入一个结尾号
- 需要注意的是最后一个数字,他是没有退出数字状态的,所以要额外判断一下
//转化处理好的str到逆波兰式的queue,其中会将整数用&分节
void antiBolan(std::queue<char>& exp, char str[])
{
//初始化
std::stack<char>op;
char ch, temp;
bool innum = false;
//利用op栈和exp队列构造
for (int i = 0; i < strlen(str); i++)
{
ch = str[i];
if (isalnum(ch))//num
{
if (!innum)//标记进入数字
innum = true;
exp.push(ch);
//如果是最后一个数字,那么后面的步骤都没了,自然就无法给他加&
if (i == strlen(str) - 1)
exp.push('&');
}
else if (ch == '(' || ch == ')')//括号
{
if (innum)//刚出数字,标记出数字,加&结尾
{
innum = false;
exp.push('&');
}
if (ch == '(')
op.push(ch);
else //右括号,那么这个右括号就不操作了,中间包的操作符一起压入
{
while (1)
{
temp = op.top();
op.pop();//不管如何,先弹出,如果是好,就压,不然就break,顺便还扔掉左括号
if (temp == '(')
break;
else
exp.push(temp);
}
}
}
else //op
{
if (innum)//刚出数字,标记出数字,加&结尾
{
innum = false;
exp.push('&');
}
while (1)//不断和op的top元素比较,直到碰到恰当的
{
if (op.empty() || op.top() == '(')//如果是空的,或者顶部为),直接入
{
op.push(ch);
break;
}
if (getPriority(ch) > getPriority(op.top())
||(ch=='^'&&op.top()=='^'))//这里注意二连^可以套
{
op.push(ch);
break;
}
else
{
temp = op.top();
op.pop();
exp.push(temp);
}
}
}
}
while (!op.empty())
{
temp = op.top();
op.pop();
exp.push(temp);
}
}
到这里,我们就得到一个可以计算的,但是不确定有0除数,和负次方,所以在后面的计算中要去判断,及时终止
逆波兰式计算
大体思路是先从前到后遍历,一整节一整节读数,转成int存到<int>型的stack里,如果要做浮点,在这种思路上做个扩充即可。
然后就是经典的后缀计算
整节读数可以用一个bool值标记是否进入数状态。
这里需要注意判断0除数和负次方问题
- 0除数,当op为/但是exp.front()为0,这个时候就是0除数,直接终止函数打印报错
- 负次方,这个在整型计算中算错,op为^但是front<0即可
int computeBolan(std::queue<char>exp)
{
std::stack<int>ans;
int a, b, c;
bool innum = false;
char toInt[LEN];
int p = 0;
while (!exp.empty())
{
char temp = exp.front();
exp.pop();
if (isalnum(temp))//是数字
{
toInt[p++] = temp;//储存到临时字符串准备转化
if (!innum)//进入数字状态
innum = true;
}
else if (temp == '&')//是&
{
toInt[p] = '\0';//封口
p = 0;//指针归零
innum = false;//退出数字状态
//puts(toInt);//输出验证
ans.push(atoi(toInt));//转化并打入
}
else//这种temp就是op了
{
//弹出两个
b = ans.top();
ans.pop();
a = ans.top();
ans.pop();
//判断计算,叠加错误判断
switch (temp)
{
case '+':
{
ans.push(a + b);
break;
}
case '-':
{
ans.push(a - b);
break;
}
case '*':
{
ans.push(a * b);
break;
}
case '/':
{
if (b == 0)//除数为0
{
puts("Divide 0.");
return INT_MAX;//用int最大值标记,如果有好办法更好
}
ans.push(a / b);
break;
}
case '%':
{
if (b == 0)//暂时不能对0求模
{
puts("error.");
return INT_MAX;
}
ans.push(a % b);
break;
}
case '^':
{
if (b < 0)
{
puts("error.");
return INT_MAX;
}
ans.push((int)pow(a, b));
break;
}
default :
{
puts("未知运算符");
}
}
}
}
return ans.top();
}
主函数以及测试输出
input:
4 2^3 2^0 2^3^2 2^(3-1)^(10-8)
output:
8 1 512 16
input:
11 (2+8 2+8) 8/0 8/(8+5-13) 2^(2-5) 10-(80-30(/3*3+4 10-80-30)/3*3+4 (2+8)(3+2) (2)3(8) 30(/3+3)+4 10(20-8)+2
output:
error. error. Divide 0. Divide 0. error. error. error. error. error. error. error.
input:
2 10(10) 14*10-(10)2
output:
error. error.
input:
14 18-32 18/4 18%3 10+20*4 10-20/4 (18-3)*3 10*(10) (10+2)/(8-10) (2*3)/(5*2) 10-(80-30)/3*3+4 (((2+8)*2-(2+4)/2)*2-8)*2 (((8+2)*(4/2))) 10/0 (10-80*2
output:
-14 4 0 90 5 45 100 -6 0 -34 52 20 Divide 0. error.
int main(void)
{
freopen("input.txt", "r", stdin);
int N, ans;
scanf("%d\n", &N);
while (N--)//一次性读入n行表达式
{
//中缀转逆波兰式,包含预处理正负号优化,,压入时增加数字分割,判定公式正确
//读取,并优化正负号,初步判断公式正确性
char str[LEN];
if (Input(str) == false)//报错情况
{
puts("error.");
continue;
}
//将str转换成逆波兰式,增加数字分割
std::queue<char>exp;
antiBolan(exp, str);
//outAntiBolan(exp); //输出验证
if ((ans = computeBolan(exp)) != INT_MAX)
{
printf("%d\n", ans);
}
}
}