应编译原理课程设计要求,做了一个简易的计算器。项目基于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