在写这篇博客的时候,显然我们的结对编程项目圆满结束了,过程曲折而有有趣,感谢老师让我们过了一个充实而又有意义的国庆啊 ( ̄﹏ ̄) 。这次结对编程的完成,绝大部分都得归功于队友小姜,他基本上完成了大部分工作。不过我个人在过程中也感觉学到了很多很多东西,对java的一些基本语法复习了一遍,也对上学期java swing组件的运用进行拾遗,确实也收获颇丰了。下面就详述了!
-------------------------------------------------------------------------------------------------------------------------
1.结对项目需求:
用户:
小学、初中和高中学生。
功能:
1、用户注册功能。用户提供手机号码,点击注册将收到一个注册码,用户可使用该注册码完成注册;
2、用户完成注册后,界面提示设置密码,用户输入两次密码匹配后设置密码成功。密码6-10位,必须含大小写字母和数字;
3、密码设置成功后,跳转到选择界面,界面显示小学、初中和高中三个选项,用户点击其中之一后,提示用户输入需要生成的题目数量;
4、用户输入题目数量后,生成一张试卷(同一张卷子不能有相同题目),界面显示第一题的题干和四个选项,用户选择四个选项中的一个后提交,界面显示第二题,...,直至最后一题;
5、最后一题提交后,界面显示分数,分数根据答对的百分比计算;
6、用户在分数界面可选择退出或继续做题;
我们可以看到结对项目需求其实就是在我们个人项目的基础上加上用户界面的设计,短信验证码功能和分数的显示(也就是既要实现出题又要把正确答案算出来),我们初始时的分工是小姜完成UI设计和短信验证码功能块,而我主要实现找到题目的正确答案和选项的设计。但是后面我遇到了一些问题,通过极限编程,我们完成了最终的设计。
2.UI设计:
对于UI的设计,脑子里第一时间当然想到的是java Swing啦。但是我和队友的个人项目都是用C++写的,非常难受。虽然QT也可以实现C++的界面设计,但是我和队友之前都没怎么接触过QT,在短时间内学习的成本太高,所以在UI设计这一块还是选择用java Swing了。这时候不禁要感叹了,老师在课上强调的代码的可扩展性和移植性有多重要了。在写一个程序时,事先预见它可能如何扩展是非常有必要的。如果我们能事先预知要实现用户界面的设计,又将节省很多时间了。UI设计这一块是队友实现的,不过我也对java Swing的一些组件进行了复习。每一个界面的显示都包装成函数,进行调用即可。在输入题目数量后,题目显示则是连续调用。老实说,上学期老师讲GUI时没怎么仔细听,课下也没有练习和应用,自然没什么印象了。这几天疯狂查资料,了解Swing组件,事件监听器怎么用,收获可以说是非常大了! 由于我们使用的是传统的java Swing,界面比较简洁,如下:
3.短信验证码功能:
短信验证码功能也是队友的模块,它的实现主要是调用阿里云短信服务的API。因为阿里云只提供100条免费短信,担心不够用,于是我自己也申请了RAM用户。因为自己第一次接触这些东西,刚开始非常懵逼,就照着它的帮助文档一步一步做,总算明白了这些都是为了获取参数,访问密匙什么的。其中涉及一些包的下载,短信签名和短信模版的申请。过程非常坎坷,开始在下载包时还遇到了这种:
mvn package命令,完全不懂,后面再看原来可以直接下载jar包=_= 再就是短信签名的申请,申请七八次都是过不了。后来也是通过微信小程序申请就很顺利就通过了,模版申请就很快,因为都有示例模版~
4.题目计算模块:
对于这个模块,因为我们开始时个人项目都是用c++写的,要改成java就有点浪费时间了。于是我们就想到了出题利用题库的方式,直接利用我们的c++程序产生题目,java程序就只用实现界面和验证码功能,这应该也算是对个人项目的一定复用吧。而在计算答案时,对于小学题目,我们利用两个栈(操作符栈、操作数栈),来存储读入的字符串表达式,然后根据输入的运算符优先级来判断是否运算,还是进行入栈操作,最终操作数的栈底就是表达式的结果。对于高中和初中的题目就是先把他们那些高优先级的符号算出来,题目转化成小学那种形式(转化中可能出现负数等要考虑,过程中我们一直解决一个问题后又出现一个问题=_=)再调用函数即可。但是据了解,java有一个函数eval()能直接计算表达式的值,也支持括号,内心十分mmp。
void Calculator() { stack<char> stkOpr; stack<double> stkDigit; Que+='#'; string str = "";///存储操作数 double left,right; char ch='#';///存储操作符 stkOpr.push(ch);///将#好入栈,放在符号栈低,作为运算结束的判断条件 bool kuohao = false;///表示符判断出栈的是否是括号 ///输入补位结束符,继续输入 for(int i=0;i<Que.length();i++) { ch=Que[i]; ///如果是操作数,则进行入栈操作 if(isdigit(ch) || '.'==ch) { str.append(1,ch); continue; } else if(isOperator(ch)) { bool flag=true;///标识符判断是否连续输入运算符或者括号 if(0 == strlen(str.data())) flag=false; else { right = atof(str.c_str());///将输入操作数赋给右操作数 str = "";///清空当前操作数,准备接收新的操作数 } ///比较栈顶与栈外优先级 char tc = stkOpr.top();///拿到栈顶的操作符 while(isp(tc)>icp(ch))///栈内优先级大时,循环计算优先级较高的运算 { flag=true;///进行运算相当于输入新的运算数 if(kuohao)///当上次栈顶为左括号时,将上次入栈的有操作数弹出栈,这里有点绕,得想想 { stkDigit.pop(); kuohao = false; } printstck(stkOpr,stkDigit);///测试打印两个栈的数据 if(!stkDigit.empty()) { ///栈顶操作数出栈赋给左操作数,同时将栈顶元素出栈 left = stkDigit.top(); stkDigit.pop(); ///计算当前运算并将计算结果入栈 cout<<"operater order:"<<left<<tc<<right<<endl; right = operater(left,right,tc); } else break; if(!stkOpr.empty())///栈顶操作符出栈 { stkOpr.pop();///计算过的操作符出栈 tc = stkOpr.top();///拿到栈顶的操作符 } else break; } ///右操作数入栈 if(flag) stkDigit.push(right); if(isp(tc)<icp(ch))///栈内优先级小 { ///符号入栈 stkOpr.push(ch); } else if(isp(tc)==icp(ch))///栈内优先级小 { if(!stkOpr.empty())///栈顶操作符出栈 { stkOpr.pop(); kuohao = true; } } }///else end } stkDigit.push(right);///计算结果入栈 ///表达式输入结束,数字栈栈底为所求表达式的值 result stkDigit.top(); } ///计算当前运算 double operater(double left,double right,char ch) { const double DEX = 1/pow(10,9);///定义运算精度 switch(ch) { case '+': return left+right; case '-': return left-right; case '*': return left*right; case '/': if(DEX>abs(right)) { cout<<"divide by 0! \n please try again!"<<endl; exit(1);///异常值,程序退出 } else return left/right; case '%': if(left == (int)left && right == (int)right) return (int)left%(int)right; else { cout<<"double type connot operator '%' ! \n please try again!"<<endl; exit(1);///异常值,程序退出 } default: exit(1);///异常值,程序退出 } } ///判断输入字符为运算符 bool isOperator(char ch) { switch(ch) { case '+': case '-': case '*': case '/': case '%': case ')': case '(': case '#': return true; default: return false; } } ///栈内优先级:)、*/%、+-(、# int isp(char ch) { switch(ch) { case '(': return 1; case '+': case '-': return 3; case '*': case '/': case '%': return 5; case ')': return 6; case '#': return 0; default: return -1; } } ///栈外优先级:(、*/%、+-、#) int icp(char ch) { switch(ch) { case '(': return 6; case '+': case '-': return 2; case '*': case '/': case '%': return 4; case ')': return 1; case '#': return 0; default: return -1; } } ///测试打印两个栈的数据 void printstck(stack<char> stkOpr, stack<double> stkDigit) { stack<char> s1; stack<double> s2; while(!stkOpr.empty()) { s1.push(stkOpr.top()); stkOpr.pop(); } while(!s1.empty()) { cout<<s1.top()<<" "; stkOpr.push(s1.top()); s1.pop(); } cout<<endl; while(!stkDigit.empty()) { s2.push(stkDigit.top()); stkDigit.pop(); } cout<<" "; while(!s2.empty()) { cout<<s2.top()<<" "; stkDigit.push(s2.top()); s2.pop(); } cout<<endl; }
-------------------------------------------------------------------------------------------------------------------------
我们的结对编程项目实现过程差不多就是这样,过程中遇到了各种各样的问题,尝试着一一解决,总体来说学到了很多很多。这次合作也是从队友那里学到了不少,对我来说合作还是十分愉快的,也体验了一把极限编程哈哈,非常感谢队友小姜对我的帮助!