一、基本功能的实现
在项目目录下新建txt文件,输入若干四则运算式,每个算式以“=”结束。
运行结果:
输入的答案可以是整数、分数。输入没有化简的分数算作正确。程序给出的正确答案是化简后的分数。
二、较第一版程序的改动
增加了两个类:Scanner类,用于处理算式;fraction类,表示分数。
Scanner类定义:
1 class Scanner { 2 private: 3 int mSize; //单词流的最大长度 4 int lsize; //单词流的实际长度 5 string *lex; //分割后的单词流 6 public: 7 string buffer; //保存从文件读入的算式 8 9 Scanner() { 10 mSize = MAXWORD; 11 lsize = 0; 12 lex = new string[mSize]; 13 } 14 int priority(const char op); //求参数符号的优先级 15 bool analyzer(); //词法分析 16 string *InfixExpToPostfixExp(); //中缀表达式转后缀表达式 17 };
fraction类定义:
1 class fraction { //分数类 2 private: 3 int numerator; //分子 4 int denominator;//分母 5 public: 6 fraction() { 7 numerator = 0; 8 denominator = 1; 9 } 10 fraction(int num, int den) { 11 numerator = num; 12 denominator = den; 13 } 14 void setFraction(int a, int b); 15 fraction getFraction(); 16 17 void operator=(const fraction &scd); // 18 bool operator==(const fraction &scd); // 19 fraction operator+(const fraction &scd); // 20 fraction operator-(const fraction &scd); // 重载运算符 21 fraction operator*(const fraction &scd); // 22 fraction operator/(const fraction &scd); // 23 friend ostream& operator << (ostream&, fraction&); // 24 25 void inputF(); //从标准输入键入分数值 26 int gcd(int a, int b); //求最大公约数 27 void simplify(); //分数化简 28 };
项目要求中有一项:
2.出现真分数和假分数的运算
学长在课上提出的具体要求是:算式中可以出现分数,用户输入的答案可以是分数,程序给出的答案以分数形式出现。
我最初的思路是,算式中的分数‘/’在计算上与除法‘÷’没有区别,那么程序不用特地处理分数,按照正常四则运算计算出一个实数答案即可。用户输入分数后,程序算出相应实数值并进行比较。
这种处理方式唯一的问题是程序给出正确答案时需要用分数表示,如何将实数转换为分数?
最直接的方法是用小学数学的知识:如何把无限循环小数弄成分数?
但是我发现当分母过大时,计算机显然不能存储太长的小数值,因此我决定将输入的数统一转换成分数形式,增加处理分数的fraction类。当负责计算结果的函数处理表达式时,将遇到的操作数变成分母为1的分数。按照分数的四则运算方法计算结果,然后化简为最简分数。
文件读取操作在main函数中实现。
下面给出main函数的代码:
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 string *PostfixExp; //后缀表达式 4 fraction UserResult,CurrectResult; //用户输入结果和正确答案 5 fraction score; //存储成绩 6 Scanner equation; //存储算式 7 int testNum = 0, currectNum = 0; //当前题目数和答对数 8 ifstream inputFile("test.txt", ios::in); 9 if (!inputFile.is_open()) { 10 cout << "can't open file!" << endl; 11 return 0; 12 } 13 score.setFraction(0, 0); //分数初始化 14 while (!inputFile.eof()) { //读取文件内容,直到遇到文件结束符 15 equation.buffer.clear(); //清空buffer 16 getline(inputFile, equation.buffer); //读取新的一行 17 if (!equation.analyzer()) { //处理词法分析失败 18 cout << "wrong equation" << endl; 19 } 20 else { 21 testNum++; 22 PostfixExp = equation.InfixExpToPostfixExp(); //将中缀表达式转换为后缀表达式 23 CurrectResult = MainCalculate(PostfixExp); //保存计算结果 24 CurrectResult.simplify(); //化简结果 25 cout << equation.buffer << endl << "input your result:" << endl; 26 UserResult.inputF(); //用户输入结果 27 UserResult.simplify(); //化简用户的结果 28 if (UserResult == CurrectResult) { //如果答案正确,得分+1 29 currectNum++; 30 score.setFraction(currectNum, testNum); 31 cout << "currect! score:" << score << endl; 32 } 33 else { //如果答案不正确,输出正确答案,不加分 34 score.setFraction(currectNum, testNum); 35 cout << "wrong! anwser:" << CurrectResult << "score:" << score << endl; 36 } 37 } 38 } 39 inputFile.close(); //关闭文件 40 return 0; 41 }
三、遇到的问题与解决方法
最早遇到的问题是在main函数中,我只定义一个Scanner类存储算式,每次读入的算式会覆盖掉上一次读入的算式。但是运行后程序显示出的“正确答案”是错的,而且能看出和前一个算式有
关。我分析认为虽然buffer中的内容清空了,但是类成员lex[]的内容没有清空,如果第二次输入的算式比第一次短,那么lex[]中就会留有上一次算式的结果。因此在成员函数
string *InfixExpToPostfixExp();的结尾加上
1 for (int i = 0; i < mSize; i++) //清空单词流 2 lex[i].clear();
问题得到解决。
第二个遇到的问题是编译时出现错误提示:
原因是我在fraction类中重载插入符“>>”,想用
os >> out.numerator >> "/" >> out.denominator >> endl;
直接将输入的字符串变为分数,看来编译器并不承认我想当然的写法:D
后来我在网上查找了各种重载插入符的文章,都没有实现格式化输入的,我只好写一个void inputF();手动将输入的字符串转为分数。
1 void fraction::inputF() { 2 unsigned int pos; 3 string str; 4 size_t sz; 5 this->setFraction(0, 0); 6 cin >> str; 7 for (pos = 0; pos < str.length() && str[pos] != '/'; pos++); //将指针移到输入流的‘/’位置,或结果为整数时移到末尾 8 if (pos == 0) { //没有分子的情况 9 cout << "fraction illegal." << endl; 10 exit(0); 11 } 12 else if (pos == str.length()) { //没有分母的情况 13 this->numerator = stoi(str); 14 this->denominator = 1; 15 } 16 else if (str[pos] == '/') { //有分子分母的情况 17 //in.setFraction(stoi(str, &sz, 10), stoi(str.substr(sz + 1), nullptr, 10)); 18 this->numerator = stoi(str, &sz); 19 this->denominator = stoi(str.substr(sz + 1)); 20 }21 }
我觉得这并不是很好的解决方法,应该有方法可以通过重载插入符直接实现cin>>fraction对象,但是还没有找到,今后会继续找解决办法。
在写这个函数时还遇到一个bug卡了我十分钟,错误现象是输入的分数答案总是判断为错误。将函数分段测试才发现原因是
if(pos == str.length())写成了if(pos = str.length())
没想到还会犯这种初级错误,而且盯了十分钟都没看出来!测试时发现函数生成的分数只有分子,分母一直是1,这才发现是因为pos指针直接被赋值跳到了字符串的结尾。而函数stoi(str)
会自动截断非数字的字符,因此得出的分数只有‘/’符号前的分子。
我认为程序设计中思维缜密非常重要,今后必须注意这种细节上的区别。
四、总结
作为第一次个人项目,我觉得难度适中,但我自己的编程速度太慢了,如果还有时间,我希望能试着实现扩展功能。