作业题目:
实现思路:
1.开始
2.输入一个字符串str,该字符串是一个算术表达式
3.初始化两个栈,一个用于存储操作数(operandStack),另一个用于存储操作符(operatorStack)
4.遍历字符串str中的每个字符
(1)如果字符是数字,将其转换为数值并压入操作数栈
(2)如果字符是运算符或括号,根据优先级将其压入操作符栈
(3)如果字符是右括号,执行操作符栈顶的运算,直到遇到左括号
5.遍历完成后,如果操作符栈中仍有元素,继续执行栈顶的运算,直到操作符栈为空
6.输出操作数栈顶的元素,即计算结果
7.结束
代码说明:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stack>
using namespace std;
double operate(double a, char op, double b) {
switch (op) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
return a / b;
default:
return 0;
}
}//实现两个数的运算
bool priority(char s1, char s2) {
if ((s1 == '*' || s1 == '/') && (s2 == '+' || s2 == '-'))
return true;
else if ((s1 == '+' || s1 == '-') && (s2 == '+' || s2 == '-'))
return true;
else if ((s1 == '*' || s1 == '/') && (s2 == '*' || s2 == '/'))
return true;
else
return false;
}//判断运算符的优先级
void pushResult(stack<char>& operatorStack, stack<double>& operandStack, char op) {
double operand2 = operandStack.top();
operandStack.pop();
double operand1 = operandStack.top();
operandStack.pop();
double result = operate(operand1, op, operand2);
operandStack.push(result);
}//取出数字栈顶部的两个数及运算符栈顶部的运算符,进行运算,并将结果存放在数字栈中
double Calculate(char str[]) {
stack<double> operandStack;
stack<char> operatorStack;
double n = 0;//用于记录整数部分
double t = 0;//用于记录小数部分
double z = 1;//0.1的次方,用于计算小数部分的值
for (int i = 0; i < strlen(str); i++) {
if (str[i] >= '0' && str[i] <= '9') {
while (i < strlen(str) && str[i] >= '0' && str[i] <= '9') {
n = n * 10 + (str[i] - '0');
i++;
}
i--;
if (str[i + 1] != '.') {
operandStack.push(n);
n = 0;
}//如果不带小数点,是整数,那就直接存入数字栈
else if (str[i + 1] = '.') {
i += 2;
while (i < strlen(str) && str[i] >= '0' && str[i] <= '9') {
t = t + (str[i] - '0') * pow(0.1, z);
i++;
z++;
}
i--;
n = n + t;
operandStack.push(n);
n = 0;
z = 1;
t = 0;
}//如果带小数点,继续计算小数部分的值,最后结果为整数部分加上小数部分,将结果存入数字栈
}
else if (str[i] == '+' || str[i] == '-' || str[i] == '*' || str[i] == '/' || str[i] == '(') {
while (!operatorStack.empty() && operatorStack.top() != '(' && priority(operatorStack.top(), str[i])) {
char op = operatorStack.top();
operatorStack.pop();
pushResult(operatorStack, operandStack, op);
}
operatorStack.push(str[i]);
}//若str取到的运算符优先级比运算符栈栈顶的运算符优先级低,则进行pushResult操作(如前所述)
else if (str[i] == ')') {
while (!operatorStack.empty() && operatorStack.top() != '(') {
char op = operatorStack.top();
operatorStack.pop();
pushResult(operatorStack, operandStack, op);
}//计算括号内的值
operatorStack.pop(); // 弹出左括号
}
else {
printf("ERROR!");
return 0;
}//如果输入除运算符和数字之外的字符,则会报错
}
while (!operatorStack.empty()) {
char op = operatorStack.top();
operatorStack.pop();
pushResult(operatorStack, operandStack, op);
}//最后按照运算顺序,依次把两个栈中的元素取出进行运算
double result = operandStack.top();//将最终结果存入数字栈
return result;
}
void CCalculatormfcDlg::OnBnClickedButtondo()
{
// TODO: 在此添加控件通知处理程序代码
USES_CONVERSION;
CString inputS, outputS;
GetDlgItem(IDC_EDIT_expression)->GetWindowTextW(inputS);
char str[100] = {0};
char*p =new char[100];
p = W2A(inputS);
strncpy_s(str, p, strlen(p));
int len = strlen(str);
if (str[0] == '-') {
for (int j = len; j > 0; j--) {
str[j] = str[j - 1];
}
str[0] = '0';
len++;
}//
for (int i = 0; i < len; i++) {
if (str[i] == '-' && i != 0 && str[i - 1] == '(') {
for (int j = len; j > i; j--) {
str[j] = str[j - 1];
}
str[i] = '0';
len++;
}
}//这两步是为了实现负数运算,先对str遍历,在每个‘-’前插入‘0’,形成一个新的str
str[len] = 0;
double ans = Calculate(str);
outputS.Format(_T("%.2f"), ans);
GetDlgItem(IDC_EDIT_result)->SetWindowTextW(outputS);
}
关于部分拓展功能实现的代码说明
1.实现多位整数运算
while (i < strlen(str) && str[i] >= '0' && str[i] <= '9') {
n = n * 10 + (str[i] - '0');
i++;
}
i--;
}
double类型的n用于记录整数部分,n初始值为0,每一次while循环,n会记录当前读入的数字,n变为当前记录的数值加上上一个n*10,详见n = n * 10 + (str[i] - '0');操作
2.实现小数运算
double t = 0;//用于记录小数部分的位数
double z = 1;//0.1的次方,用于计算小数部分的值
if (str[i + 1] != '.') {
operandStack.push(n);
n = 0;
}//如果不带小数点,是整数,那就直接存入数字栈
else if (str[i + 1] = '.') {
i += 2;
while (i < strlen(str) && str[i] >= '0' && str[i] <= '9') {
t = t + (str[i] - '0') * pow(0.1, z);
i++;
z++;
}
i--;
n = n + t;
operandStack.push(n);
n = 0;
z = 1;
t = 0;
}//如果带小数点,继续计算小数部分的值,最后结果为整数部分加上小数部分,将结果存入数字栈
基本思路与整数部分的记录相似,核心是每一次while循环执行t = t + (str[i] - '0') * pow(0.1, z)操作,用于记录小数部分的值,然后加上之前运算过的整数部分的值,整个存入数字栈中。最后不要忘了将n,z,t重新变为0(第一版血的教训)。
3.实现负数运算
int len = strlen(str);
if (str[0] == '-') {
for (int j = len; j > 0; j--) {
str[j] = str[j - 1];
}
str[0] = '0';
len++;
}//
for (int i = 0; i < len; i++) {
if (str[i] == '-' && i != 0 && str[i - 1] == '(') {
for (int j = len; j > i; j--) {
str[j] = str[j - 1];
}
str[i] = '0';
len++;
}
}//这两步是为了实现负数运算,先对str遍历,在每个‘-’前插入‘0’,形成一个新的str
大致思路为:在输入str后直接遍历,在每个‘-’前插入‘0’,比如-1+2=0-1+2,形成一个新的str,再把str输出Calculate函数,按照普通的步骤运算。
测试结果:
应用界面
1.可以实现输入包含加、减、乘、除、括号等运算符和0-9数字的表达式运算
2.表达式包含多位整数、小数或负数
3.如若输入数字及运算符以外的符号,则会输出0.00以示输入表达式有误
稍微复杂一点的式子:
与iPad计算器运算结果一致,说明它还是比较聪明的。
遇到的问题及解决方法:
(描述在完成作业过程中遇到的问题以及解决方法)
3月17日,写完第一版代码(仅支持不包含多位数、小数、及负数的运算),出现的错误是:
每次输出的结果都是输入的表达式的最后一个数
怀疑是运算的次数出了问题,即取出数字栈栈顶的两个数进行运算这步操作到进行到输入的最后一个数字之前就停止了,导致最后数字栈的栈顶永远是输入的最后一个数。
在友人的帮忙检查下,发现问题出在:
此处少做了一个i的更新,导致程序跳过了所有的运算符,只存储数字进数字栈,于是最后输出栈顶的数永远都是所输入的最后一个数字。
更改方案是在框中位置添加i—操作。
3月22日,写完第二版代码(可以支持多位整数、小数,正要实现负数运算)
出现的问题是:
(1)只要表达式里包含负数,结果都是ERROR!
怀疑是在str中插入‘-’的时候出现问题了,于是把经历过插入操作后的心的str打印出来,发现果然有问题:
0是成功插入了,但后面会出现一些奇怪的东西。诸如:
输入-1+2
输出0-1+2abababababa空空空牛牛牛
查看编译器的警告,它说我没有零终止符,所以我把str初始化了
char str[100]={0};
问题解决!
(2)发现上个问题解决之后,它会这样操作,-1+2=-(1+2)=-3
我一直以为又是插入哪里错了,结果是一开始就没有做对过。
bool priority(char s1, char s2) {
if ((s1 == '*' || s1 == '/') && (s2 == '+' || s2 == '-'))
return true;
else
return false;
}//判断运算符的优先级
一开始我是这样写的,都没有考虑如果运算符和前一个是一样的情况下,优先级应该如何考虑。
所以加上了运算符相同时的优先级判断:
bool priority(char s1, char s2) {
if ((s1 == '*' || s1 == '/') && (s2 == '+' || s2 == '-'))
return true;
else if ((s1 == '+' || s1 == '-') && (s2 == '+' || s2 == '-'))
return true;
else if ((s1 == '*' || s1 == '/') && (s2 == '*' || s2 == '/'))
return true;
else
return false;
}//判断运算符的优先级
3月23号,在普通的c++项目后测试成功后,准备把代码稍微改动后复制进mfc程序中,出现了以下问题:
由于好像只有CString转char*这样的操作,老师上课讲的操作是这样的:
USES_CONVERSION;
CString inputS, outputS;
GetDlgItem(IDC_EDIT_expression)->GetWindowTextW(inputS);
char* p = W2A(inputS);
由于我原来的代码都是用的char数组,所以我这里用了
strncpy_s(str, p, strlen(p));
来实现char*转char数组的操作。
这些其实不算大问题,主要是我这样做了之后好像又出现了没有零终止符的问题,因为只要输入负数就会输出0报错。
稍微改一下代码,使输出为插入操作过后的新的str,发现果然是这样:
把char*和char数组都初始化了
char str[100] = {0};
char*p =new char[100];
发现没有用。
想了好久,终于想到直接在新造的str末尾手动加入零终止符
str[len] = 0;
然后终于解决了。
改进和优化:
(提出对作业的改进和优化意见)
1.之前老师还说的实现函数运算没有实现,不过个人的思路是如果读到连续四个字符sin(,就把数字栈栈顶的数拿出来进行sin运算。
2.在开始写了第一版之后课程才上到用二叉树中序遍历写出逆波普兰式再运算的方法,可惜没有时间再用这种方法完成作业了。
3.MFC这里如果能做得更美观就好了,比如插入一些可爱的图片,但是网上看了一下好像有点复杂,遂放弃。
4.虽然只是一项很小的作业,但是作为菜鸡,看见想了很久的问题最后被自己解决了,还是很开心的,希望以后也可以用乐观耐心的心态完成作业。当然理论课学习也要多费心。
参考资料:
利用栈实现表达式求值(含C/C++实现)_c++实现(1)输入一个表达式 (2)利用栈的各种相关操作求解该表达式 (3)编程对表达式-CSDN博客