设计要求:
输入一个只含有+、-、*、/、和%运算符的数学表达式,然后计算出其值;并且要能够处理圆括号。
例如:2 + 3 * (4 - 2)
分析过程:
- 我们都知道后缀表达式是非常符合计算机的计算方式的,因为它不用考虑优先级的问题,只需要从左到右依次计算即可。
- 所以我们的整体思路是:先把输入的中缀表达式转换为后缀表达式,然后再计算出它的值。具体过程如下:
下面是程序的具体流程:
- 每次调用exp_trans函数读取一行输入,将其中的中缀表达式转换为后缀表达式,并保存在全局数组expr中;而getop函数则每次从expr中读取字符,解析生成的后缀表达式,最后借用stack计算出它的值。
- 为了方便计算后缀表达式,我们在解析生成的后缀表达式时对其格式作了一点简单的调整,即在每个运算符或操作数之间都加上了空格。
- 还应当注意的是:浮点数不能直接和0进行比较。
计算后缀表达式的算法是这样的:
while (下一个运算符或操作数不是EOF)
if (是数)
将该数压入到栈中
else if (是运算符)
弹出所需数目的操作数
执行运算
将结果压入到栈中
else if (是\n)
弹出并打印栈顶值
else
出错
下面给出代码实现:
首先是一些声明
#define MAXEXP 200 /* 允许输入的中缀表达式的最大长度 */
#define MAXOP 20 /* 运算符或操作数的最大长度 */
#define NUMBER '0' /* 标识找到一个数 */
extern int expr[];
extern int li;
int getop(char *);
void exp_trans(void);
int op_prior(int);
void push(double);
double pop(void);
double top(void);
int isfull(void);
int isempty(void);
主函数主要控制表达式的计算,值得一提的是,这里便是switch语句的一个典型应用
main.c
int expr[MAXEXP]; /* 存储转换后的后缀表达式 */
int li = 0; /* expr数组的索引 */
int main(int argc, const char *argv[])
{
char s[MAXOP]; /* 存储获取的运算符或操作数 */
int type;
double op2;
while ((type = getop(s)) != EOF)
switch (type) {
case NUMBER:
push(atof(s));
break;
case '+':
push(pop() + pop());
break;
case '*':
push(pop() * pop());
break;
case '-':
op2 = pop();
push(pop() - op2);
break;
case '/':
op2 = pop();
if (fabs(op2) > DBL_EPSILON)
push(pop() / op2);
else
printf("error: zero divisor\n");
break;
case '%':
op2 = pop();
if (fabs(op2) > DBL_EPSILON)
push(fmod(pop(), op2));
else
printf("error: zero divisor\n");
break;
case '\n':
printf("\t%.8g\n", pop());
break;
default:
printf("error: unknow command %s\n", s);
break;
}
return 0;
}
然后exp_trans完成表达式的转换,转换过程也是利用栈来完成的,和最终计算表达式是用的相同的一个栈。在我的另一篇博客中有对转换过程的详细描述。
expts.c
/*
* 遇见数字,输出;遇见操作符,入栈;遇见比栈顶操作符优先级更低的,弹出栈中比遇见的操作符优先级高的;
* 遇见')',弹出栈中'('以前的操作符
*/
/* exp_trans函数:将中缀表达式转换为后缀表达式 */
void exp_trans(void)
{
int c, i;
i = 0;
while ((c = getchar()) != EOF)
if (isspace(c) || isdigit(c) || c == '.') { /* 原样保存空白符和数字 */
if (c == '\n') {
while (!isempty()) { /* 遇见\n,弹出栈中所有操作符 */
expr[i++] = pop();
expr[i++] = ' ';
}
break;
}
expr[i++] = c;
} else if (op_prior(c) || c == ')') { /* 是运算符或')' */
if (isempty() && c != '-') {
push(c);
} else if (c == ')') {
while (!isempty()) {
if (top() == '(') {
pop();
break;
}
expr[i++] = pop();
expr[i++] = ' ';
}
} else {
if (c == '-') { /* 判断是负号还是减号 */
if (isdigit(c = getchar())) {
expr[i++] = '-';
expr[i++] = c;
continue;
} else {
ungetc(c, stdin);
c = '-';
}
}
while (!isempty() && top() != '(' && op_prior(top()) >= op_prior(c)) {
expr[i++] = pop();
expr[i++] = ' ';
}
push(c);
}
}
expr[i++] = c;
expr[i] = '\0';
}
/* op_prior函数:返回对应操作符的优先级 */
int op_prior(int c)
{
switch (c) {
case '+':
case '-':
return 1;
break;
case '*':
case '/':
case '%':
return 2;
break;
case '(':
return 3;
break;
default:
return 0;
break;
}
}
我们每次调用exp_trans函数读取一行,然后getop函数用于从中解析需要的运算符和操作数
getop.c
/* getop函数:获取下一个运算符或操作数 */
int getop(char *s)
{
if (expr[li] == '\0') {
exp_trans(); /* 如果刚开始时未读入或读到expr的末尾,就调用exp_trans函数读入新的一行 */
li = 0;
}
while ((s[0] = expr[li]) == ' ' || expr[li] == '\t')
li++;
s[1] = '\0';
if (!isdigit(expr[li]) && !islower(expr[li]) && expr[li] != '.' && expr[li] != '-')
return expr[li++];
if (expr[li] == '-') { /* 判断‘-’是减号还是负号 */
if (isdigit(expr[++li]) || expr[li] == '.')
*++s = expr[li];
else
return '-';
} /* 收集数 */
if (isdigit(expr[li]))
while (isdigit(*++s = expr[++li]))
;
if (expr[li] == '.')
while (isdigit(*++s = expr[++li]))
;
*s = '\0';
return NUMBER;
}
最后就是栈的实现了,很简单,没什么好说的
#define MAXVAL 100
static int val[MAXVAL]; /* 值栈 */
static int sp = -1; /* 栈顶指针 */
/* push函数:将c压入栈中 */
void push(double c)
{
if (!isfull())
val[++sp] = c;
else
printf("stack is full\n");
}
/* pop函数:弹出并返回栈顶值 */
double pop(void)
{
if (!isempty())
return val[sp--];
else {
printf("stack is empty\n");
return 0;
}
}
/* top函数:只返回栈顶值 */
double top(void)
{
if (!isempty())
return val[sp];
else {
printf("stack is empty\n");
return 0;
}
}
/* isfull函数:判断栈是否已满 */
int isfull(void)
{
return sp == MAXVAL - 1;
}
/* isempty函数:判断栈是否为空 */
int isempty(void)
{
return sp == -1;
}