Qt/C + + implementation of multifunctional calculator

Posted by Sydok on Sat, 18 Dec 2021 09:17:54 +0100

1, Interface layout

The following picture is the interface layout of the calculator. You can know the basic functions of the calculator from the interface, including basic operations of addition, subtraction, multiplication and division, cosine, sine and other function operations.

2, Function brief introduction and operation display


The above operations involve logarithm, exponent, multiplication and addition. Multiplication has a multiplier sign between "25" and ln by default (which is specifically reflected in the code).
Function introduction:
The calculator can realize simple addition, subtraction, multiplication and division, as well as the operation of bracketed, basic trigonometric function, logarithm and exponential expressions.
Fault tolerant processing:
1. When entering an expression in the text box, it will automatically detect whether the expression is correct. If the character to be entered will lead to an expression error, the character will not be entered into the text box, and qvector expr (described in the variable code) will not record the character.
2. Press the "=" key when the expression is incomplete, and no processing will be done.

3, Inverse Polish (infix to suffix)

Algorithm idea:
1: Traverse infix expression from left to right;
2: Direct output in case of digital;
3: Operator encountered:
①. If it is "(", directly enter the symbol stack;
②. If it is ")", the elements in the symbol stack will be output in turn until "(", "(" is out of the stack, and will not be output;
③. For other operators, the elements in the symbol stack are output in turn until an operator or "(" with lower priority than the current operator is encountered, and then the symbol is put on the stack.
④. After traversal, if the symbol stack is not empty, the elements in the symbol stack will be output successively until it is empty, and the new output expression is the suffix expression of the desired expression.
4: Suffix expression evaluation
①. Traverse the expression from left to right;
②. If it is a number, enter it directly into the number stack;
③. If it is binocular operator c, the two elements of the stack are recorded as a1 (first out of the stack) and A2 (second out of the stack); Make the number a3=a2 c a1(a2 in front and a1 in rear), and then press a3 into the number stack;
④. If it is a monocular operator c, an element is taken out of the number stack and recorded as a1 and A2 respectively (later out of the stack); Make the number a= c a1, and then press a into the number stack;
⑤ After traversal, there is still one element left in the number stack, which is the result of the expression.

4, Code analysis

// 1.expression.h
The expression class is designed to store key information. The key symbol is represented by QString data, and the key type is represented by btntype_ Type.

#include <QObject>
enum BtnType{
Num,//number
Point,//spot
BinocularOp,//Binocular operator
MonocularOp,//unary operator 
Special,//Special symbols
LeftParentheses,//Left parenthesis
RightParentheses,//Right parenthesis
PercentSign,//Percent sign
Index,//index
Back,//Backspace
Equl,//be equal to
Clear,//eliminate
};

class expression
{
public:
    expression();
    expression(QString mData,BtnType mType):data(mData),_type(mType){};
    void setData();
    void setType();
    QString getData();
    BtnType getType();
private:
    QString data;
    BtnType _type;  
};

// 2.expression.cpp
The key information is directly obtained by the constructor of expression (i.e. expression (qstring, MDATA, btntype, mtype): data (MDATA)_ type(mType){};) Information extraction is realized by getData and getType.

#include "expression.h"

expression::expression()
{//default constructor
}

QString expression::getData()
{
    return data;
}

BtnType expression::getType()
{
    return _type;
}

// 3.widget.h

#ifndef WIDGET_H
#define WIDGET_H
#include<QStack>
#include "expression.h"
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    void btnConnect();
    void onclicked(BtnType _type,QString _btn);

    void InToPo();//Infix expression to suffix expression
    bool checkParentheses();//Check whether the brackets in infix expression are correct
    int checkPoint(const QString& str);//Detect the number of points in the data and return.
    bool Operation();//Operation (calculated according to suffix expression)
    void TextOpention(const QString& newExprssion);//Operation of the text box after pressing the key
    bool checkLastChar();//Detects the last character of the expression
private slots:
    void on_BtnINV_clicked();

    void on_BtnRAD_clicked();

    void on_BtnMc_clicked();

    void on_BtnM_clicked();

    void on_Btnm_clicked();

    void on_BtnMr_clicked();

private:
    Ui::Widget *ui;
    QVector<expression> expr;//Infix expression
    QVector<expression> poexp;//Postfix Expression 
    QStack<double> num;//Digital stack
    QStack<expression> symbols;//Symbol stack
};
#endif // WIDGET_H

// 4.btnConnect function

void Widget::btnConnect()
{
    //Numeric key binding
    connect(ui->BtnZero,&QPushButton::clicked,[this](){onclicked(Num,"0");});
    connect(ui->BtnOne,&QPushButton::clicked,[this](){onclicked(Num,"1");});
    connect(ui->BtnTwo,&QPushButton::clicked,[this](){onclicked(Num,"2");});
    connect(ui->BtnThree,&QPushButton::clicked,[this](){onclicked(Num,"3");});
    connect(ui->BtnFour,&QPushButton::clicked,[this](){onclicked(Num,"4");});
    connect(ui->BtnFive,&QPushButton::clicked,[this](){onclicked(Num,"5");});
    connect(ui->BtnSix,&QPushButton::clicked,[this](){onclicked(Num,"6");});
    connect(ui->BtnSeven,&QPushButton::clicked,[this](){onclicked(Num,"7");});
    connect(ui->BtnEight,&QPushButton::clicked,[this](){onclicked(Num,"8");});
    connect(ui->BtnNine,&QPushButton::clicked,[this](){onclicked(Num,"9");});

    //Binocular operator binding
    connect(ui->BtnMultiply,&QPushButton::clicked,[this](){onclicked(BinocularOp,"*");});
    connect(ui->BtnDividing,&QPushButton::clicked,[this](){onclicked(BinocularOp,"÷");});
    connect(ui->BtnPlus,&QPushButton::clicked,[this](){onclicked(BinocularOp,"+");});
    connect(ui->BtnSubtracts,&QPushButton::clicked,[this](){onclicked(BinocularOp,"-");});

    //index
    connect(ui->BtnSemicolon,&QPushButton::clicked,[this](){onclicked(Index,"^(-1)");});
    connect(ui->BtnSquare,&QPushButton::clicked,[this](){onclicked(Index,"^(2)");});
    connect(ui->BtnIndex,&QPushButton::clicked,[this](){onclicked(Index,"^");});

    //spot
    connect(ui->BtnPoint,&QPushButton::clicked,[this](){onclicked(Point,".");});

    //Monocular operator binding
    connect(ui->BtnRoot,&QPushButton::clicked,[this](){onclicked(MonocularOp,"√");});
    connect(ui->BtnLn,&QPushButton::clicked,[this](){onclicked(MonocularOp,"ln");});
    connect(ui->BtnLog,&QPushButton::clicked,[this](){onclicked(MonocularOp,"log");});
    connect(ui->BtnSin,&QPushButton::clicked,[this](){onclicked(MonocularOp,"sin");});
    connect(ui->BtnCos,&QPushButton::clicked,[this](){onclicked(MonocularOp,"cos");});
    connect(ui->BtnTan,&QPushButton::clicked,[this](){onclicked(MonocularOp,"tan");});

    //Percent sign
    connect(ui->BtnPercentSign,&QPushButton::clicked,[this](){onclicked(PercentSign,"%");});

    //Bracket binding
    connect(ui->BtnLeftParenthesis,&QPushButton::clicked,[this](){onclicked(LeftParentheses,"(");});
    connect(ui->BtnRightParenthesis,&QPushButton::clicked,[this](){onclicked(RightParentheses,")");});

    //Special symbol binding
    connect(ui->BtnE,&QPushButton::clicked,[this](){onclicked(Special,"e");});
    connect(ui->BtnPai,&QPushButton::clicked,[this](){onclicked(Special,"π");});

    //empty
    connect(ui->BtnClear,&QPushButton::clicked,[this](){onclicked(Clear,"");});

    //Backspace
    connect(ui->BtnDelete,&QPushButton::clicked,[this](){onclicked(Back,"");});

    //be equal to
    connect(ui->BtnEqual,&QPushButton::clicked,[this](){onclicked(Equl,"");});
}

The percent sign is a monocular operator and the index is a binocular operator. Because it is different from other operators, a symbol type is set separately, which can still be classified as monocular and binocular operators when calculating the expression at the end. The above binds a type of key signal to the slot function onclicked.

// 5.onclicked function
Due to the length of the code, only the code when the number key is pressed is listed here.

void Widget::onclicked(BtnType _type, QString _btn)
{
  int rowCount=ui->textEdit->document()->lineCount();

        switch (_type)
        {
        case Num:
        {//number
            if(rowCount==0||rowCount==1)
            {
                if(!expr.isEmpty())
                {
                    switch (expr.back().getType())
                    {
                    case Num:
                    {
                        QString preNum=expr.back().getData();
                        if(checkPoint(preNum)==0)//There is no decimal point in the previous number
                        {
                            double tempNum;
                            tempNum=expr.back().getData().toDouble();
                            tempNum*=10;
                            tempNum+=_btn.toDouble();
                            expr.pop_back();//Delete previous data
                            expression curData(QString::number(tempNum),_type);
                            expr.push_back(curData);//Press the current data into the array

                            QString curExpression=ui->textEdit->toPlainText();
                            curExpression+=_btn;
                            TextOpention(curExpression);
                            break;
                        }
                       else if(checkPoint(preNum)==1)//There is a decimal point in the previous number
                        {
                            QString tempNum=expr.back().getData();
                            tempNum+=_btn;
                            expr.pop_back();
                            expression curData(tempNum,_type);
                            expr.push_back(curData);

                            QString curExpression=ui->textEdit->toPlainText();
                            curExpression+=_btn;
                            TextOpention(curExpression);
                            break;
                        }
                        break;
                    }
                    case Special: case RightParentheses: case PercentSign:
                    {
                        expression multiply("*",BinocularOp);
                        expr.push_back(multiply);

                        QString curExpression=ui->textEdit->toPlainText();
                        curExpression+=_btn;
                        TextOpention(curExpression);
                        expression curData(_btn,_type);
                        expr.push_back(curData);
                        break;
                    }
                    case LeftParentheses: case BinocularOp:
                    {
                        QString curExpression=ui->textEdit->toPlainText();
                        curExpression+=_btn;
                        TextOpention(curExpression);
                        expression curData(_btn,_type);
                        expr.push_back(curData);
                        break;
                    }
                    default:break;
                    }
                }
                else
                {
                    QString curExpression=ui->textEdit->toPlainText();
                    curExpression+=_btn;
                    TextOpention(curExpression);
                    expression curData(_btn,_type);
                    expr.push_back(curData);
                    break;
                }
                break;
            }
            break;
        }
      }
}

// 6.InToPo function (middle suffix)

void Widget::InToPo()
{
    while(!expr.isEmpty())
    {
        switch(expr.first().getType())
        {
        case Num: case Special:
        {//Number direct input suffix expression
            expression curData(expr.first().getData(),expr.first().getType());
            poexp.push_back(curData);
            break;
        }
        case LeftParentheses:
        {//The left bracket has the highest priority and is directly pushed into the symbol stack
            expression curChar(expr.first().getData(),expr.first().getType());
            symbols.push(curChar);
            break;
        }
        case RightParentheses:
        {//If the symbol is a right bracket, the symbols in the symbol stack will be out of the stack one by one and the suffix expression will be input until the left bracket is encountered
            while(symbols.top().getType()!=LeftParentheses)
            {
                expression curChar(symbols.top().getData(),symbols.top().getType());
                poexp.push_back(curChar);
                symbols.pop();
            }
            symbols.pop();//Left bracket out of stack
            break;
        }
        case BinocularOp:
        {//binary operator 
            if(expr.first().getData()=="+"||expr.first().getData()=="-")
            {//+, - has the lowest priority
                while(!symbols.isEmpty()&&symbols.top().getType()!=LeftParentheses)
                {//Push the elements in the symbol stack out of the stack until the symbol stack is empty or encounters an open parenthesis
                    expression curChar(symbols.top().getData(),symbols.top().getType());
                    poexp.push_back(curChar);
                    symbols.pop();
                }
                //Current symbol stack
                expression curChar(expr.first().getData(),expr.first().getType());
                symbols.push(curChar);
                break;
            }
            if(expr.first().getData()=="*"||expr.first().getData()=="÷")
            {//*The priority of ÷ is only higher than +-
                while(!symbols.isEmpty()&&(symbols.top().getType()!=LeftParentheses&&symbols.top().getData()!="+"&&symbols.top().getData()!="-"))
                {//Push the elements in the symbol stack out of the stack until the symbol stack is empty or encounters an open bracket or +-
                    expression curChar(symbols.top().getData(),symbols.top().getType());
                    poexp.push_back(curChar);
                    symbols.pop();
                }
                //Current symbol stack
                expression curChar(expr.first().getData(),expr.first().getType());
                symbols.push(curChar);
                break;
            }
            break;
        }
        case MonocularOp: case Index: case PercentSign:
        {//Priority is second only to the left parenthesis
            while(!symbols.isEmpty()&&(symbols.top().getType()==MonocularOp||symbols.top().getType()==Index||symbols.top().getType()==PercentSign))
            {//When the symbol stack is not empty, and the top element of the stack is a monocular operator, exponential symbol and percent sign, enter the top element of the stack into the suffix expression and delete the top element
                expression curChar(symbols.top().getData(),symbols.top().getType());
                poexp.push_back(curChar);
                symbols.pop();
            }
            //Current symbol stack
            expression curChar(expr.first().getData(),expr.first().getType());
            symbols.push(curChar);
            break;
        }
        default:break;
        }
        expr.pop_front();
    }
    if(expr.isEmpty()&&!symbols.isEmpty())
    {
        while(!symbols.isEmpty())
        {//The remaining symbols in the symbol stack are sequentially out of the stack and input into the suffix expression
            expression curChar(symbols.top().getData(),symbols.top().getType());
            poexp.push_back(curChar);
            symbols.pop();
        }
    }
}

// 7. Operation (calculation of suffix expression)

bool Widget::Operation()
{//The result is calculated according to the suffix expression
    bool chushu=true;
    while(!poexp.isEmpty())
    {
        switch(poexp.first().getType())
        {
        case Num:
        {
            double temp=poexp.first().getData().toDouble();
            num.push(temp);
            poexp.pop_front();
            break;
        }
        case BinocularOp:
        {
            double temp1=num.top();//Operand 1
            num.pop();
            double temp2=num.top();//Operand 2
            num.pop();
            double temp;
            if(poexp.first().getData()=="+")
            {//addition
                 temp=temp2+temp1;
            }
            else if(poexp.first().getData()=="-")
            {//subtraction
                 temp=temp2-temp1;
            }
            else if(poexp.first().getData()=="*")
            {//multiplication
                 temp=temp2*temp1;
            }
            else if(poexp.first().getData()=="÷")
            {//division
                if(temp1==0)
                {//Divisor cannot be zero
                    ui->textEdit->append("Divisor is zero,Incorrect expression!");
                    chushu=false;
                }
                else
                    temp=temp2/temp1;
            }
            num.push(temp);
            poexp.pop_front();
            break;
        }
        case PercentSign:
        {//percentage
            double temp1=num.top();//Operand
            num.pop();
            double temp=temp1/100;
            num.push(temp);
            poexp.pop_front();
            break;
        }
        case Special:
        {
            double temp;
            if(poexp.first().getData()=="e")
                temp=exp(1);
            else if(poexp.first().getData()=="π")
                temp=acos(-1);
            num.push(temp);
            poexp.pop_front();
            break;
        }
        case Index:
        {
            double temp1=num.top();//Operand 1
            num.pop();
            double temp2=num.top();//Operand 2
            num.pop();
            double temp;
            temp=pow(temp2,temp1);
            num.push(temp);
            poexp.pop_front();
            break;
        }
        case MonocularOp:
        {
            QString op=poexp.first().getData();
            double temp=num.top();//Operand
            if(op=="√")
                temp=sqrt(temp);
            else if(op=="sin")
                temp=sin(temp);
            else if(op=="cos")
                temp=cos(temp);
            else if(op=="tan")
                temp=tan(temp);
            else if(op=="ln")
                temp=log(temp);
            else if(op=="log")
                temp=log10(temp);
            num.pop();
            num.push(temp);
            poexp.pop_front();
           break;
        }
        default:break;
        }
    }
    return chushu;
}

This is my first time to write a blog. There are many shortcomings. If you have any questions, you can raise them and make progress together.
Source code and executable file download address:
Link: Project source code.

Topics: C++ Qt