用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各个模块实现组合

七· 效果展示

猜你喜欢

转载自www.cnblogs.com/RuningAnt/p/11964712.html