一、实验题目
算数表达式计算:
设计算法根据用户输入的合法表达式计算结果并显示出来
表达式中的符号为+、-、*、/、(、)
表达式中数字为整数
二、实验环境
Windows 11
Visual Studio Code
(总体代码在最后)
三、实验过程
思路分析:
需要实现一个算法来解析和计算包含加法(+)、减法(-)、乘法(*)、除法(/)以及括号(( ))的合法整数表达式:
根据学过的树的知识,这个表达式可以有三种表达方式: 前缀、中缀、后缀
前缀:将表达式变为前缀后,从右往左,每次从操作数栈取一个数,再取一个操作符,再取一个操作数,进行运算。
后缀:将表达式变为后缀后,从左往右,每次从操作数栈取一个数,再取一个操作符,再取一个操作数,进行运算。
中缀:虽然表达式写出来很适合人类观看,但是对计算机实现来说比较困难,每当遇见一个算术运算符就要看操作符栈是不是空的,如果是空的就直接放进去,如果不是空的就要比较优先级,如果优先级大就进去,如果小就要取出两个数字进行运算再放进去
举个例子:
1. 创建两个栈:一个用于存储操作数(数字),另一个用于存储操作符(+、-、*、/);
2. 从左到右扫描表达式:
如果遇到操作数,将其压入操作数栈。
如果遇到操作符:
如果操作符栈为空,或者栈顶是左括号,直接将操作符压入操作符栈。
如果当前操作符的优先级高于操作符栈栈顶的操作符,将当前操作符压入操作符栈。
否则,从操作数栈中弹出一个操作数和操作符栈的栈顶操作符,执行相应的运算,并将结果压回操作数栈。然后将当前操作符压入操作符栈。
如果遇到左括号,将其压入操作符栈。
如果遇到右括号,从操作数栈中弹出一个操作数和操作符栈中的操作符,执行运算,并将结果压回操作数栈。重复此过程直到遇到左括号,然后从操作符栈中弹出左括号。
3. 表达式扫描完毕后,如果操作符栈中仍有操作符,重复第二步的过程,直到操作符栈为空。
4. 最后,操作数栈中应该只剩下一个元素,即表达式的结果。
代码:
计算函数 (Calculate):这个函数负责执行基本的算术运算。它从数字栈中弹出两个数字,根据传入的运算符执行相应的运算,并将结果压入数字栈。
完整代码请见附件
测试结果:
四、结果分析
使用这两个结构体来实现数字栈和字符栈
压栈
主函数 (main):这是程序的入口点。它首先读取一个表达式,然后初始化数字栈和字符栈。接着,它遍历表达式中的每个字符,如果是数字,则将其转换为浮点数并入栈;如果是运算符,则根据运算符的优先级和栈内的运算符进行比较,然后决定是入栈还是执行计算。最后,当表达式中的所有字符都被处理完毕后,执行剩余的运算符,并将最终结果打印出来。
总体代码结构:
1. 输入处理:
-
从用户那里获取输入的表达式,并将其转换为字符数组或字符串。
-
编写一个函数来检查表达式的合法性,包括括号匹配和操作符的正确使用。
2. 表达式解析:
-
实现上述两个栈的逻辑。
-
编写一个循环来逐个字符处理表达式。
3. 计算结果:
-
根据两个栈的内容执行计算。
-
在遇到操作符时,从栈中弹出相应的操作数,执行运算,并压入结果。
4. 输出结果:
-
一旦表达式的所有部分都被处理完毕,操作数栈顶的元素就是最终结果。
-
显示结果给用户。
五、实验心得
在本次实验中,通过编写和测试一个简单的表达式求值器程序,加深对栈这一数据结构的理解,同时熟悉基本的算术运算和运算符优先级处理,我更加深入地理解了栈的工作原理以及如何利用栈来解决实际问题。
我将具体完成阶段分为三个:设计阶段:首先设计了两个栈结构,一个用于存储操作数(数字),另一个用于存储操作符(字符)。确定了每个栈的基本操作,包括初始化、入栈、出栈和获取栈顶元素。 编码阶段:接着,根据设计实现了主函数和栈操作函数。主函数负责读取表达式并逐个字符进行解析,根据运算符优先级和结合性规则,执行相应的入栈和计算操作。 测试阶段:在完成编码后,我进行了多轮测试,包括简单表达式的计算和错误输入的处理。
程序能够正确处理基本的算术表达式,并返回预期的结果。
学习总结:学习了栈的基本操作和在程序中的应用。 理解了运算符优先级和结合性的概念。 加强了对C语言中字符串处理和字符操作的理解。 意识到了代码测试的重要性,以及如何通过测试来发现并解决问题。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <math.h>
#define maximum 100000
typedef struct//数字栈
{ float data[maximum];
int top;
} number;
typedef struct//字符栈
{ char data[maximum];
int top;
} sign;
void InitNumber(number *stack);//初始化数字栈
void GetTopNumber(number stack, float *e);//获取栈顶元素
void PushNumber(number *stack, float e);//进栈
void PopNumber(number *stack, float *e);//出栈
void InitSign(sign *stack);
void GetTopSign(sign stack, char *e);
void PushSign(sign *stack, char e);
void PopSign(sign *stack, char *e);
void Calculate(number *stack, char e);
number Num;
sign sig;
char expression[maximum];
int main()
{ gets(expression);
int length;
length = strlen(expression);
int i;
float en, n;
char es;
InitNumber(&Num);
InitSign(&sig);
for (i = 0; i < length; i++)
{ if (expression[i] >= '0' && expression[i] <= '9')
{ n = expression[i] - '0'; //字符型转换为整型
while (expression[i + 1] != '\0')
{ if (expression[i + 1] >= '0' && expression[i + 1] <= '9')
{ n = n * 10 + expression[i + 1] - '0';
++i;
}
else
break;
}
PushNumber(&Num, n);
}
else if (expression[i] == '+' || expression[i] == '-' || expression[i] == '*' || expression[i] == '/'
|| expression[i] == '^' || expression[i] == '(' || expression[i] == ')')
{ switch (expression[i])
{ case '+':
if (sig.data[sig.top - 1] != '+' && sig.data[sig.top - 1] != '-' && sig.data[sig.top - 1] != '*'
&& sig.data[sig.top - 1] != '/' && sig.data[sig.top - 1] != '^')
//与栈顶元素的优先级相比较, 高于时入栈,此处判断是否入栈。
PushSign(&sig, '+');
else
{ while (sig.top > 0 && sig.data[sig.top - 1] != '(') //如果栈不为空切不为左括号,则出栈
{ PopSign(&sig, &es);
Calculate(&Num, es);
}
PushSign(&sig, '+');
}
break;
case '-':
if (sig.data[sig.top - 1] != '+' && sig.data[sig.top - 1] != '-' && sig.data[sig.top - 1] != '*'
&& sig.data[sig.top - 1] != '/' && sig.data[sig.top - 1] != '^')
PushSign(&sig, '-');
else
{ while (sig.top > 0 && sig.data[sig.top - 1] != '(')
{ PopSign(&sig, &es);
Calculate(&Num, es);
}
PushSign(&sig, '-');
}
break;
case '*':
if (sig.data[sig.top - 1] != '*' && sig.data[sig.top - 1] != '/' && sig.data[sig.top - 1] != '^')
PushSign(&sig, '*');
else
{ while (sig.top > 0 && sig.data[sig.top - 1] != '(')
{ PopSign(&sig, &es);
Calculate(&Num, es);
}
PushSign(&sig, '*');
}
break;
case '/':
if (sig.data[sig.top - 1] != '*' && sig.data[sig.top - 1] != '/' && sig.data[sig.top - 1] != '^')
PushSign(&sig, '/');
else
{ while (sig.top > 0 && sig.data[sig.top - 1] != '(')
{ PopSign(&sig, &es);
Calculate(&Num, es);
}
PushSign(&sig, '/');
}
break;
case '^':
if (sig.data[sig.top - 1] != '^')
PushSign(&sig, '^');
else
{ while (sig.top > 0 && sig.data[sig.top - 1] != '(')
{ PopSign(&sig, &es);
Calculate(&Num, es);
}
PushSign(&sig, '^');
}
case '(':
PushSign(&sig, '(');
break;
case ')':
while (sig.data[sig.top - 1] != '(')
{ PopSign(&sig, &es);
Calculate(&Num, es);
}
PopSign(&sig, &es);
}
}
}
while (sig.top > 0)
{ PopSign(&sig, &es);
Calculate(&Num, es);
}
GetTopNumber(Num, &en);
printf("%.0f\n", en);
return 0;
}
void InitNumber(number *stack)
{ stack->top = 0;
}
void GetTopNumber(number stack, float *e)
{ if (stack.top == 0)
return;
else *e = stack.data[stack.top - 1];
}
void PushNumber(number *stack, float e)
{ if (stack->top >= maximum)
return;
else
stack->data[stack->top++] = e;
}
void PopNumber(number *stack, float *e)
{ if (stack->top == 0)
return;
else *e = stack->data[--stack->top];
}
void InitSign(sign *stack)
{ stack->top = 0;
}
void GetTopSign(sign stack, char *e)
{ if (stack.top == 0)
return;
else *e = stack.data[stack.top - 1];
}
void PushSign(sign *stack, char e)
{ if (stack->top >= maximum)
return;//栈满
else
{ stack->data[stack->top] = e;
stack->top++;
}
}
void PopSign(sign *stack, char *e)
{ if (stack->top == 0)
return;
else *e = stack->data[--stack->top];
}
void Calculate(number *stack, char e)// 计算结果
{ float num1, num2, result;
PopNumber(stack, &num2);
PopNumber(stack, &num1);
switch (e)
{ case '+':
result = num1 + num2;
PushNumber(stack, result);
break;
case '-':
result = num1 - num2;
PushNumber(stack, result);
break;
case '*':
result = num1 * num2;
PushNumber(stack, result);
break;
case '/':
if (num2 == 0)
printf("表达式错误!");
else
{ result = num1 / num2;
PushNumber(stack, result);
break;
}
case '^':
result = pow(num1, num2);
PushNumber(stack, result);
break;
}
}