四则运算基本功能完成

一、基本功能的实现

在项目目录下新建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)

会自动截断非数字的字符,因此得出的分数只有‘/’符号前的分子。

我认为程序设计中思维缜密非常重要,今后必须注意这种细节上的区别。

四、总结


作为第一次个人项目,我觉得难度适中,但我自己的编程速度太慢了,如果还有时间,我希望能试着实现扩展功能。

原文地址:https://www.cnblogs.com/lifangda/p/5277642.html