用Qt實現一個計算器


一· 介紹

  • 目的: 做一個標准型的計算器。用於學習Qt基礎學習。
  • 平台: Qt 5.12.0

二· 結構框架設計

2.1最終產品樣式

界面的設計大體按照win系統自帶的計算器做模仿。左邊是win7 的界面。本次項目是為了熟悉Qt的界面設計,取消掉一些復雜的計算,和一些比較花樣的布局。最終實現右邊圖樣的界面。

2.2 架構規則

  • 弱耦合,強內聚,業務邏輯與界面邏輯一定要分離
  • 模塊之間使用接口進行關聯
  • 模塊之間的關系必然是 單項依賴 ,最大力度避免循環依賴關系

2.3 框架結構

​ 計算器一定包含界面類和 后台計算類。按照上述規則,設計如下:

  • 一個界面類 IOCalculator;

  • 一個后台計算的類 QCalculatorDec;

  • 一個接口類,用來連接 界面類和后台業務計算類 QcalculatorUI;

  • 一個 完整的計算機類 Qcalculator;(實現上述三個類的融合,對外就是一個計算器類)

三· 接口類的設計

接口類主要是實現界面類與業務邏輯的通信。最主要的有兩個內容

  1. 從界面類中得到 用戶輸入的計算公式。
  2. 由計算公式得到計算結果。

接口類只提供接口,UI 類使用該接口。業務邏輯類 負責具體的實現。

類的結構為:

class IOCalculator
{
public:
    virtual bool expression(const QString& exp )=0;
    virtual QString result()=0;
    virtual ~IOCalculator() ;
};

四· 界面類的設計

4.1 界面布局設計

直接離別別人設計好的

從圖中可以看到 界面類用到的元素

  • 一個窗口界面 (QWidget)
  • 一個顯示欄(QLineEdit)
  • 20個按鍵(QPushButton)

4.2 代碼設計

4.21 類的設計

UI類除了要實現界面,還用到接口。類的結構如下:

class QcalculatorUI : public QWidget
{
    Q_OBJECT

private:
    QPushButton * m_buttons[20];
    QLineEdit* m_ledit;
    IOCalculator* m_cal;

    QcalculatorUI();
    bool construct();

 private  slots:
     void getEquationFoUser();

public:
    static QcalculatorUI* NewInstance();
    void show();
    void importInterface(IOCalculator* cal );
    ~QcalculatorUI();
};

為了防止半成品的對象的生成,該類使用二階構造方式

4.22 具體實現

QcalculatorUI::QcalculatorUI() :QWidget(nullptr, Qt :: WindowMinimizeButtonHint | Qt:: WindowCloseButtonHint )
{
        m_cal =nullptr;
}


void QcalculatorUI::getEquationFoUser()
{
    QPushButton* binsig= dynamic_cast<QPushButton *>( sender());

    if(binsig != nullptr)
    {
        QString gets=binsig->text();
        if( "C"==gets   )
        {
            m_ledit->setText("");
        }
        else if("<-"==gets )
        {
            QString ls = m_ledit->text();
            if(ls.length()>0)
            {
                ls.remove(ls.length()-1,1);
            }
            m_ledit->setText(ls);
        }
        else if("="==gets)
        {
            if(m_cal != nullptr)
            {
                m_cal->expression(m_ledit->text());
                m_ledit->setText(m_cal->result());
            }
        }
        else
        {
            m_ledit->setText( m_ledit->text()+gets );
        }

    }
}


bool QcalculatorUI:: construct()
{
    bool ret =true;
    const char* btnText[20] =
    {
        "7", "8", "9", "+", "(",
        "4", "5", "6", "-", ")",
        "1", "2", "3", "*", "<-",
        "0", ".", "=", "/", "C",
    };

    m_ledit = new QLineEdit( this);
    if(m_ledit  != nullptr)
    {
        m_ledit->move(10,10 );
        m_ledit-> resize(240,30);
        m_ledit->setReadOnly(true);
        m_ledit->setAlignment(Qt::AlignRight);

    }
    else
    {
        ret =false;
    }


    for(int i=0;i< 5;i++)
        for(int j =0;j<4;j++)
        {
            m_buttons[j*5 +i] = new QPushButton(this);
            if(m_buttons[j*5+i] != nullptr)
            {
                    m_buttons[j*5 +i]->setText (btnText[ j*5+i]);
                    m_buttons[j*5 +i]->move(10+i*50,50+j*50);
                    m_buttons[j*5+i]->resize(40,40);
                    connect(m_buttons[j*5+i] ,SIGNAL(clicked()),this ,SLOT(getEquationFoUser()));
            }
            else
            {
                ret = false;
                continue;
            }
        }



    return ret;
}

 QcalculatorUI* QcalculatorUI:: NewInstance()
{
     QcalculatorUI* ret = new QcalculatorUI() ;
    if(ret == nullptr  || ! ret->construct()  )
    {
        delete ret;
        ret =nullptr;
    }
    return ret;
}


void QcalculatorUI :: show()
{
    QWidget ::show();
   setFixedSize(this->width(),this->height());

}

 void QcalculatorUI ::importInterface(IOCalculator* cal )
 {
     m_cal=cal;
 }


4.23 主要代碼解釋

1 二結構造類

​ 為了防止半成品的對象生成,所以使用二階構造模式。詳細請參考。

2 接口函數的使用

​ m_cal->expression(m_ledit->text()); 和 m_ledit->setText(m_cal->result());使用 接口對象,實現界面類和后台運算對象的交互。

五· 后台計算類

后台類主要實現計算器的計算,實現接口。

5.1 計算器算法

​ 計算器的計算方式和我們人類的計算方式大不相同。計算機只會按照一條條指令來執行,人的那種整體性的去認知和分析在計算機世界里是很難完成。所以要按照計算機的思路設計算法。

5.11 元素分離

​ 該類從UI界面類得到的表達式,對於計算器(准確的說是 C++編譯器)他只是一個字符串。想把該字符解釋成(當做)一個表達式。則該表達式中的元素有 四則遠算,數字,和正負號,還有括號符,每個元素根據前后的元素不同,就有不同的意義。例如 一個“+”元素,如果該元素前邊是一個數字,后邊是個數字,那該元素是加號,如果前邊元素是個符號,后邊是個數字元素,則是個正號。所以簡單的把字符串拆成每個字符是毫無意義。需要根據字符的前后特性,識別出每個表達式的元素,才能有后邊的計算的可能。所以先實現 元素分離。

​ 隨便找個表達式:“1/2*(-2-1.123)+1”來分析。不看看出,如果以 符號為標志對每個字符分析,就能實現分離。

  • 數字元素識別:

    1. 由於數字元素是多個字符組成。則要用字符串來存儲。
    2. 當前字符是數字字符或者小數點字符,則追加到到數字元素后邊。
  • 符號元素的識別:

    1. “*”,“/”,“(”, “)” 這些元素很好識別。直接可以識別出來。

    2. 如果遇到 “+”和“-”,如果① 該元素前是個符號且后邊為數字。②該元素位於當前表達式的首位。則該元素為 后邊的數字元素的正負號,存到下一個數字字符串中即可。

    偽碼為:

    // 用個對列分離出來的元素存起
    
    exp= 表達式字符串
    num 一個字符串的累加器
    
    for(int i=0;i<exp.length();<i++)
    {
        if(exp[i] == 數字 || exp[i] == 小數點 )
            num +=exp[i]; //追加
        else 
        {
            //到這里,前邊的數字元素已經結束
            保存num;
            if( exp[i] == “*”,“/”,“(”, “)”  )
            	保存該元素;
            else if( exp[i] == “+” “-” )
        	{
                if( ( i>0 && exp[i-1] == 字符 ) || (i==0))
                {
                        存入數字字符串;
                }
                else
                {
                    error;
                }
       		}  
         }
    
        else
        {
            error;
        }
            
            
    }
    

5.22 中綴表達式轉后綴表達式

​ 已經有了可以定性(知道該元素在表達式里代表什么意義)的元素,接下來就可以根據元素來做計算了。分析當前處境所遇到的問題是:計算機只能順序的執行指令,面對改變順序執行的 優先級的運算規則無法處理。則需要

  • 四則運算符需要得到該運算符的優先級

  • 用后綴表達式代替中綴表達式來解決 括號帶來的運算順序的改變問題。

    1. 當前元素為 左括號 : 入棧

    2. 當前元素為 右括號 :彈出棧的元素到出現 左括號並把左括號也彈出

    3. 當前元素為數字:輸出

    4. 當前元素為運算符:

      ​ 1) 與棧頂元素比較優先級

      ​ 2) 大於棧頂元素,入棧

      ​ 3) 小於等於則把棧頂元素彈出,一直到大於棧頂元素,入棧

    偽碼為:

    // 中綴轉后綴,把轉好的元素按規則存到一個隊列里
    //exp是個隊列,存放已經做了分離的元素
    while(exp != 空)
    {
        QString s=exp.dequeue();
        if(s==數字 )
        {
            放入 out隊列;        
        }
        else if(s==運算符)
        {
            while(getPriority( s) <= getPriority( stack.top() ) )
                stack.pop() 放入到out隊列;
        }
        else if(s== "(")
            入棧
         else if(s == ")")
         {
               while(  "(" !=  stack.top() && ( stack.length()>0 )  )
               {
                   stack.pop() 放入到out隊列;
               }
               if(stack.length()>0)
               {
                   //"("已經不需要了。丟到
                   stack.pop();
               }
             else
             {
                 // 說明棧里沒有 "(",表達式有誤
                 error;
             }
    
          }
        else
        {
            error;
        }
    }
    

5.23 后綴表達式的計算

​ 經過前邊兩步的處理,用后綴表達式計算 該表達已經萬事俱備了。后綴表達式的計算規則為:

  • 當前元素為 數字:入棧
  • 當前元素為 運算符:
    1. 從棧中彈出 右操作數
    2. 從棧中彈出 左操作數
    3. 根據運算符,進行計算
    4. 把計算結果壓棧

5.2 后台計算類設計

​ 前邊的5.1 章節做了計算器的算法處理,這只是該類的內部功能的實現。根據項目的整體結構設計。該類要是對接口的函數做具體的實現。則代碼如下:(私有函數是在具體代碼實現是才能確定,主要脈絡是對外的函數)

class QCalculatorDec :public IOCalculator
{
private:
    QString m_exp;
    QString m_result;

    bool isNumberOrDec(QChar c );
    bool isSymbol(QChar c);
    bool isOperator(QString c);
    bool isSign(QChar c);
    bool isNumber(QString s);
    int getPriority( QString& s);
    QString getResult(const QString& var1,const QString& ver2,const QString& oper );

    QQueue<QString> split(const QString& exp);
    bool transverter(QQueue<QString>& exp , QQueue<QString>& output );
    QString suffixCalculate(QQueue<QString>& exp);

public:
    QCalculatorDec();
    bool expression(const QString& exp );
    QString result();
    ~QCalculatorDec();
};

5.3 具體實現

QCalculatorDec::QCalculatorDec()
{

}

bool QCalculatorDec:: isNumberOrDec(QChar c )
{
   return ((c>='0'&& c<='9') || c=='.');
}

bool QCalculatorDec:: isSymbol(QChar c)
{
    return(isOperator(c) ||c=='(' || c==')');
}
bool QCalculatorDec ::isOperator(QString c)
{
    return(c =="+" || c =="-" || c =="*" || c =="/");
}

bool QCalculatorDec ::isSign(QChar c)
{
    return(c =='+' || c =='-');
}

int QCalculatorDec:: getPriority( QString& s)
{
    int ret=0;
    if(s=="+" || s=="-")
    {
        ret =1;
    }
    else if(s=="*" || s=="/")
    {
         ret =2;
    }

    return ret;
}

bool QCalculatorDec:: isNumber(QString s)
{
    bool ret =false;

    s.toDouble( &ret);

    return ret;
}


/* 元素 分離 */
QQueue<QString> QCalculatorDec::split(const QString& exp)
{
    QQueue<QString> ret;

    QString num;
    QString pre;

    for(int i=0; i<exp.length();i++)
    {
        if( isNumberOrDec(exp[i]))
        {
            num.append(exp[i]);
        }
        else if(isSymbol(exp[i]))
        {
            if(!num.isEmpty())
            {
                ret.enqueue(num);
                num.clear();
            }
            if(isSign(exp[i]) &&( pre=="" || pre=="(" || isOperator( pre) )  )
            {
                num.append(exp[i]);
            }
            else //肯定是運算符
            {
                ret.enqueue(exp[i]);
            }

        }
        pre = exp[i];
    }

    if(!num.isEmpty())
    {
        ret.enqueue(num);
        num.clear();
    }

    return ret;
}

/*中綴變后綴*/
bool  QCalculatorDec::transverter(QQueue<QString>& exp , QQueue<QString>& output )
{

    bool ret= true;
    QStack<QString> stack;
    stack.clear();

  //   while(!exp.isEmpty())
     for(int i=0;i<exp.length();i++)
    {
        if( isNumber(exp[i]))
        {
             output.enqueue(exp[i]);
        }
        else if(isOperator(exp[i]) )
        {
             while(!stack.isEmpty() && ( getPriority(exp[i])<= getPriority(stack.top() ) )   )
             {
                 output.enqueue(stack.pop());
             }

                 stack.push(exp[i]);
        }
       else  if(exp[i]== "(")
        {
            stack.push(exp[i]);
        }
        else if(exp[i]== ")")
        {
            while(!stack.isEmpty() && ( "(" != stack.top() )   )
            {
                output.enqueue(stack.pop());
            }
            if(!stack.isEmpty() &&( "(" == stack.top()) )
            {
                stack.pop();
            }
            else // if ( stack.isEmpty() || ("(" != stack.top() ))
            {
                ret=false;
            }

        }
        else
        {
            ret=false;
        }



    }

    while(!stack.isEmpty())
    {
        if("(" != stack.top())
        {
            output.enqueue(stack.pop());
        }
        else
        {
            ret=false;
            break;
        }
    }

    if(! ret)
    {
        output.clear();
    }

    return ret;
}


/*單純的計算*/
QString QCalculatorDec::getResult( const QString& var1,const QString& var2,const QString& oper )
{
  QString ret;

  if(isNumber(var1) && isNumber(var2) )
  {

      double lp = var1.toDouble();
      double rp = var2.toDouble();

        if("+" == oper)
        {
           ret.sprintf("%f", lp + rp);
        }
        else if("-" == oper)
        {
            ret.sprintf("%f", lp - rp);
        }
        else if("*" == oper)
        {
           ret.sprintf("%f", lp * rp);
        }
        else if("/" == oper)
        {
             if(rp<0.000000000001 && rp> -0.000000000001 )
                {
                     ret="error";
                }
              else
                 {
                     ret.sprintf("%f", lp / rp);
                 }
        }
        else
        {
             ret="error";
        }
    }
  else
  {
      ret="error";
  }

    return ret;
}
/*后綴表達式的計算*/
QString QCalculatorDec::suffixCalculate(QQueue<QString>& exp)
{
    QString ret;
    QStack<QString> stack;
    stack.clear();

    for(int i= 0; i<exp.length();i++)
    {
        if(isNumber(exp[i]))
        {
            stack.push(exp[i]);
        }
        else if(isOperator(exp[i] )  )
        {
            if(!stack.isEmpty())
            {
                QString leftVar,rightVar;

                rightVar=stack.pop();
                if(!stack.isEmpty())
                {
                    leftVar=stack.pop();
                    ret = getResult(leftVar,rightVar,exp[i]);
                    if("error"!= ret)
                    {
                        stack.push(ret);
                    }
                }
                else
                {
                    ret="error";
                }
            }
            else
            {
                ret="error";
            }
        }
        else
        {
            ret="error";
        }
    }

    if(1== stack.length())
    {
        ret =stack.pop();
    }
    else
    {
         ret ="error";
    }

    return  ret;
}


/*計算結果*/
bool QCalculatorDec:: expression(const QString& exp )
{
    bool ret=false;
    m_exp= exp;
    qDebug()<<"expression() is start " ;
    QQueue<QString> tranin=split(m_exp) ;
    QQueue<QString> tranOut;


   if( transverter( tranin, tranOut ) )
    {
      m_result = suffixCalculate(tranOut);
        ret = (m_result != "Error");
    }
    else
    {
        m_result= "error";
    }

    return ret;
}

QString QCalculatorDec :: result()
{
    return m_result;
}

5..4 主要代碼解釋

1 浮點數與0的比較

​ 在C/C++ 語言中,遇到 浮點數與0 的比較時,由於存儲特性。不能直接做相等比較,要與 很小的數字區間比較。

六· 計算類的實現

​ 為了架構的整潔和使用的方便,把計算機的各個部分用一個類做個整體的封裝。對外只有對象的生產和顯示接口。

6.1 計算類的設計

class Qcalculator
{
private:
    QcalculatorUI* m_ui;
    QCalculatorDec m_dec;

    Qcalculator();
    bool construct();
public:
    static Qcalculator * NewInstance();
    void show();
};

6.2 具體實現

Qcalculator::Qcalculator()
{

}

bool Qcalculator::construct()
{
     m_ui=  QcalculatorUI::NewInstance();
     if(m_ui != nullptr)
     {
        m_ui->importInterface(&m_dec);
     }
     return (m_ui != nullptr);
}

Qcalculator * Qcalculator:: NewInstance()
{
    Qcalculator* ret = new Qcalculator;
    if(ret ==nullptr || ! ret->construct())
    {
        delete ret;
        ret = nullptr;
    }

    return ret;
}

void Qcalculator :: show()
{
    m_ui->show();
}

6.3 主要代碼解釋

1各個模塊實現組合

七· 效果展示


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM