结对项目终于结束。下面是我们小组程序的总结。
之前急急忙忙地学习了MFC初步用法,并将之付诸实践,其取得的效果还是很令人满意的,出题小程序有模有样。
我们程序的思路大概是:以十道题为一次小测,判断对错并查看答案。题库一共有五十道题。当然,我们有自动出题的模块,可以自动生成五十道新题。还有判断题目错误(如除零错误,符号错误等),只是我们自动生成的题目不会存在这些问题>_<
这个是我们的界面,初始界面:
点击“自动出题”按钮会自动生成五十道题,并弹出对话框显示成功,像这样:
然后点击“开始测试”,会在左侧生成十道题,像这样:
可以注意到,“开始测试”按钮变成了“再次测试”,并且变灰了,不能按下。只有再答完题确认提交之后才会亮起,并显示下一道题:
答案会依序显示在右侧“答案”框中。若点击再次测试,则界面会回到上一张图那样。
如果完成五十道题的测试,则会显示这个:
当然可以通过重新生成五十道题,继续测试。
若想退出程序,则可以点击“退出测试”,或右上角的“X”。
下面是具体的代码:
这个是核心的计算器类,包括计算算式(中缀转后缀,后缀计算等),判断错误等功能:
class Calculator //核心计算器类 { public: //辅助计算参数 double result; //计算结果 fenshu fresult; //分数计算结果 MyError Error; //计算过程中是否有错误 string str; //存放中缀表达式 Calculator(string s) :fresult(1, 1) { //计算器初始化 u = new unit(); str = s; accuracy = 3; maxunit = 80; daterange = 100000; clear(); } MyError run() { //计算表达式的值,存入result MyError temperror = zzh(str); if (temperror != ERROR_NO) { Error = temperror; result = -11111; return Error; } int i; bool b = true; for (i = 0; i<str.size(); i++) { //没有小数点,就计算分数结果 if (str[i] == '.') { b = false; break; } } if (b) { temperror = getFResult(); //MessageBox(printError(temperror).c_str(), "ErrorError", MB_ICONHAND); if (temperror != ERROR_NO) { fenshu f(-1, -1); fresult = f; Error = temperror; return Error; } else if (abs(fresult.fz)>daterange || abs(fresult.fm)>daterange) { Error = ERROR_RANGE; return Error; } } else { temperror = getResult(); if (temperror != ERROR_NO) { Error = temperror; result = -11111; return Error; } else if (abs(result)>daterange) { Error = ERROR_RANGE; return Error; } } return ERROR_NO; } void clear() { //清空计算器一切辅助计算参数 num = 0; Error = ERROR_NO; result = 0; fenshu f(1, 1); fresult = f; str = ""; delete u; u = new unit[maxunit]; } void recalculator(string s) { //重启计算器对象 clear(); str = s; } string getMyResult() { //获得计算结果,小数结果或者分数结果 int i = 0; char s[20]; string ss; for (; i<str.size(); i++) { if (str[i] == '.') { if (accuracy != -1) //判断精度并输出 sprintf(s, "%.*lf", accuracy, result); else sprintf(s, "%g", result); ss = s; return ss; } } ss = fresult.getfenshu(); return ss; } MyError setDateRange(int type) { //设置数据范围 if (0<type) { daterange = type; return ERROR_NO; } else Error = ERROR_SET; return ERROR_SET; } MyError setMaxUnit(int num) { //设置最大识别数量 if (0<num&&num <= 80) { maxunit = num; u = new unit[maxunit]; //清空后缀表达式 this->num = 0; return ERROR_NO; } else Error = ERROR_SET; return ERROR_SET; } MyError setAccuracy(int a) { //设置精度 if (a >= -1 && a <= 6) { accuracy = a; return ERROR_NO; } else Error = ERROR_SET; return ERROR_SET; } private: //非辅助计算参数,设置后,除非重复设置,否则不会被clear之类的清除 int daterange; //算式参数中数据的范围 int maxunit; //算式参数最多能识别的字符数量 int accuracy; //小数精确位数,-1为不精确,即去掉所有末尾的0,其他数字即小数点后保留的位数 //辅助计算参数 unit *u; //存储后缀表达式 int num; //后缀表达式unit数量 MyError zzh(string s) { //中缀表达式转后缀表达式 if (s.size()>maxunit) { return ERROR_STRING; //error,传入的算式长度超过设置的最大识别数量 } char c; char *temp1 = new char[maxunit]; double temp; string stemp; stack<char> st; while (!s.empty()) { //如果字符串不为空则继续循环 c = s[0]; if (isoperator(c)) { //是操作符 s.erase(0, 1); //从string中删除操作符 if (pushintostack(c, &st) == ERROR_OPERATOR) return ERROR_OPERATOR; } else if (isnum(c)) { //是数字 stringstream sst(s); sst >> temp; sprintf(temp1, "%g", temp); stemp = temp1; s.erase(0, stemp.size()); //从string中删除数字 sst.clear(); u[num++].set(temp); //存储数字到栈中 } else { return ERROR_STRING; } } if (pushintostack('#', &st) == ERROR_OPERATOR) return ERROR_OPERATOR; return ERROR_NO; } bool isoperator(char c) { //判断是否是操作符 if (c == '+') return true; if (c == '-') return true; if (c == '*') return true; if (c == '/') return true; if (c == '(') return true; if (c == ')') return true; return false; } bool isnum(char c) { if (c >= '0'&&c <= '9') return true; return false; } int youxian(char c1, char c2) { //判断两操作符优先级 if (c2 == '#') //结束符 return 0; if (c2 == '(') return 1; if (c2 == ')') if (c1 == '(') return 2; else return 0; if (c1 == '(') if (c2 == '+' || c2 == '-' || c2 == '*' || c2 == '/') return 1; if (c1 == '*' || c1 == '/') return 0; if (c1 == '+' || c1 == '-') if (c2 == '*' || c2 == '/') return 1; else if (c2 == '+' || c2 == '-') return 0; return -1; //非法运算符 } MyError pushintostack(char c, stack<char> *st) { //将操作符执行一系列入栈判断操作 char a; int y = 0; while (!st->empty()) { a = st->top(); y = youxian(a, c); if (y == 0) { //后来的操作符优先级小 st->pop(); u[num++].set(a); } else if (y == 1) { //后来的操作符优先级大 break; } else if (y == 2) { //俩操作符是'('和')' st->pop(); return ERROR_NO; } else return ERROR_OPERATOR; } st->push(c); return ERROR_NO; } void test() { //输出后缀表达式,测试用(暂留) int i; cout << num << endl; for (i = 0; i<num; i++) { if (u[i].kind == 1) cout << u[i].op << " "; else if (u[i].kind == 2) cout << u[i].num << " "; } } MyError getResult() { //由run函数调用,获取小数结果,存入result中 int i; char op; double num1, num2; stack<double> st; for (i = 0; i<num; i++) { //处理后缀表达式 if (u[i].kind == 2) { //如果是数字则入栈 st.push(u[i].num); } else if (u[i].kind == 1) { //如果是操作符,则出栈两个数字 op = u[i].op; if (st.empty()) return ERROR_STRING; //算式非法 num2 = st.top(); st.pop(); if (st.empty()) return ERROR_STRING; //算式非法 num1 = st.top(); st.pop(); switch (op) { case '+': st.push(num1 + num2); break; case '-': st.push(num1 - num2); break; case '*': st.push(num1*num2); break; case '/': if (num2 == 0) return ERROR_ZERO; //除0错误 st.push(num1 / num2); break; } } else return ERROR_STRING; //算式非法 } result = st.top(); return ERROR_NO; } MyError getFResult() { //由run函数调用,获取分数结果,存入fresult中 int i; char op; fenshu f1(1, 1), f2(1, 1); stack<fenshu> st; for (i = 0; i<num; i++) { if (u[i].kind == 2) { //如果是数字则入栈 st.push(fenshu(u[i].num, 1)); } else if (u[i].kind == 1) { //如果是操作符,则出栈两个数字 op = u[i].op; if (st.empty()) return ERROR_STRING; //算式非法 f2 = st.top(); st.pop(); if (st.empty()) return ERROR_STRING; //算式非法 f1 = st.top(); st.pop(); switch (op) { case '+': st.push(f1 + f2); break; case '-': st.push(f1 - f2); break; case '*': st.push(f1*f2); break; case '/': if (f2.fz == 0){ return ERROR_ZERO; //除0错误 } st.push(f1 / f2); break; } } else return ERROR_STRING; //算式非法 } fresult = st.top(); return ERROR_NO; }
这个是定义的错误以及显示错误的函数:
enum MyError { ERROR_NO = 0, ERROR_SET = 1, ERROR_OPERATOR = 2, ERROR_ZERO = 4, ERROR_STRING = 8, ERROR_RANGE = 16 }; string printError(MyError me) { if (me == ERROR_NO) { return "没有错误"; } else if (me == ERROR_SET) { return "设置参数非法"; } else if (me == ERROR_OPERATOR) { return "操作符非法"; } else if (me == ERROR_ZERO) { return "除0错误"; } else if (me == ERROR_STRING) { return "算式非法"; } else if (me == ERROR_RANGE) { return "计算结果超出范围"; } else { return "未定义的错误类型"; } //cout<< " 错误代码:" << me << endl << endl; }
因为改用了MFC,所以以前的cout都换成了return,返回string直接显示在MessageBox中。
下面这个是生成算式的按钮,如之前所言,避免了各种错误(如除零错误,算是非法等)的发生:
void CMFCTest4DlgOnBnClickedAuto() { TODO 在此添加控件通知处理程序代码 srand((int)time(0)); string equation; char temp[100]; ofstream outf(equation.txt); int i = 50; cout 输入生成算式的数量:; cin i; while (i--) { equation.clear(); if (i % 2) { 分数运算 char lastop = '+'; 上一个运算符 int num = random(3) + 4; 算式包含的操作数个数-1 sprintf(temp, %d, random(20) + 1); 第一个操作数 equation.append(temp); while (num--) { int b; if (lastop == '') 防止连续除法的出现 b = random(2); else b = random(12); switch (b) { case 0 case 4 case 8 lastop = temp[0] = '+'; break; case 1 case 5 case 9 lastop = temp[0] = '-'; break; case 2 case 6 case 10 lastop = temp[0] = ''; break; case 3 case 7 case 11 lastop = temp[0] = ''; break; } temp[1] = 0; equation.append(temp); sprintf(temp, %d, random(20) + 1); equation.append(temp); } int k, a = 0; for (int j = 0; jequation.size(); j++) { 添加括号 if ((equation[j] == '+' equation[j] == '-') && a == 0) { a++; } else if ((equation[j] == '+' equation[j] == '-') && a == 1) { k = j - 1; 添加左括号 while (!isoperator(equation[k - 1]) && k != 0) k--; if (equation[k - 1] == '') { k--; while (!isoperator(equation[k - 1]) && k != 0) k--; } equation.insert(k, (); k = j + 2; 添加右括号 while (!isoperator(equation[k + 1]) && k != equation.size() - 1) k++; equation.insert(k + 1, )); break; } } coutequationendl; } else { 小数运算 char lastop = '+'; 上一个运算符 int num = random(3) + 4; 算式包含的操作数个数-1 int temp1 = random(200) + 1; sprintf(temp, %g, temp1 10.0); 第一个操作数 equation.append(temp); while (num--) { int b; if (lastop == '') 防止连续除法的出现 b = random(2); else b = random(12); switch (b) { case 0 case 4 case 8 lastop = temp[0] = '+'; break; case 1 case 5 case 9 lastop = temp[0] = '-'; break; case 2 case 6 case 10 lastop = temp[0] = ''; break; case 3 case 7 case 11 lastop = temp[0] = ''; break; } temp[1] = 0; equation.append(temp); int temp2 = random(200) + 1; if (equation[equation.size() - 1] == '' && (temp1%temp2) != 0) { temp2 = temp1 5 + 1; while (temp1%temp2) { temp2++; } } temp1 = temp2; sprintf(temp, %g, temp2 10.0); equation.append(temp); } coutequationendl; } outf equation endl; } //cout 生成算式成功 endl; MessageBox(生成50道算式成功, 提示, MB_ICONINFORMATION); GetDlgItem(IDOK2)-EnableWindow(true); in.close(); in.open(equation.txt); outf.close(); }
下面这个是点击“确认提交”按钮之后会发生的事情:
void CMFCTest4DlgOnBnClickedButton10()//确认提交按钮 { int a = 0; //循环存用户答案 while (a 10) { cal.recalculator(str_buf[a]); //重启计算器,并传入算式参数 temperror = cal.run(); MessageBox(printError(temperror).c_str(), ERROR, MB_ICONHAND); if (cal.Error != ERROR_NO) //显示错误 { MessageBox(printError(temperror).c_str(), ERROR, MB_ICONHAND); } switch (a) { case 0 GetDlgItemText(IDC_EDIT1, answer[a]); SetDlgItemText(IDC_A1, cal.getMyResult().c_str()); break; case 1 GetDlgItemText(IDC_EDIT2, answer[a]); SetDlgItemText(IDC_A2, cal.getMyResult().c_str()); break; case 2 GetDlgItemText(IDC_EDIT3, answer[a]); SetDlgItemText(IDC_A3, cal.getMyResult().c_str()); break; case 3 GetDlgItemText(IDC_EDIT4, answer[a]); SetDlgItemText(IDC_A4, cal.getMyResult().c_str()); break; case 4 GetDlgItemText(IDC_EDIT5, answer[a]); SetDlgItemText(IDC_A5, cal.getMyResult().c_str()); break; case 5 GetDlgItemText(IDC_EDIT6, answer[a]); SetDlgItemText(IDC_A6, cal.getMyResult().c_str()); break; case 6 GetDlgItemText(IDC_EDIT7, answer[a]); SetDlgItemText(IDC_A7, cal.getMyResult().c_str()); break; case 7 GetDlgItemText(IDC_EDIT8, answer[a]); SetDlgItemText(IDC_A8, cal.getMyResult().c_str()); break; case 8 GetDlgItemText(IDC_EDIT9, answer[a]); SetDlgItemText(IDC_A9, cal.getMyResult().c_str()); break; case 9 GetDlgItemText(IDC_EDIT10, answer[a]); SetDlgItemText(IDC_A10,cal.getMyResult().c_str()); break; } if (answer[a].GetString() == cal.getMyResult()) { correct++; } else { incorrect++; } a++; } char buf[2][3]; sprintf(buf[0], %d, correct); sprintf(buf[1], %d, incorrect); SetDlgItemText(IDC_CORRECT, buf[0]);//显示正确的题数 SetDlgItemText(IDC_INCORRECT, buf[1]);//显示错误的题数 GetDlgItem(IDOK2)-EnableWindow(true);//将“再次测试”按钮设为可以点击 GetDlgItem(IDC_BUTTON10)-EnableWindow(false);//将自己设为不能点击 }
其中char型二维数组是存放正确和错误题数的,我设置了两个全局变量,correct和incorrect来记录正确错误题数。
下面是“开始测试”按钮和“再次测试”按钮:
void CMFCTest4DlgOnBnClickedOk2() //真正的开始按钮 { //TODO 在此添加控件通知处理程序代码 int e = 0; last = false; correct = 0; incorrect = 0; SetDlgItemText(IDC_CORRECT, 0); SetDlgItemText(IDC_INCORRECT, 0); while (e 10) { if (in str_buf[e]) { switch (e) { case 0 SetDlgItemText(IDC_EDIT1, ); SetDlgItemText(IDC_A1, 第一题答案); SetDlgItemText(IDC_EQUALITY1, str_buf[e].c_str()); break; case 1 SetDlgItemText(IDC_EDIT2, ); SetDlgItemText(IDC_A2, 第二题答案); SetDlgItemText(IDC_EQUALITY2, str_buf[e].c_str()); break; case 2 SetDlgItemText(IDC_EDIT3, ); SetDlgItemText(IDC_A3, 第三题答案); SetDlgItemText(IDC_EQUALITY3, str_buf[e].c_str()); break; case 3 SetDlgItemText(IDC_EDIT4, ); SetDlgItemText(IDC_A4, 第四题答案); SetDlgItemText(IDC_EQUALITY4, str_buf[e].c_str()); break; case 4 SetDlgItemText(IDC_EDIT5, ); SetDlgItemText(IDC_A5, 第五题答案); SetDlgItemText(IDC_EQUALITY5, str_buf[e].c_str()); break; case 5 SetDlgItemText(IDC_EDIT6, ); SetDlgItemText(IDC_A6, 第六题答案); SetDlgItemText(IDC_EQUALITY6, str_buf[e].c_str()); break; case 6 SetDlgItemText(IDC_EDIT7, ); SetDlgItemText(IDC_A7, 第七题答案); SetDlgItemText(IDC_EQUALITY7, str_buf[e].c_str()); break; case 7 SetDlgItemText(IDC_EDIT8, ); SetDlgItemText(IDC_A8, 第八题答案); SetDlgItemText(IDC_EQUALITY8, str_buf[e].c_str()); break; case 8 SetDlgItemText(IDC_EDIT9, ); SetDlgItemText(IDC_A9, 第九题答案); SetDlgItemText(IDC_EQUALITY9, str_buf[e].c_str()); break; case 9 SetDlgItemText(IDC_EDIT10, ); SetDlgItemText(IDC_A10, 第十题答案); SetDlgItemText(IDC_EQUALITY10, str_buf[e].c_str()); break; } } else { last = true; } e++; } //按完开始,变为”再次测试“后就不能再次按下 SetDlgItemText(IDOK2, 再次测试); GetDlgItem(IDOK2)-EnableWindow(false); GetDlgItem(IDC_BUTTON10)-EnableWindow(true); if (last) { MessageBox(没有更多的题了!测试结束!, 提示, MB_ICONINFORMATION); GetDlgItem(IDC_BUTTON10)-EnableWindow(false);//当没有更多题时,将“确认提交按钮设为enable。 in.close(); } }
以上就是我们结对项目主要的内容了。这次项目我没有划水,付出了自己的一份努力,完成之后神清气爽。希望小组项目也能同样顺畅。