编译原理课程设计---公式计算器(基于Qt开发的带界面的简易计算器)

应编译原理课程设计要求,做了一个简易的计算器。项目基于Qt5.13.2开发。语法分析采用LL(1)递归下降文法。该计算器除了可以实现+、-、*、/功能之外,还可以对一些特殊函数以及字符进行运算,比如PI,e,sin,cos,log等等。在计算器界面中除了可以按下按钮进行相应计算,还可以通过键盘来进行输入计算。

参考资料

视频链接

使用的开发工具

运行环境:Qt 5.13.2
编程语言:C++

界面的设计

新建工程

Step1:点击New Project

在这里插入图片描述
Step2:选择Application里的Qt Widgets Application

在这里插入图片描述
Step3:对project进行命名并将其保存在某一文件路径中(命名任意均可,但不能有中文,中文符号也不能有,项目路径保存任意,没有要求)
在这里插入图片描述
Step4:默认下一步即可
在这里插入图片描述
Step5:Base class选择为QWidget,Class name可以自己任意命名,这里我命名为Calcilator_UI,还有就是一定要勾选Generate from(生成界面所用)
在这里插入图片描述
Step6:选择Desktop Qt 5.13.2 MinGW 64-bit
在这里插入图片描述
Step7:默认下一步
在这里插入图片描述

界面布局

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
这里的布局只是示范,小伙伴们可以根据自己的审美观念来设计更好看的布局

样式的添加

Step1:添加myhelper.h和myhelper.cpp文件
在这里插入图片描述
在这里插入图片描述
Class name定义为myhelper,Base class定义为QWidget
在这里插入图片描述
默认下一步,然后点击完成即myhelper创建成功

Step2:添加相应代码
myhelper.h文件如下:

#ifndef MYHELPER_H
#define MYHELPER_H

#include <QWidget>
#include <QtCore>
#include <QtGui>
#include <QDesktopWidget>
#include <QApplication>

class myhelper : public QWidget
{
    Q_OBJECT
public:
    explicit myhelper(QWidget *parent = nullptr);
    //设置为开机启动
    static void AutoRunWithSystem(bool IsAutoRun, QString AppName, QString AppPath)
    {
        QSettings *reg = new QSettings(
            "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
            QSettings::NativeFormat);

        if (IsAutoRun) {
            reg->setValue(AppName, AppPath);
        } else {
            reg->setValue(AppName, "");
        }
    }

    //设置编码为UTF8
    static void SetUTF8Code()
    {
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
        QTextCodec *codec = QTextCodec::codecForName("UTF-8");
        QTextCodec::setCodecForLocale(codec);
        QTextCodec::setCodecForCStrings(codec);
        QTextCodec::setCodecForTr(codec);
#endif
    }

    //设置皮肤样式
    static void SetStyle(const QString &styleName)
    {
        QFile file(QString("://image/%1.css").arg(styleName));
        file.open(QFile::ReadOnly);
        QString qss = QLatin1String(file.readAll());
        qApp->setStyleSheet(qss);
        qApp->setPalette(QPalette(QColor("#F0F0F0")));
    }

    //加载中文字符
    static void SetChinese()
    {
        QTranslator *translator = new QTranslator(qApp);
        translator->load("://image/qt_zh_CN.qm");
        qApp->installTranslator(translator);
    }

    //判断是否是IP地址
    static bool IsIP(QString IP)
    {
        QRegExp RegExp("((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)");
        return RegExp.exactMatch(IP);
    }



    //延时
    static void Sleep(int sec)
    {
        QTime dieTime = QTime::currentTime().addMSecs(sec);
        while ( QTime::currentTime() < dieTime ) {
            QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
        }
    }

    //窗体居中显示
    static void FormInCenter(QWidget *frm)
    {
        int frmX = frm->width();
        int frmY = frm->height();
        QDesktopWidget w;
        int deskWidth = w.width();
        int deskHeight = w.height();
        QPoint movePoint(deskWidth / 2 - frmX / 2, deskHeight / 2 - frmY / 2);
        frm->move(movePoint);
    }

signals:

public slots:
};

#endif // MYHELPER_H

myhelper.cpp文件如下:

#include "myhelper.h"

myhelper::myhelper(QWidget *parent) : QWidget(parent)
{

}

Step3:添加资源

在这里插入图片描述
选择Qt中的Qt Resource File

在这里插入图片描述
在这里插入图片描述
记住这里的名称一定要为rc,因为这是与程序里相对应的,若不命名为rc,则需对接下来的程序进行稍作修改

接下来就是默认完成
在这里插入图片描述
然后添加前缀
在这里插入图片描述
前缀的名称一定要为’/’
在这里插入图片描述
在这里插入图片描述
然后添加当前路径中image文件中所有的样式
在这里插入图片描述

在这里插入图片描述

然后再编译一下,编译后结果如下所示
在这里插入图片描述
image文件下载

Step4:添加icon_style.h和icon_style.cpp文件
在添加之前先在Calculator.pro文件中添加QT += sql,然后编译一下
在这里插入图片描述
接着添加icon_style.h和icon_style.cpp文件
在这里插入图片描述
Class name定义为icon_style,Base class定义为QWidget
在这里插入图片描述
接下来就是默认完成
在这里插入图片描述
Step5:添加相应代码
icon_style.h文件如下:

#ifndef ICON_STYLE_H
#define ICON_STYLE_H

#include <QWidget>
#include <QFont>
#include <QFontDatabase>
#include <QMutex>
#include <QLabel>
#include <QPushButton>
#include <QApplication>

class icon_style : public QWidget
{
    Q_OBJECT
public:
    explicit icon_style(QWidget *parent = nullptr);
    static icon_style* Instance()
    {
        static QMutex mutex;
        if (!_instance) {
            QMutexLocker locker(&mutex);
            if (!_instance) {
                _instance = new icon_style;
            }
        }
        return _instance;
    }

    void SetIcon(QLabel* lab, QChar c, int size = 10);
    void SetIcon(QPushButton* btn, QChar c, int size = 10);

signals:

private:
    QFont iconFont;
    static icon_style* _instance;

public slots:
};

#endif // ICON_STYLE_H

icon_style.cpp文件如下:

#include "icon_style.h"

icon_style* icon_style::_instance = 0;
icon_style::icon_style(QWidget *parent) : QWidget(parent)
{
    int fontId = QFontDatabase::addApplicationFont(":/image/fontawesome-webfont.ttf");
    QString fontName = QFontDatabase::applicationFontFamilies(fontId).at(0);
    iconFont = QFont(fontName);
}

void icon_style::SetIcon(QLabel* lab, QChar c, int size)
{
    iconFont.setPointSize(size);
    lab->setFont(iconFont);
    lab->setText(c);
}

void icon_style::SetIcon(QPushButton* btn, QChar c, int size)
{
    iconFont.setPointSize(size);
    btn->setFont(iconFont);
    btn->setText(c);
}

与此同时calculator_ui.h文件更改为如下:

#ifndef CALCULATOR_UI_H
#define CALCULATOR_UI_H

#include <QWidget>
#include <QApplication>

QT_BEGIN_NAMESPACE
namespace Ui { class Calculator_UI; }
QT_END_NAMESPACE

class Calculator_UI : public QWidget
{
    Q_OBJECT

public:
    Calculator_UI(QWidget *parent = nullptr);
    ~Calculator_UI();
    void InitStyle_Calculator();

private:
    Ui::Calculator_UI *ui;
    QPoint mousePoint;
    bool mousePressed;
    bool max;
    QRect location;
};
#endif // CALCULATOR_UI_H

calculator_ui.cpp文件更改为如下:

#include "calculator_ui.h"
#include "ui_calculator_ui.h"

#include "myhelper.h"
#include "icon_style.h"


Calculator_UI::Calculator_UI(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Calculator_UI)
{
    ui->setupUi(this);

    myhelper::FormInCenter(this);
    this->InitStyle_Calculator();

}

Calculator_UI::~Calculator_UI()
{
    delete ui;
}

void Calculator_UI::InitStyle_Calculator()
{
    //设置窗体标题栏隐藏
    this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint);
    location = this->geometry();
    max = false;
    mousePressed = false;

    //安装事件监听器,让标题栏识别鼠标双击
    ui->label_title->installEventFilter(this);

    icon_style::Instance()->SetIcon(ui->button_close, QChar(0xf00d), 10);
    icon_style::Instance()->SetIcon(ui->button_max, QChar(0xf096), 10);
    icon_style::Instance()->SetIcon(ui->button_min, QChar(0xf068), 10);
    icon_style::Instance()->SetIcon(ui->btnMenu, QChar(0xf0c9), 10);
    icon_style::Instance()->SetIcon(ui->lab_Ico, QChar(0xf015), 12);
}

main.cpp文件修改如下:

#include "calculator_ui.h"

#include "myhelper.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    myhelper::SetUTF8Code();
   myhelper::SetStyle("blue");//蓝色风格

   myhelper::SetChinese();

    Calculator_UI w;
    w.show();
    return a.exec();
}

编译运行后的效果如下图所示:

在这里插入图片描述
到这里按下界面上的按钮都不会有反应,接着添加缩小,放大以及关闭按钮功能
首先在ui设计界面那里添加槽函数,即对于缩小,放大以及关闭按钮右击,然后点击转到槽,然后按下ok即可。这里以button_close为例:
在这里插入图片描述
在这里插入图片描述
button_max和button_min类似

calculator_ui.h文件添加后如下:

#ifndef CALCULATOR_UI_H
#define CALCULATOR_UI_H

#include <QWidget>
#include <QApplication>

QT_BEGIN_NAMESPACE
namespace Ui { class Calculator_UI; }
QT_END_NAMESPACE

class Calculator_UI : public QWidget
{
    Q_OBJECT

public:
    Calculator_UI(QWidget *parent = nullptr);
    ~Calculator_UI();
    void InitStyle_Calculator();

protected:
    bool eventFilter(QWidget *obj, QEvent *event);
    void mouseMoveEvent(QMouseEvent *e);
    void mousePressEvent(QMouseEvent *e);
    void mouseReleaseEvent(QMouseEvent *);

private slots:
    void on_button_close_clicked();

    void on_button_max_clicked();

    void on_button_min_clicked();

private:
    Ui::Calculator_UI *ui;
    QPoint mousePoint;
    bool mousePressed;
    bool max;
    QRect location;
};
#endif // CALCULATOR_UI_H

calculator_ui.cpp文件添加后如下:

#include "calculator_ui.h"
#include "ui_calculator_ui.h"

#include "myhelper.h"
#include "icon_style.h"


Calculator_UI::Calculator_UI(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Calculator_UI)
{
    ui->setupUi(this);

    myhelper::FormInCenter(this);
    this->InitStyle_Calculator();

}

Calculator_UI::~Calculator_UI()
{
    delete ui;
}

void Calculator_UI::InitStyle_Calculator()
{
    //设置窗体标题栏隐藏
    this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint);
    location = this->geometry();
    max = false;
    mousePressed = false;

    //安装事件监听器,让标题栏识别鼠标双击
    ui->label_title->installEventFilter(this);

    icon_style::Instance()->SetIcon(ui->button_close, QChar(0xf00d), 10);
    icon_style::Instance()->SetIcon(ui->button_max, QChar(0xf096), 10);
    icon_style::Instance()->SetIcon(ui->button_min, QChar(0xf068), 10);
    icon_style::Instance()->SetIcon(ui->btnMenu, QChar(0xf0c9), 10);
    icon_style::Instance()->SetIcon(ui->lab_Ico, QChar(0xf015), 12);
}

bool Calculator_UI::eventFilter(QWidget*obj, QEvent *event)
{
    if (event->type() == QEvent::MouseButtonDblClick) {
        this->on_button_max_clicked();
        return true;
    }
    return QObject::eventFilter(obj, event);
}
void Calculator_UI::mouseMoveEvent(QMouseEvent *e)
{
    if (mousePressed && (e->buttons() && Qt::LeftButton) && !max)
    {
        this->move(e->globalPos() - mousePoint);
        e->accept();
    }
}

void Calculator_UI::mousePressEvent(QMouseEvent *e)
{
    if (e->button() == Qt::LeftButton) {
        mousePressed = true;
        mousePoint = e->globalPos() - this->pos();
        e->accept();
    }
}

void Calculator_UI::mouseReleaseEvent(QMouseEvent *)
{
    mousePressed = false;
}


void Calculator_UI::on_button_max_clicked()
{
    if (max) {
        this->setGeometry(location);
        icon_style::Instance()->SetIcon(ui->button_max, QChar(0xf096), 10);
        ui->button_max->setToolTip("最大化");
    } else {
        location = this->geometry();
        this->setGeometry(qApp->desktop()->availableGeometry());
        icon_style::Instance()->SetIcon(ui->button_max, QChar(0xf079), 10);
        ui->button_max->setToolTip("还原");
    }
    max = !max;
}

void Calculator_UI::on_button_min_clicked()
{
    this->showMinimized();
}

void Calculator_UI::on_button_close_clicked()
{
    qApp->exit();
}

添加相应代码后,界面就可以缩小,关闭,移动以及放大,注意这里按下放大键后界面不会放大,因为在界面设计时,整体界面的最大长度,宽度以及最小长度和宽度都固定了,若想看到放大功能只需在界面设计中进行相应的调整并布局

词法分析

准备工作

添加token.h和token.cpp文件,步骤与前面样式添加类似。
右击Calculator_Design -> 点击Add New -> 选择C++里的C++ Class -> Class name 定为 token -> Base class 定为QWidget -> 点击下一步 -> 点击完成

工程建立完成后,在正式编写代码时,先将Calculator_Design.pro中的c++ 11改为c++ 17标准,因为在后面的编程中需要用到c++17 新特性。
在这里插入图片描述

代码添加

token.h
#ifndef TOKEN_H
#define TOKEN_H

#include <QWidget>
#include <string>
#include <tuple>  //用来存储返回值
#include <variant>
#include <functional> //把lambda存储下来称为一个变量
#include <optional>


enum class TokenType
{
    //数字
    Number,
    //符号,英文字母a-z,A-Z
    Symbo,
    //用来表示结束
    End,	//终止符号
    //用来表示错误
    Error,	//错误符号
    Plus = '+',
    Minus = '-',
    Mul = '*',
    Div = '/',
    Lp = '(',
    Rp = ')',

};

struct Token  //定义符号结构体
{
    TokenType type;  //符号类型
    std::variant<double,std::string> value = 0.0;
};


//查找符号在符号表中的具体定义
std::optional<std::variant<double, std::function<double(double)>>> getSymboValue(std::string symbo);


//input = "1 + 2 * 3"
//tokenize("1 + 2 * 3") -> 符号1 + 剩余的字符串"+ 2 * 3"
//tokenize("+ 2 * 3") -> 运算符+ + 剩余的字符串"2 * 3"
//tokenize("2 * 3") -> 数字2 + 剩余的字符串"* 3"
//....
//这里返回值是多个,可以用tuple
std::tuple<Token, std::string> tokenize(std::string input);
//输入为 std::string input
//第一个返回值为Token,第二个返回值为剩下字符串std::string

class token : public QWidget
{
    Q_OBJECT
public:
    explicit token(QWidget *parent = nullptr);

signals:

public slots:
};

#endif // TOKEN_H

token.cpp
#include "token.h"
#include <math.h>
#include <stdexcept>  //异常处理
#include <unordered_map> //索引值可以为字符串
#include <functional> //把lambda存储下来称为一个变量


//符号表
//索引值是string
//返回类型为variant,有两种可能一种就是double,另一种是输入为double,返回为double的一个函数
static std::unordered_map<std::string, std::variant<double, std::function<double(double)>>> SymboTable
{
    {"PI",atan(1.0) * 4},
    {"e",exp(1.0)},
    {"sin",[](double val) {return sin(val); }},
    {"cos",[](double val) {return cos(val); }},
    {"asin",[](double val) {return asin(val); }},
    {"acos",[](double val) {return acos(val); }},
    {"tan",[](double val) {return tan(val); }},
    {"atan",[](double val) {return atan(val); }},
    {"sqrt",[](double val) {return sqrt(val); }},
    {"log", [](double val) {return log(val); }},
};


//解析符号
static std::tuple<std::string, std::string> parseSymbo(std::string input)
{
    std::string symbo;
    while (1)
    {
        //输入字符串到头了,退出死循环
        if (input.empty())
        {
            break;
        }
        //得到第一个字符
        char ch = input.front();
        if (isalpha(ch))
        {
            symbo.push_back(ch);
            input.erase(input.begin());
        }
        else
        {
            break;
        }
    }
    return { symbo,input };
}

//用static表示此函数只在该.cpp文件中有意义
static std::tuple<double, std::string> parseNumber(std::string input)
{
    std::string numstr;
    //表示第一次遇到小数点
    //避免1.5.5这种情况
    bool firstDot = true;

    while (1)
    {
        if (input.empty())
        {
            break;
        }
        char ch = input.front();

        if ((ch >= '0' && ch <= '9') || (ch == '.' && firstDot))
        {
            numstr.push_back(ch);

            input.erase(input.begin());
            if (ch == '.')
            {
                firstDot = false;
            }

        }
        else
        {
            break;
        }

    }
    //stod用于将字符转化为数字
    return { std::stod(numstr) , input };

}

//查找符号在符号表中的具体定义
std::optional<std::variant<double, std::function<double(double)>>> getSymboValue(std::string symbo)
{
    if (auto iter = SymboTable.find(symbo); iter != SymboTable.end())
    {
        //返回它的具体取值
        return { iter->second };
    }
    return {};
}


std::tuple<Token, std::string> tokenize(std::string input)
{
    Token tk;
    char ch;
    //第一件事情是去除掉开头的空格
    do
    {
        if (input.empty()) //输入为空
        {
            tk.type = TokenType::End;
            return { tk,"" };
        }
        else
        {
            //拿到input最开头的字符
            ch = input.front();//取最前面的字符
            //并且把这个字符从input的开头去掉
            input.erase(input.begin());
        }

    } while (ch == ' ');
    //第二件事情是根据第一个非空格的字符生产相应的token用于返回
    switch (ch)
    {
    case '+':
    case '-':
    case '*':
    case '/':
    case '(':
    case ')':
        tk.type = TokenType(ch);
        break;
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
        tk.type = TokenType::Number;
        //把ch重新添加到input的开头
        //比如开头为1.0
        input.insert(input.begin(), ch);
        //把这个字符串中的数字变成真正的数值
        std::tie(tk.value, input) = parseNumber(input);
        break;
    default:
        //判断是否是a-z,A-Z的字符
        if (isalpha(ch))
        {
            tk.type = TokenType::Symbo;
            //把ch重新添加到input的开头
            input.insert(input.begin(), ch);
            //解析出这个symbo
            std::tie(tk.value, input) = parseSymbo(input);
        }
        else
        {
            //用异常来表示错误
            throw std::runtime_error("错误: 存在不合法的符号!\n");
        }
        break;
    }
    return { tk,input };
}

token::token(QWidget *parent) : QWidget(parent)
{

}

语法分析

准备工作

添加parser.h和parser.cpp文件,步骤与前面样式添加类似。
右击Calculator_Design -> 点击Add New -> 选择C++里的C++ Class -> Class name 定为 parser -> Base class 定为QWidget -> 点击下一步 -> 点击完成
工程建立完成后,开始编写代码

代码添加

parser.h
#ifndef PARSER_H
#define PARSER_H

#include <QWidget>
#include <string>
#include <tuple>

//解析不是一次性的,而是逐个解析 所以返回还是用tuple来返回多个值
std::tuple<double, std::string> parseExpress(std::string input);

class parser : public QWidget
{
    Q_OBJECT
public:
    explicit parser(QWidget *parent = nullptr);
    bool is_contain_lp = false;

signals:


public slots:
};

#endif // PARSER_H

parser.cpp
#include "parser.h"
#include "token.h"

//解析不是一次性的,而是逐个解析 所以返回还是用tuple来返回多个值
static std::tuple<double, std::string> parseFactor(std::string input)
{
    double result;
    Token tk;
    //解析第一个token
    std::tie(tk, input) = tokenize(input);

    switch (tk.type)
    {
    case TokenType::Number:
        result = std::get<double>(tk.value);
        break;

    case TokenType::Symbo:
    {	//先搜索符号是否在符号表中有定义
        auto search = getSymboValue(std::get<std::string>(tk.value));

        if (search)
        {
            //有两种情况
            //情况1 symbo是常量,此时result等于symbo对应的常量具体数值
            if (std::holds_alternative<double>(search.value()))
            {
                result = std::get<double>(search.value());
            }
            //情况2 symbo是函数,此时继续解析(E)并把解析出来的E作用于symbo对应的函数
            //函数计算出来的值赋给result
            else
            {
                //得到函数本体
                auto fun = std::get<std::function<double(double)>>(search.value());
                //解析一个(
                std::tie(tk, input) = tokenize(input);
                if (tk.type != TokenType::Lp)
                {
                    throw std::runtime_error("语法错误:表达式缺'('\n");
                }
                //解析表达式
                double v;
                std::tie(v, input) = parseExpress(input);
                //解析)
                std::tie(tk, input) = tokenize(input);
                if (tk.type != TokenType::Rp)
                {
                    throw std::runtime_error("语法错误:表达式缺')'!\n");
                }
                result = fun(v);

            }
        }
        //找不到符号抛出异常
        else
        {
            throw std::runtime_error("语法错误: 存在不合法的符号 "+ std::get<std::string>(tk.value)+"\n");
        }

        break;
    }
    case TokenType::Lp:
        //解析(E)里的E
        std::tie(result, input) = parseExpress(input);
        //再解析一个)
        std::tie(tk, input) = tokenize(input);
        //如果解析出来的不是)则说明输入的这个表达式有错
        if (tk.type != TokenType::Rp)
        {
            throw std::runtime_error("语法错误:表达式缺')'!\n");
        }
        break;
    default:
        //语法出错,应抛出异常
        throw std::runtime_error("语法错误: 表达式缺数字或者'('!\n");
        break;
    }
    return { result,input };
}
//解析不是一次性的,而是逐个解析 所以返回还是用tuple来返回多个值
static std::tuple<double, std::string> parseTerm(std::string input)
{
    double result;
    //翻译T -> FR中的F
    std::tie(result, input) = parseFactor(input);

    //翻译T -> FR中的R
    bool loop = true; //用此变量用于跳出循环
    while (loop)
    {
        Token op;
        std::string res;
        double term;
        //翻译R -> *TR | /TR | null 的第一个token,那就是 + 或者 - 或者是空
        std::tie(op, res) = tokenize(input);

        switch (op.type)
        {
        case TokenType::Mul:
            //解析*TR中的T
            std::tie(term, input) = parseFactor(res);
            //根据乘法的语义更新result的数值
            result *= term;
            break;
        case TokenType::Div:
            //解析/TR中的T
            std::tie(term, input) = parseFactor(res);
            //不可以除以0
            if (term == 0.0)
            {
                throw std::runtime_error("错误: 除数不能为0!\n");
            }
            //根据除法的语义更新result的数值
            result /= term;
            break;
        default:
            //退出循环
            loop = false;
            break;
        }
    }

    return { result,input };
}

//解析不是一次性的,而是逐个解析 所以返回还是用tuple来返回多个值
//E -> TR
//R -> +TR | -TR | null
 std::tuple<double, std::string> parseExpress(std::string input)
{
    double result;
    //翻译E -> TR
    std::tie(result, input) = parseTerm(input);

    //翻译E -> TR中的R
    bool loop = true; //用此变量用于跳出循环
    while (loop)
    {
        Token op;
        std::string res;
        double term;
        //翻译R -> +TR | -TR | null 的第一个token,那就是 + 或者 - 或者是空
        std::tie(op, res) = tokenize(input);

        switch (op.type)
        {
        case TokenType::Plus:
            //解析+TR中的T
            std::tie(term,input) = parseTerm(res);
            //根据加法的语义更新result的数值
            result += term;
            break;
        case TokenType::Minus:
            //解析-TR中的T
            std::tie(term, input) = parseTerm(res);
            //根据减法的语义更新result的数值
            result -= term;
            break;
        default:
            //退出循环
            loop = false;
            break;
        }
    }

    return { result,input };
}
parser::parser(QWidget *parent) : QWidget(parent)
{

}

不带界面的测试

main.cpp

#include "calculator_ui.h"

#include "myhelper.h"
#include <QApplication>
#include "parser.h"
#include <stdio.h>
#include <QDebug>


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //std::string input = "1.23 + 2 * (1 + 2 / 5 - 4)";
    std::string input = "1.23 + 2 * (1 + 2 / 5 - 4)";
    //std::string input = "1+tan(PI/4)";
    //std::string input = "1+sin(PI/6)*sqrt(32*sin(PI/6))";
    //auto[result,res] = parseExpress(input);
    try
    {
        auto[result, res] = parseExpress(input);
        qDebug() << result;
    }
    catch (std::exception& e)
    {
        qDebug() << e.what();
    }
    myhelper::SetUTF8Code();
   myhelper::SetStyle("blue");//蓝色风格

   myhelper::SetChinese();

    Calculator_UI w;
    w.show();
    return a.exec();
}

测试结果如下:在这里插入图片描述

实现界面计算功能

准备工作

双击calculator_ui.ui -> 右击每个按钮-> 点击转到槽 -> 点击ok

代码添加

calculator_ui.h
#ifndef CALCULATOR_UI_H
#define CALCULATOR_UI_H

#include <QWidget>
#include <QApplication>

QT_BEGIN_NAMESPACE
namespace Ui { class Calculator_UI; }
QT_END_NAMESPACE

class Calculator_UI : public QWidget
{
    Q_OBJECT

public:
    Calculator_UI(QWidget *parent = nullptr);
    ~Calculator_UI();
    void InitStyle_Calculator();

protected:
    bool eventFilter(QWidget *obj, QEvent *event);
    void mouseMoveEvent(QMouseEvent *e);
    void mousePressEvent(QMouseEvent *e);
    void mouseReleaseEvent(QMouseEvent *);

private slots:
    void on_button_close_clicked();

    void on_button_max_clicked();

    void on_button_min_clicked();

    void on_button_0_clicked();

    void on_button_1_clicked();

    void on_button_2_clicked();

    void on_button_3_clicked();

    void on_button_4_clicked();

    void on_button_5_clicked();

    void on_button_6_clicked();

    void on_button_7_clicked();

    void on_button_8_clicked();

    void on_button_9_clicked();

    void on_button_dot_clicked();

    void on_button_clear_clicked();

    void on_button_back_clicked();

    void on_button_Plus_clicked();

    void on_button_Minus_clicked();

    void on_button_Mul_clicked();

    void on_button_Div_clicked();

    void on_button_Lp_clicked();

    void on_button_Rp_clicked();

    void on_button_euqal_clicked();

    void on_button_sin_clicked();

    void on_button_asin_clicked();

    void on_button_cos_clicked();

    void on_button_acos_clicked();

    void on_button_tan_clicked();

    void on_button_atan_clicked();

    void on_button_sqrt_clicked();

    void on_button_log_clicked();

    void on_button_PI_clicked();

    void on_button_e_clicked();

private:
    Ui::Calculator_UI *ui;
    QPoint mousePoint;
    bool mousePressed;
    bool max;
    QRect location;

    //初始化输出框显示0
    QString my_Display = "0";

    //判断是否第一次输入,若是则为true
    bool is_FirstInput = true;

    //判断是否出错,若出错则为true,否则为false
    bool is_Error = false;

    //判断是否有运算符,若有则为true,若没有则为false
    bool is_Sym = false;

    //判断"("左边是数字还是+-*/(,若是数字则为true,否则为false
    bool lp_left_is_num = false;
};
#endif // CALCULATOR_UI_H
calculator_ui.cpp
#include "calculator_ui.h"
#include "ui_calculator_ui.h"

#include "myhelper.h"
#include "icon_style.h"
#include <string>
#include <stdexcept>
#include "parser.h"

#include <QVariantList>
//设置文本框的显示方式
#include <QCompleter>
#include <QStringList>

#include <QPushButton>
#include <QLineEdit>

//QT正则表达式引擎
#include <QRegExp>


Calculator_UI::Calculator_UI(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Calculator_UI)
{
    ui->setupUi(this);

    myhelper::FormInCenter(this);
    this->InitStyle_Calculator();
    ui->lineEdit->setText(my_Display);

}

Calculator_UI::~Calculator_UI()
{
    delete ui;
}

void Calculator_UI::InitStyle_Calculator()
{
    //设置窗体标题栏隐藏
    this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint);
    location = this->geometry();
    max = false;
    mousePressed = false;

    //安装事件监听器,让标题栏识别鼠标双击
    ui->label_title->installEventFilter(this);

    icon_style::Instance()->SetIcon(ui->button_close, QChar(0xf00d), 10);
    icon_style::Instance()->SetIcon(ui->button_max, QChar(0xf096), 10);
    icon_style::Instance()->SetIcon(ui->button_min, QChar(0xf068), 10);
    icon_style::Instance()->SetIcon(ui->btnMenu, QChar(0xf0c9), 10);
    icon_style::Instance()->SetIcon(ui->lab_Ico, QChar(0xf015), 12);
}

bool Calculator_UI::eventFilter(QWidget*obj, QEvent *event)
{
    if (event->type() == QEvent::MouseButtonDblClick) {
        this->on_button_max_clicked();
        return true;
    }
    return QObject::eventFilter(obj, event);
}
void Calculator_UI::mouseMoveEvent(QMouseEvent *e)
{
    if (mousePressed && (e->buttons() && Qt::LeftButton) && !max)
    {
        this->move(e->globalPos() - mousePoint);
        e->accept();
    }
}

void Calculator_UI::mousePressEvent(QMouseEvent *e)
{
    if (e->button() == Qt::LeftButton) {
        mousePressed = true;
        mousePoint = e->globalPos() - this->pos();
        e->accept();
    }
}

void Calculator_UI::mouseReleaseEvent(QMouseEvent *)
{
    mousePressed = false;
}


void Calculator_UI::on_button_max_clicked()
{
    if (max) {
        this->setGeometry(location);
        icon_style::Instance()->SetIcon(ui->button_max, QChar(0xf096), 10);
        ui->button_max->setToolTip("最大化");
    } else {
        location = this->geometry();
        this->setGeometry(qApp->desktop()->availableGeometry());
        icon_style::Instance()->SetIcon(ui->button_max, QChar(0xf079), 10);
        ui->button_max->setToolTip("还原");
    }
    max = !max;
}

void Calculator_UI::on_button_min_clicked()
{
    this->showMinimized();
}

void Calculator_UI::on_button_close_clicked()
{
    qApp->exit();
}



void Calculator_UI::on_button_0_clicked()
{
    //将文本框的内容赋给my_Display
    my_Display = ui->lineEdit->text();
    //获取按下按钮的信息
    QString text = ui->button_0->text();
    if(my_Display!="0")   //不是第一个0(不是只有一个0)
    {
        //找到最后一个+-*/()的位置
        auto index = my_Display.lastIndexOf(QRegExp("[+-*/()]"));
        if(index!=-1)
        {
            //找到最后一个+-*/()后的字符串
            auto res = my_Display.right(my_Display.length()-index-1);
            if(res != "0")
            {
                my_Display += "0";
                ui->lineEdit->setText(my_Display);

            }
        }
        else
        {
            my_Display += "0";
            ui->lineEdit->setText(my_Display);
        }
    }


    bool is_Sym = my_Display.contains(QRegExp("[+-*/]"));
    //用于显示最初0.0000.....number
    bool is_Number = my_Display.contains(QRegExp("[123456789]"));
    if(!is_Sym && is_Number)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_1_clicked()
{
    //这里将文本框的内容赋给my_Display是为了兼顾键盘输入
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_1->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_2_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_2->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}


void Calculator_UI::on_button_3_clicked()
{
    my_Display = ui->lineEdit->text();
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_3->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_4_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_4->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_5_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_5->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_6_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_6->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_7_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_7->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_8_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_8->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_9_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_9->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_dot_clicked()
{
    my_Display = ui->lineEdit->text();
    //先找到文本最后一个'.'
    auto index = my_Display.lastIndexOf('.');
    if(index == -1)  //前面不存在小数点
    {
        my_Display += '.';
        ui->lineEdit->setText(my_Display);
        is_Sym = true;
        is_FirstInput = false;
        is_Error = false;
    }
    else
    {
        //把最后一个小数点之后的字符串取出来
        auto res = my_Display.right(my_Display.length()- index - 1);
        //判断这个res中是否存在运算符() + - * / (正则表达式)
        //rbegin的r的意思是reverse
        if(res.contains(QRegExp("[()+-*/]")) && res.rbegin()->isNumber())
        {
            my_Display += '.';
            ui->lineEdit->setText(my_Display);
        }
    }

}

//清空
void Calculator_UI::on_button_clear_clicked()
{
    my_Display = ui->lineEdit->text();
    my_Display = "0";
    is_FirstInput = true;
    ui->lineEdit->setText(my_Display);
}

//退格
void Calculator_UI::on_button_back_clicked()
{
    my_Display = ui->lineEdit->text();
    if(my_Display != "")
    {
        my_Display.remove(my_Display.length()-1,1);
        ui->lineEdit->setText(my_Display);

    }
    if(my_Display == "")
    {
        my_Display = "0";
        ui->lineEdit->setText(my_Display);
        is_FirstInput = true;
    }
}

void Calculator_UI::on_button_Plus_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_Plus->text();

    //在现有文本基础上添加新按钮
    my_Display += text;
    //为了让计算后可以接着计算
    is_Sym = my_Display.contains(QRegExp("[+-*/]"));

    //设置回去
    ui->lineEdit->setText(my_Display);

}

void Calculator_UI::on_button_Minus_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_Minus->text();

    //在现有文本基础上添加新按钮
    my_Display += text;

    is_Sym = my_Display.contains(QRegExp("[+-*/]"));
    //设置回去
    ui->lineEdit->setText(my_Display);
}

void Calculator_UI::on_button_Mul_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_Mul->text();

    //在现有文本基础上添加新按钮
    my_Display += text;

    is_Sym = my_Display.contains(QRegExp("[+-*/]"));
    //设置回去
    ui->lineEdit->setText(my_Display);
}

void Calculator_UI::on_button_Div_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_Div->text();

    //在现有文本基础上添加新按钮
    my_Display += text;

    is_Sym = my_Display.contains(QRegExp("[+-*/]"));
    //设置回去
    ui->lineEdit->setText(my_Display);
}

void Calculator_UI::on_button_Lp_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_Lp->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮的值
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
    //找到最后一个"("的位置
    auto index = my_Display.lastIndexOf("(");

    //找到最后一个"("前面一个字符和它本身
    auto res = my_Display.right(my_Display.length()-index+1);
    res = res.left(res.length()-1);
    bool lp_left_is_sym = res.contains(QRegExp("[+-*/(gnst]")); 
   //g,n,s,t分别表示log,tan,atan...最后一个字符
    if(!lp_left_is_sym && index!=0)
    {
        lp_left_is_num = true;
    }
}

void Calculator_UI::on_button_Rp_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_Rp->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮的值
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }

}

//计算
void Calculator_UI::on_button_euqal_clicked()
{
    my_Display = ui->lineEdit->text();
    //统计'('的个数
    int count_lp=0;
    //统计')'的个数
    int count_rp=0;
    for(int i=0;i<my_Display.length();i++)
    {
        if(my_Display[i] == '(')
        {
            count_lp++;
        }
        if(my_Display[i] == ')')
        {
            count_rp++;
        }

    }
    try
    {
        auto[result, res] = parseExpress(my_Display.toLatin1().data());
        my_Display = QString::number(result);
        is_Sym = false;
        //设置回去
        ui->lineEdit->setText(my_Display);
        if(lp_left_is_num == true)
        {
            is_Error = true;
            lp_left_is_num = false;
            throw std::runtime_error("错误: 符号'('左边缺失运算符!\n");
        }
        if(count_lp != count_rp)
        {
            throw std::runtime_error("错误: 符号 '(' 与 ')' 个数不匹配!\n");
        }
    }
    catch (std::exception& e)
    {
        my_Display = e.what();
        is_Error = true;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}



void Calculator_UI::on_button_sin_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_sin->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_asin_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_asin->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_cos_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_cos->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_acos_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_acos->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_tan_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_tan->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_atan_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_atan->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_sqrt_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_sqrt->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_log_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_log->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_PI_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_PI->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

void Calculator_UI::on_button_e_clicked()
{
    my_Display = ui->lineEdit->text();
    //获取文本信息
    QString text = ui->button_e->text();

    if(is_FirstInput || is_Error || !is_Sym)
    {
        my_Display = text;
        ui->lineEdit->setText(my_Display);
        is_FirstInput = false;
        is_Error = false;
        is_Sym = true;
    }
    else
    {
        //在现有文本基础上添加新按钮
        my_Display += text;
        //设置回去
        ui->lineEdit->setText(my_Display);
    }
}

带界面的测试

正确计算测试

测试1

在这里插入图片描述
在这里插入图片描述

测试2

在这里插入图片描述
在这里插入图片描述

测试3

在这里插入图片描述
在这里插入图片描述

错误计算测试

测试1

在这里插入图片描述
在这里插入图片描述

测试2

在这里插入图片描述
在这里插入图片描述

测试3

在这里插入图片描述
在这里插入图片描述

测试4

在这里插入图片描述

在这里插入图片描述

完整工程下载

简易计算器

猜你喜欢

转载自blog.csdn.net/qq_46068864/article/details/112309747