• 《面向对象程序设计》 四 计算


    第四次作业,还没开始就要deadline了,我的内心嘿嘿嘿嘿嘿。

    第一步尝试 计算 优先级的实现

    计算器的计算部分最难实现的是优先级的计算。之前完成计算器时查阅过有关资料,了解到需要栈的使用以及前缀后缀中缀表达式的转换。
    最开始时我计划将中缀表达式转换为前缀表达式,再对前缀表达式进行求值。着手时发现转换过程需要从右至左扫描中缀表达式(即逆序),而之前的作业中将表达式拆分时运用queue,对于这种先进先出的结构,无法通过两个队列间的转换完成逆序,所以我选择将中缀表达式转换为后缀表达式再求值。

    step one: 将中缀表达式转换为后缀表达式

    1. 初始化两个栈,运算符栈signS,储存中间结果的栈numS。
    2. 从左至右扫描中缀表达式。(从队首开始扫描)
      2-1. 遇到操作数,将其压入numS。
      2-2. 遇到括号
      2-2-1. 若为"(",直接压入signS。
      2-2-2. 为")",依次弹出signS栈顶运算符并压入numS,直到遇到"(",将这对括号舍弃。
      2-3. 遇到运算符,比较其与signS栈顶运算符的优先级。
      2-3-1. signS为空或signS栈顶为"("或该运算符的优先级比signS栈顶元素高,该运算符压入signS。
      2-3-2. 其余情况,将signS栈顶元素弹出并压入numS,回到2-2继续比较。
    3. 将signS中元素依次弹出并压入numS。
    4. 此时numS中为后缀表达式的逆序情况.将numS中元素依次弹出并压入signS,signS中即为后缀表达式的正确打开方式。(依赖于栈的操作出栈入栈都对栈顶元素操作完成此逆序过程)。

    step two: 后缀表达式求值

    • 1 从左至右扫描signS,将数字压入numS。
    • 2 遇到运算符时,弹出numS栈顶的两个数,用运算符对他们做相应的计算,将运算结果压入numS。
    • 3 最后numS中的一个元素即位所求的值。

    代码贴

    #include"Calculation.h"
    #include<sstream>
    #define SIGNNUM 6
    #include<iostream>
    #include<ctype.h>
    using namespace std;
    string priority[SIGNNUM][2]={{"(","5"},{")","5"},{"+","1"},{"-","1"},{"*","2"},{"/","2"}}; // 储存运算符优先级数组 
    
    /*返回运算符在优先级数组中的位置,方便优先级比较*/
    int Calculation::comPri(string input)
    {
        for(int j = 0; j < SIGNNUM; j++)
        {
            if(input == priority[j][0])
            {
                return j; 
            } 
        }
    }
    
    /*将中缀表达式转换为后缀表达式*/ 
    void Calculation::toPrefix(queue<string> q)
    {
    
        string last;
        int size = q.size();
        numS.push("0"); 
        for(int i = 0;i < size; i++)
        {
            if( isdigit(q.front()[0])  || (q.front()[0] == '-' && isdigit(q.front()[1])) )              
            {
        	
                numS.push(q.front()); 
            }
            else if(q.front() == "(" )
            {
                signS.push(q.front());
            }
            else if(q.front() == ")" )
            {
                while(signS.top()!="(")
                {
                    numS.push(signS.top());
                    signS.pop();            
                }
                signS.pop();
            }
            else if(q.front() == "+" || q.front() == "-" || q.front() == "*" || q.front() == "/")
            { 
    
                    while(1)
        	    {
                        if(signS.empty() || signS.top() == "(" || priority[comPri(q.front())][1] > priority[comPri(signS.top())][1])
                        {
                            signS.push(q.front()); 
                            break;
                        }
                        else
                        {
                            numS.push(signS.top());
                            signS.pop();
                        }
                   }   
            }
    
            last = q.front();
            q.pop();
        }
    
        while(!signS.empty())
        {
            numS.push(signS.top());
            signS.pop();
        }
        while(!numS.empty())
        {
            signS.push(numS.top());
            numS.pop();
        }
    
    }
    
    
    /*后缀表达式求值*/ 
    void Calculation::Calculate()
    {
        stringstream stream;
        double num1;
        double num2;
        double res;
        string result;
        int size = signS.size();
        for(int i = 0; i < size; i++)
        {
            if(isdigit(signS.top()[0]) || (signS.top()[0] == '-' && isdigit(signS.top()[1])))                   
            {
                numS.push(signS.top()); 
            }
            else if(signS.top() == "+" || signS.top() == "-" || signS.top() == "*" || signS.top() == "/")
            {
                stream << numS.top();
                stream >> num1;
            
                stream.clear();
                numS.pop();
                stream << numS.top();
                stream >> num2;
        
                stream.clear();
                numS.pop();
        
            
                if(signS.top() == "+")
                {
                    res = num1 + num2;
                } 
                else if(signS.top() == "-")
                {
                    res = num2 - num1;
                }
                else if(signS.top() == "*")
                {
                    res = num1 * num2; 
                }
                else if(signS.top() == "/")
                {
                    res = num2 / num1;
                }
                stream << res;
                stream >> result;
                stream.clear();
                numS.push(result);
            
            }
        
            signS.pop();
        } 
    
        stream << numS.top();
        stream >> res;
        cout << res;
        stream.clear();
    }
    

    第二部分的完成包括了了解,使用stringstream对象简化类型转换。

    样例与注意点:

    基本数据类型转换例子 int转string

    #include <string>
    #include <sstream>
    #include <iostream> 
    int main()
    {
        std::stringstream stream;
        std::string result;
        int i = 1000;
        stream << i; //将int输入流
        stream >> result; //从stream中抽取前面插入的int值
        std::cout << result << std::endl; // print the string "1000"
    } 
    

    重复利用stringstream对象

    多次转换中使用同一个stringstream对象,记住每次转换前要使用clear()方法。
    在多次转换中重复使用同一个stringstream(而不是每次都创建一个新的对象)对象最大的好处在于效率。stringstream对象的构造和析构函数通常是非常耗费CPU时间的。

    以上两个部分粗略完成时并没有太大问题,大致思路完成后,一个bug出现了,对"-"的判断。依照之前将表达式拆解存入队列思路,数字与符号显性分开,而"-"可能是减号也可能是负号,目前解决方案为判断"-"的意义,若"-"前并没有符号或"-"前为"(",则判定为符号,先往栈中压入"0"做减法运算得到负数.目前还在调试中,调试是对思维的挑战。(该过程已修正)

    修正bug过程,修正题目中超过十位报错的处理,在Calculation类中新添getResult方法,使得计算过程仅需调用getResult一个方法。

        /*计算表达式*/ 
        void Calculation::getResult(queue<string> q)
        {
            Calculation::toPrefix(q);
            Calculation::Calculate();
        }
    

    4.14 修正bug与更新 在付少航姐的脑洞下,考虑如3 * -6 2(3+5)也为合理情况,修改Scan类中拆分表达式的方法,将判断负数过程合并到拆分表达式过程,同时加入对后种情况的判定,补入乘号变为2*(3+5)。
    更新一些判断,输入表达式要求()括号匹配,除数要求不为0,否则拒绝计算。
    还有很多情况是我没有考虑到或考虑到还没实现的(逃 ,希望一步步能做的更好,有所进步。

    第一步参考贴
    c++ 字符串流 sstream(常用于格式转换)
    前缀后缀中缀表达式


    第二步尝试 在命令行中调用编译好的可执行文件

    我的理解,c++是面向对象的语言,重要的是对象和对象能调用的方法,而不需要知道方法的具体实现,所以我的主函数只有对对象的创建及调用对象的相应方法。我对自己这部分的理解还存有疑惑,希望老师大神予以解答。
    在Scan类中添加judge方法进行判断命令行输入的表达式,将需要进行计算的表达式传给拆分表达式的方法。(已修正,在main函数里处理argc和argv)
    同时为了解决传入-a等参数时输出表达式的问题,在Scan类中添加bool类型isPrint,并传给输出表达式的函数。

    int main(int argc, char* argv[]) 
    {
    
        string input;	//接收键盘输入的表达式 	
        Scan sc;
        //cin>>input; 
    
        if(argc == 2)
        {
    	    input = argv[1];
    
        }
        else if(argc > 2)
        {
    	    sc.isPrint = true;
    	    input = argv [argc-1];
    	
        } 
    
            queue<string> q = sc.toStringQueue(input);  //存放获取的队列 
    
        Print pr;
        pr.printQue(q);
    
    
        Calculation cal;
        cal.getResult(q);
    
        return 0;
    }
  • 相关阅读:
    【原创】构建高性能ASP.NET站点之一 剖析页面的处理过程(前端)
    .NET 并行(多核)编程系列之七 共享数据问题和解决概述
    架构设计解惑
    项目开发经验谈之:设计失败的挫败感
    项目开发经验谈之:忆第一次设计Framework
    盲目的项目开发
    扩展GridView之添加单选列
    日期转换格式
    动手完善个性化弹出提示框的过程及乐趣
    SQL开发中容易忽视的一些小地方(六)
  • 原文地址:https://www.cnblogs.com/syaoyao/p/5364145.html
Copyright © 2020-2023  润新知