软件工程第3次作业

要求0:

作业地址:https://edu.cnblogs.com/campus/nenu/2016CS/homework/2266

要求1:

git地址:  https://git.coding.net/Jingr98/f4.git

要求2:

1.结对编程同学的博客地址&姓名:https://www.cnblogs.com/Jingr/p/9924521.html  井冉

2.解题思路:

  当初看到四则运算这个题目时,第一想法是先解决计算表达式这一块,再来增加生成表达式的功能,大致形成计算表达式(满足功能1)-->计算表达式(满足功能2和功能3)-->随机生成表达式的流程思路。于是先在网上查找了四则运算计算这一块的内容,发现大多会使用中缀表达式与后缀表达式来完成,并采用双栈(即操作数一个栈,运算符一个栈),从这篇博客我还了解到挺多的(https://blog.csdn.net/that163/article/details/7558268),代码简短易懂。其次就是随机生成四则运算题目,在网上查阅了很多博主写的代码,参考了他们的随机生成算式的思路如下:

  1. 随机生成一个数字,代表着生成表达式中操作数的个数。
  2. 循环生成一个数字,将其输出,然后等概率生成‘+’‘-’‘*’‘/’中的一个跟在该数字后面,输出。
  3. 以一定概率生成左括号,若生成了左括号则输出,并进行计数标志当前共有多少个未完成匹配的左括号。
  4. 若当前有未完成匹配的左括号,则在生成一个数字后,生成一个操作符前,以一定的概率生成右括号。
  5. 在生成完毕后,生成最后一个数并将为匹配的左括号予以匹配。

附上博客地址,希望对大家有用(https://www.cnblogs.com/fanfan-blogs/p/5246469.html)。

3.重点/难点

1)功能1:支持整数和不含括号的四则运算且表达式可以重复。

  功能1只需要计算整数,所以唯一的难点是输出结果时的判断。当时没有读清题目,考虑不仔细,简单的判断若为整数,则原样输出,否则,保留3位小数,但发现会存在有可以被整除的小数,得到的结果可能是类似2.5,19.36等这样的形式,这时保留3位小数就会输出结果冗余。所以又改了思路,判断小数点后面的位数,如果位数超过3位,则保留3位输出,否则原样输出。附上判断小数位数代码。

函数point():判断小数点后面的位数是否超过3

/**
*函数名:point()
*函数功能:判断小数点后面的位数是否超过3
*param:double num
*/
int point(double num)
{
    int i,f = 0;
    num *= 1000;
    if(num - (int)num > eps)
        f = 1;
    return f;
}

2)功能2:支持小数和含小括号的四则运算且表达式可以重复。

  功能2涉及到小数和小括号的运算,同样也涉及到了结果的输出以及和用户的结果进行对比,判断是否正确。由于要求只需保留到3位小数,故设置eps=1e-4,当用户结果与正确答案相差小于eps时,则判定回答正确。其次,是由于小括号的加入,所以还涉及到了更进一步的优先级计算,于是直接设置了判断优先级的函数,给6种运算符赋予优先级。最后就是由于小括号的加入,需要随机生成小括号,下附部分代码:

//小括号的随机生成
            if( n < 3 && i < 6 && i > 0 ){
                s += "(";
                ans1++;
            }
            s += tmp;
            if( n > 6  && i > 0 && i < 6){
                if( ans1 > 0 )
                    ans1--;
                else
                    ans2++;
                s += ")";
            }
        } else {
            int n = rand() % 4;
            s += op[n];
        }
    }
    sort( a , a + 4 );
    pdd current = mk( a[0] , a[1] , a[2] , a[3] );
    if( stPdd.find(current) != stPdd.end() ){
        tmp++;
        return 1;
    }
    stPdd.insert( current );
    string lft , rht;
    //保证小括号成对出现
    for(int i = 0 ; i < ans2 ; i++ )
        lft += "(";
    for(int i = 0 ; i < ans1 ; i++ )
        rht  += ")";
    s = lft + s + rht;

3)功能3:表达式不重复且输出结果显示在控制台,且将控制台显示的结果输出到指定位置的txt文件中。

  功能3其实就是在功能2的基础上增加表达式不重复且将结果输出到指定文件中的功能。在使生成的表达式不重复上,我感觉还是挺难的,因为你需要判断交换律、结合律、分配律等导致的重复的情况,比较复杂,所以这个地方是参考了别人的代码,然后是将控制台结果输出到txt文件里,在这里运用到了C++的ofstream,

  ofstream fout( "d:\mytest.txt" );

  fout << "Learning C++ is very useful."<< endl;

搜索到了这样一篇博客,详细的讲解了ofstream与ifstream的用法(https://www.cnblogs.com/batman425/p/3179520.html)。

4)解决计算表达式的功能:利用双栈将中缀表达式转换成后缀表达式,遍历后缀表达式得到结果。此处通过双栈,分离开操作数与运算符,简化了计算,方便操作与判断。下附解决代码:

/**
*函数名:work()
*函数功能:利用双栈将中缀表达式转换成后缀表达式,遍历后缀表达式得到结果
*param:string s
*/
double work(string s )
{
    stack<double> sNum; //存储操作数的栈
    stack<char> sOp;    //存储运算符的栈
    int i = 0, flag = 1;
    char c;
    double x, y;
    sOp.push('');
    c = s[i];
    while (flag)
    {
        if (c >= '0' && c <= '9' || c == '.') {
            sNum.push(TransToNum(s, i));
        }
        else if (c == '' && sOp.top() == '') {
            flag = 0;
        }
        else if (c == '(' || (power(c) > power(sOp.top()))) {
            sOp.push(c);
            i++;
        }
        else if (c == ')'&& sOp.top() == '(') { //遇到一对括号,先将括号内算式进行计算
            sOp.pop();
            i++;
        }
        else if (power(c) <= power(sOp.top())) {
            x = sNum.top();
            sNum.pop();
            if(sNum.empty())
                y = 0;
            else {
                y = sNum.top();
                sNum.pop();
            }
            c = sOp.top();
            if( c == '/' && fabs( x ) < eps ){
                int n = rand() % 3;
                c = op[n];
            }
            sOp.pop();
            switch (c)
            {
                case '+':y = x + y; break;
                case '-':y = y - x; break;
                case '*':y = x * y; break;
                case '/':y = y / x; break;
            }
            sNum.push(y);
        }
        c = s[i];
    }
    return sNum.top();
}

4.编程收获:

  这次编程题目看似只是简单地四则运算,但是由于涉及到众多要求,后期改善时发现了一些bug,比如小数位数的判断与处理,重复表达式的处理等等。从前期的思路搭建,到实际代码编写的落实再到后期bug调试解决,都让我感觉到前期的仔细规划有多么重要。这次也是回到了基本的C++语言来编写,在查找参考他人的代码时,会碰到一些以前从未使用过的C++11标准与STL库等,通过对这些知识的学习,感觉自己对于C++的理解与掌握又更深了一步。

5.结对编程的体会:

  这次的结对编程是两人一起完成一个程序,在编写代码的过程中,一个人充当驾驶员,另一个人充当领航员,在这种模式下,当一方出现代码编写的bug时,另一方就会及时提醒,使其及时更正,与个人编程相比,结对编程一定程度上减少了很多不必要的bug的后期调试时间,当一方较为疲累是,也能相互鼓励监督,提高了编程效率。

  结对编程有利有弊,在一定程度上他是限制了写代码的自由,因为你需要考虑你的搭档的情况,但他又有着比个人编程更为优势的一点是你能接触到不同的思路,可以开拓自己的思维。总而言之,只要双方掌握好交流的度,互相尊重,结对编程能达到事半功倍的效果。

6.3项在编码、争论等活动中花费时间较长,给我较大收获的事件

1)初期解题思路发生分歧

起初我的思路是先解决计算表达式问题,再解决随机生成表达式问题,而我的搭档则恰好相反,两相商讨下,我们最后采用了先计算再生成的思路。由于解题思路商定费了一定的时间,加之一人查阅相关资料,另一人编写代码,刚开始双方步调都不太能协调的上,所以在编写代码前就费时不少。

2)编写代码时功能2的实现

功能2加入了小数和小括号计算,与之对应的题目生成就也必须加上小数和小括号的随机生成,这就与功能1相比上升了不少难度。我们两人在编码时就不知道该如何去解决这个问题,两人在网上查找资料,互相讨论,总结资料上的思路,最后是通过随机生成的2个整数相除来生成整数与小数,小括号则是利用对称保证它一定是成对出现的。

3)编写代码时功能3的实现

功能3需要判断表达式不重复,以及控制台结果输出到文本。后者还较为简单,但前者就比较复杂,讨论时我们一开始想着判断数字是否相等,以及数字间的运算符是否相等,但发现这种方法判断起来耗时速度慢,操作也比较复杂,查阅相关资料,参考其他同学的思路,发现只要判断数字是否重复即可,于是将数字存储到set集里,利用sort函数排序在比较是否有重复,以实现这一功能。

7.照片

原文地址:https://www.cnblogs.com/zhangxlBlog/p/9926518.html