软件工程第四次作业-3四则运算
结对同学:姜珊
目录:
1.需求分析
2.基本设计
3.代码说明
4.测试运行
5.重难点知识
6.缺点改进及体会
1.需求分析:
作业链接:https://edu.cnblogs.com/campus/nenu/SWE2017FALL/homework/997
功能一和功能二其实可以合并成为一个功能,就是计算带括号的四则运算式,并对结果进行判断和输出。
功能三是利用输入参数来进行出题并输出。
2.基本设计
由需求分析可以得到,可以根据参数个数来执行某一程序段。
对于功能一二,只有一个参数就是文件名,直接输入f4并回车即可运行。
对于功能三,要输入三个参数如:f4 –c 2,其中-c后边的数字要当作程序体中的循环次数,完成输出个数的限定。
3.代码说明
地址:https://git.coding.net/immixiaomi/f4-mhjs.git
首先定义了字符类型的栈及相关操作,在将中序表达式转化为后序表达式时要用到:
typedef int Status; typedef char ElemType; typedef struct { ElemType data[MAXSIZE]; int top;//栈顶指针 }Stack; Status InitStack(Stack *S)//初始化 { int i; for(i = 0; i < MAXSIZE; i++) S->data[i] = NULL; S->top = -1; return 1; } Status CreateStack(Stack *S,int n)//创建一个长度为n的堆栈 { if(n>MAXSIZE || n<1) { printf("输入长度有误!\n"); return -1; } srand(time(0)); int i; for(i = 0; i < n; i++) { S->data[i] = rand()%100+1; } S->top = n-1; return 1; } Status push(Stack *S,ElemType e)//压栈操作 { if(MAXSIZE-1 == S->top) { printf("栈已满\n"); return -1; } ++(S->top); S->data[S->top] = e; return 1; } Status pop(Stack *S,ElemType *e)//出栈 { if(-1 == S->top){ printf("栈为空!\n"); return -1; } *e = S->data[S->top]; --(S->top); return 1; }
GetStr()函数是自动生成字符串的函数,参数是空字符数组,通过随机数的产生,并将随机数用itoa()函数转化成字符型,加入随机的运算符产生了中序表达式。先产生了表达式再向其中加入括号,括号都是成对加入,完成了带括号表达式的构建。
void GetStr(char *str)//构建随机串,并添加括号 { char str1[1] = ""; int a, b, c, d; char op1, op2, op3; int i; srand((int)time(0)); a = (int)(rand()%10);//四个变量 b = (int)(rand()%10); c = (int)(rand()%10); d = (int)(rand()%10); i = (int)(rand()%4);//三个操作符 switch(i) { case 0: op1 = '+'; break; case 1: op1 = '-'; break; case 2: op1 = '*'; break; case 3: op1 = '/'; break; } i=(int)(rand()%4); switch(i) { case 0: op2 = '+'; break; case 1: op2 = '-'; break; case 2: op2 = '*'; break; case 3: op2 = '/'; break; } i=(int)(rand()%4); switch(i) { case 0: op3 = '+'; break; case 1: op3 = '-'; break; case 2: op3 = '*'; break; case 3: op3 = '/'; break; } itoa(a, str1, 10); strcpy(str, str1); str[1] = op1; itoa(b, str1, 10); strcat(str, str1); str[3] = op2; itoa(c, str1, 10); strcat(str, str1); str[5] = op3; itoa(d, str1, 10); strcat(str, str1); for(int j = 0; j < 7; j++) { if(str[j] == '/') { int e; e = (int)(rand()%9)+1; itoa(a, str1, 10); str[j+1] = str1[0]; } } int j, k;//左括号右括号 int flag = 0; j = (int)(rand()%3); int length = 8; if(j == 0)//位置1加不加左括号的判定 { k = (int)(rand()%4); if(k != 3&&flag != 2) { for(int i = length;i > 0;i--)//向坐标0处插入括号 { str[i] = str[i-1]; } str[0] = '('; flag++; length++; int l = (int)(rand()%4); if(l < 2) { for(int i = length; i > 4; i--) { str[i] = str[i-1]; } str[4] = ')'; flag--; length++; for(int i = length;i > 6;i--) { str[i] = str[i-1]; } str[6] = '('; flag++; length++; for(int i = length;i > 10;i--) { str[i] = str[i-1]; } str[10] = ')'; flag--; length++; } else if(l = 2) { for(int i = length; i > 4; i--) { str[i] = str[i-1]; } str[4] = ')'; flag--; length++; } else if(l = 3) { for(int i = length; i > 6; i--)//插入括号 { str[i] = str[i-1]; } str[6] = ')'; flag--; length++; } } else if(k != 2&&flag != 2) { for(int i = length+1; i > 1; i--)//向坐标0,1处插入俩左括号 { str[i] = str[i-2]; } str[0] = '('; str[1] = '('; flag = flag+2; length = length+2; for(int i = length; i > 5; i--) { str[i] = str[i-1]; } str[5] = ')'; flag--; length++; for(int i = length; i > 8; i--) { str[i] = str[i-1]; } str[8] = ')'; flag--; length++; } } else if(j == 1) { k=(int)(rand()%4); if(k != 3&&flag != 2) if(0) { for(int i = length; i > 2; i--)//向坐标2处插入括号 { str[i] = str[i-1]; } str[2] = '('; flag++; length++; } else if(k != 2&&flag != 2) { for(int i = length+1; i > 3; i--)//向坐标2,3处插入俩左括号 ,只能接着向6和8(原坐标)添加括号 { str[i] = str[i-2]; } str[2] = '('; str[3] = '('; flag = flag+2; length = length+2; for(int i = length; i > 7; i--) { str[i] = str[i-1]; } str[7] = ')'; flag--; length++; for(int i = length;i > 10;i--) { str[i] = str[i-1]; } str[10] = ')'; flag--; length++; } } else if(j == 2) { k = (int)(rand()%4); if(k != 3&&flag != 2) { for(int i = length; i>4; i--)//向坐标4处插入括号 { str[i] = str[i-1]; } str[4] = '('; flag++; length++; //str4 插入左括号一个 for(int i = length; i>8; i--) { str[i] = str[i-1]; } str[8] = ')'; flag--; length++; } } for(i = 0; i < length-1; i++) { printf("%c",str[i]); } printf("="); }
Translate()函数是将中序表达式转化为后序表达式,参数是两个字符串,分别是旧串和转化后的新串。用栈进行操作,并对括号进行配对取出,转化成为没有括号的逆波兰表达式。
int Translate(char *str,char *exp)//中缀表达式转后缀表达式 { //新建一个栈,来存储符号 char e; Stack S; if(InitStack(&S) != 1) { printf("初始化栈失败!\n"); } //当带转换的字符串*mid未终止时,循环处理 while(*str) { //如果是数字,则直接输出 if(*str >= '0' && *str <= '9') { *(exp++) = *(str++); continue; }else if(*str == '+' || *str == '-' || *str == '*' || *str == '/' || *str == '(' || *str == ')') { //输入的是合法运算符号,比较之前是否有更高优先级的符号 if(S.top == -1 || '(' == *str) { //当符号栈为空或遇到左括号时,符号入栈 push(&S, *(str++)); continue; } if(')' == *str) { //遇到右括号时,栈顶元素依次出栈;直到遇到第一个左括号时结束 pop(&S, &e); *(exp++) = e; while(pop(&S, &e) && e != '(') { *(exp++) = e; } // printf("%c\n",e); str++; continue; } //后续的处理都要取出临时的栈顶元素,与当前输入的符号*mid相比较;当临时栈顶元素优先级大于等于输入符号的优先级时,出栈;否则符号入栈(已经弹出一个,记得把弹出的元素也入栈) pop(&S,&e); if('+' == *str || '-' == *str) { if(e == '(') { push(&S, '('); push(&S, *(str++)); continue; } else { *(exp++) = e; push(&S, *(str++)); continue; } } else if('*' == *str || '/' == *str) { if('*' == e || '/' == e) { *(exp++)=e; push(&S, *(str++)); continue; } else { push(&S, e); push(&S, *(str++)); continue; } } } else { printf("error%c\n", *str); return -1; } } //当待转换的字符已经结束时,符号栈至少还有一个元素(中缀表达式的特点:数字结尾;后缀表达式以符号结尾);将栈中的元素依次出栈 while(S.top != -1) { pop(&S, &e); *(exp++) = e; } //字符串的结束符! *exp = '\0'; }
Calculate()函数是将后缀表达式进行计算,返回值类型为double型,值为该表达式的结果。
double Calculate(char* exp)//计算逆波兰表达式的值 { int i = 0; double temp[MAXSIZE]; int top = -1; double a; while(exp[i] != '\0') { if(exp[i]>='0' && exp[i]<='9') { temp[++top] = exp[i]-'0'; } else if(exp[i] == '-') { double m = temp[top]; top--; double n = temp[top]; top--; temp[++top] = n-m; } else if(exp[i] == '+') { double m = temp[top]; top--; double n = temp[top]; top--; temp[++top] = n+m; } else if(exp[i] == '/') { double m = temp[top]; top--; double n = temp[top]; top--; temp[++top] = n/m; } else if(exp[i] == '*') { double m = temp[top]; top--; double n = temp[top]; top--; temp[++top ] = n*m; } i++; } return temp[top]; }
输出有两个函数,PrintOnScr()负责功能一二的输出,PrintOnFile()负责功能三的输出,但是功能三因技术原因未完成,暂时只输出至屏幕。
void PrintOnScr(double answer,int count)//输出至屏幕,对应功能一二 { printf("\n"); printf("?"); double ans; scanf("%lf",&ans); if(answer == ans) { printf("答对啦,你真是个天才!"); count++; //break; } else { printf("再想想吧,答案似乎是%d喔!",answer); } printf("\n"); } void PrintOnFile(double answer)//输出至文件,对应功能三 { printf("\t\t\t%d\n",answer); return; }
主函数里定义两个空串,用于传入函数中接收数据,然后根据参数个数执行对应某段函数体。
int main(int argc, char *argv[]) { char str[MAXSIZE]=""; char final[MAXSIZE]=""; double answer; int count = 0; if(argc == 1) { for(int i = 0; i < 20; i++) { GetStr(str); Translate(str,final); answer=Calculate(final); PrintOnScr(answer,count); fflush(stdin); } printf("\n"); printf("你一共答对%d道题,共20道题。", count); return 0; } else if(argc == 3) { int n = atoi(argv[2]); if(n < 0) { printf("题目数量必须是 正整数。"); } else if(argv[2][0] >= 'a'&&argv[2][0]<='z') { printf("题目数量必须是 正整数。"); } else { for(int i = 0; i < 100; i++) { for(int j = 0; j < 100; j++) { if(argv[i][j] == '.') { printf("题目数量必须是 正整数。"); return 0; } } } for(int i = 0; i < n; i++) { GetStr(str); Translate(str,final); answer=Calculate(final); PrintOnFile(answer); } } } }
4.测试运行
功能一和功能二:
只能算最多2道题,然后程序崩溃了。我估计是堆栈溢出的错误,但是不知如何清空内存,因为在刚刚打开窗口的时候程序是正常的。无法修改这个错误。
功能三:
可以正确判断-c后边的参数,却无法进行输出,程序再次崩溃。
5.重难点知识
本次编程用到了以下知识,参考链接如下:
C语言产生随机数:http://www.cnblogs.com/qjziyou/p/4782454.html
Itoa函数:http://www.cnblogs.com/bluestorm/p/3168719.html
定义字符栈的方法:http://blog.csdn.net/keepupblw/article/details/26343769
判断整数:http://blog.csdn.net/youngdze/article/details/13628877
浮点数转换成ASCII码:http://download.csdn.net/download/letre/1936905
6.缺点改进及体会
这次的三个功能都没有完全实现,我和姜珊同学对需求的考虑不充分,没有提前测试一下循环情况下程序会不会溢出,结果真的发生了溢出,也尝试过修改但是并没有奏效。在字符栈那里的使用肯定没有想象的那么顺利。最开始我们一起讨论代码规范的书写,互相交流了风格,发现了我们其实并没有什么风格,就制订了一份详细的规范我们仔细遵守就好。此外,我和姜珊同学对于函数的参数设置进行了争论,最后一致决定采用字符串传参数。在Calculate()函数这里我们讨论了一下是否要使用atoi()函数,我认为采用减去’0’的效果更好更简洁,最后我们决定使用后者的方法。在遇到字符栈输出溢出的时候,我们试着重新写了一个新的函数,遗憾的是,效果还没有原来的好,根本不能运行,直接崩溃。在最后判断浮点数的时候,我提出了将其转化为ASCII码的建议,姜珊同学认为直接搜索小数点即可,我们采用了她的方法。
以后要注意对代码段的掌控,不要等到错误无法挽回在试图修改。
照片: