目的:锻炼我们对Qt的掌握和应用,锻炼实际解决问题,完成项目的能力。
要求:在课后作业03,05的基础上,增加图形交互功能,增加三个逻辑运算符&&,||,!,并能处理逻辑运算符和算术运算符的混合运算;增加容错功能,能进行异常处理。
软件:Qt 5
硬件: Legion
设计并实现计算器
用文字、图(流程图等)、表格等方式记录实验过程中分析、设计工作。
此次需求在于基于课后作业扩充。主要难点在于图形交互界面的建立,需要学习QT,并且将原本的代码转化为Qt可以识别的代码。并且在算术运算的基础上增加逻辑运算,并支持混合运算。
在对Qt一段时间的学习之后,我选择使用ui来进行图形界面的设计简化过程。在设计好计算器的大概视图后,需要更改每个按钮的名称以便于后期的实现。
之后在代码的编写上,为了增加美观性与实用性,我选择添加背景图片和显示实时时间。之后需要通过connect语句将按钮信号和对应的槽函数相连接,以便于可以通过按按钮来进行处理(这一步非常重要)。
之后对槽函数的设计上,选择每输入一个,就在textEdit上显示一个,并且将它储存到事先准备好的string里去。在进行计算的时候,代码类似于第三、五次作业内容,故不再赘述。需要注意的是,在按下等于号时,需要将计算的结果显示到textEdit上。
本人最开始设计的是ui,建立的按钮效果如下
现在我逐一讲解过程:
1、pushButton和textEdit的建立
创建完项目之后,首先看到的肯定是
(Resource和Other files是后加的,刚创建并没有,后面会讲到)
那么点开forms,就可以进入ui界面。之后拖拽左框的pushbutton按钮和textEdit按钮,然后自行排版(pushbutton是后期自己按按钮用的,textEdit是显示用的,就留一个textEdit)
这里提一句,在排版时,除了可以自行调节按钮位置外,更可以选中你所需要排列的按钮,通过上方的
这两个按钮进行水平或者垂直布局。
此时你将会获得一个已经排版好的计算器样式,双击pushbutton按钮可以更改名称,可以将pushbutton改为‘(’、‘=’等任意符号,方便之后计算器运行。
2、对pushbutton和textEdit的修改
在大概的界面设计结束后,接下来的步骤是对pushbutton和textEdit属性的修改。点开ui界面,任意点击一个按钮,会在右下角看到这样的界面
其中,objectName就是指按钮信号的名称,因为在后期需要设置相应的槽函数和信号连接,所以信号必须有一个容易区分的名字(默认都是pushbutton_i。i=1,2…n)。例如,可以点击‘+’,将它的名字改为pushButton_plus或者pushButton_jia,怎么改都行,只要自己可以理解。此外,顺便提一句,因为pushbutton都是有默认大小的,如果不满意,可以在geometry的宽度高度进行修改。
既然改了,就改到底!我在设计时嫌默认按钮太小,就将按钮放大,但是放大后却感到字体好小啊…所以,就要将字体调大,那干脆字体格式也改了吧。咋改呢?回到myWidget.cpp文件中(我构造的类名称叫myWidget),在这个
Ui->setupUi(this)下面接着写这样几句话。
ui->pushButton_0->setFont(QFont("宋体", 18));
//此处,pushbutton_0是我定义按钮0的名称,那么我设置的就是宋体18号的大小
//但是为什么同样的代码,你写会报错呢?
//包含Qfont头文件了嘛…没有就去包含吧,就不会报错了。
//同理
QFont textEditFont ( "Microsoft YaHei", 25, 75);
ui->textEdit->setFont(textEditFont);
这两行代码,就把输入输出时显示在textEdit框里数字的大小和格式修改了。
3、myWidget.h的编写
点开myWidget.h文件,映入眼帘的是public、private slots、private几个单词,在private下还有
Ui::myWidget *ui;
一行代码对不对?
好的,接下来需要做的是,在public 中写几个函数
我写了这几个函数,is_num的作用是判断字符是不是数字,Cal的作用是进行局部运算(了解计算器机制的都清楚,储存在栈中,按优先级进行入栈或者局部运算操作),prior作用是判断运算符优先级,Do就是最核心的部分,将表达式字符串进行计算,return出结果。这些函数因人而异,每个人写的函数,写的方式都不同,但因为我写这篇博客在于我已经完成了前一次在vs中编写的过程,并且我此篇博客是面向的合肥工业大学同学们的大作业,所以对于这些函数的介绍,我只会在篇末给出大概思路。在后期修改中,我又加了一个
void on_actionRight_triggered();
用来将textEdit显示的数字进行右对齐(因为我看到计算器都是这样的,所以这一步很可以省略)
在private slots中,我们需要编写槽函数,来和按钮匹配
我定义了这么多槽函数,前面那么多个,可以看出分别匹配了我ui中的按钮。那么后面四个五个槽函数呢?分别是四个报错函数和一个显示时间的函数,可以根据需要添加。最后,在private中,我添加了一个Qstring strin,即建立了一个Qstring类的对象叫strin。到这里,就结束了myWidget.h的编写。
4、myWidget.cpp的编写
这是一个非常浩大的工程。第一步,我想将窗口的名称改了,毕竟我要证明是我做的,可以使用setWindowTitle(“我的窗口”),你可以“”中写入任何东西,产生出的窗口就会显示这个名称
接着要做的是,将textEdit上显示的数字设置为不可更改,不然,产生的数字就可以被delete了!那可得了?果断
ui->textEdit->setReadOnly(true);
设置为只可读。然后再来一个
ui->textEdit->setText("0");
用于将初始啥也没按时,设置为0。
第二步,strin=“#”。啥意思呢,strin是我在.h里建立的Qstring对象,它干什么用?就是讲你输入的字符统统储存到里面,便于之后计算,之所以要初始化为“#”,是因为储存在栈中时,需要一个字符与接下来第一个进入的字符做优先级比较,工大同学应该都懂,不再赘述。
第三步,连接槽函数和信号,这一步非常简单,就是将你按按钮的行为和槽函数的调用联系在一起,只需要写一行,然后Ctrl C,Ctrl V粘贴就行。
//绑定按键0与处理函数
connect(ui->pushButton_0,&QPushButton::clicked,[this](){pushButton_0();});
这就将按钮0和槽函数void pushbutton_0联系了。注意,第一个pushbutton_0指的是信号,第二个指的是槽函数,只是我把槽函数设置成和信号同名便于查找而已。下面解释一下connect括号内的内容:ui->pushButton_0 表示信号发送者,&QpushButton::clicked表示行为,也就是当产生click的行为后发送信号,[this]表示信号是传给本身的,随后那个指的是产生的效果,也就是点击产生的槽函数的调用。
第四步,这几行代码
Timer *timer = new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(timerUpdate()));
timer->start(1000);
这是产生时间所需要的三行代码
on_actionRight_triggered();这是对齐代码(可以忽略)
OK!已经完成很多了!起码可以编写其他函数了
第五步,编写 void on_actionRight_triggered();上面说了这个可以根据个人需要进行右对齐,所以我直接放代码了,ui->textEdit->setAlignment(Qt::AlignRight);。左对齐就是AlignLeft…
第六步,timerUpdate(void)的编写,(记得包含#include
QDateTime time = QDateTime::currentDateTime();
QString str = time.toString("yyyy-MM-dd hh:mm:ss dddd");
ui->label->setText(str);
QFont textEditFont ( "Microsoft YaHei", 15, 30);
ui->label->setFont(textEditFont);
第一行,设置当前时间,转化成年月日时分秒形式,底下几行是设置字体大小和样式的。这里注意一点,代码为ui->label ,也就是说你要回到ui界面,在左侧找到label,拖一个出来就行了,时间就会显示在上面。
槽函数的编写!!!
第七步!数字按钮的编写
这里从注意按钮0到9都是差不多的,写一个然后复制粘贴就行,以1为例。
if(strin=="#") ui->textEdit->clear();
bracketdigiterror();
strin+="1";
on_actionRight_triggered();
ui->textEdit->textCursor().insertText("1");
如果strin==“#”,就将textEdit清空,这是啥意思?因为,每进行一次运算,摁下等于号之后textEdit会显示结果,然后要将strin初始化为#便于下一步运算,此时在按任何按钮时,上一步运算结果还停留在textEdit上,很不方便,所以要通过这一步消除。Bracketdigiterror是一个判错的槽函数,暂时不理它。Strin+=“1”指的是将1加入,因为我们要得到一个完整的表达式来带入Do中计算,而strin就是储存表达式的,所以每按一个按钮,都会让表达式多一位,也就需要接入strin中。下面一行,右对齐。最后一行,就是将1显示出来。之后的几个数字按钮与此同理。
第八步,现在进行左括号槽函数编写
void myWidget::pushButton_lkuohao()
{
if(strin=="#") ui->textEdit->clear();
char*s=strin.toLocal8Bit().data();
digitbracketerror();
strin+="(";
on_actionRight_triggered();
ui->textEdit->textCursor().insertText("(");
}
第二行,是定义一个char*类型变量s,将strin转化。为什么要转化?与底下判错函数有关。
第九步,现在进行右括号函数编写
void myWidget::pushButton_rkuohao()
{
if(strin=="#")
ui->textEdit->clear();
if(!bracketerror())//检查左括号,左括号缺失
{
QMessageBox::about(this,"输入错误","请检查一下左括号数量");
//这一步要包含QmessageBox头文件,指的是,如果出现这样的错误, //会弹出弹窗弹窗标题为“输入错误”,内容为“请检查左括号数量”
digitbracketerror();//判错函数
strin+="(";
on_actionRight_triggered();//对齐
ui->textEdit->textCursor().insertText("(");//补齐缺失的左括号
}
Else//正常输入,无错误的情况
{
char*s=strin.toLocal8Bit().data();//转化为char*
if(s[strlen(s)-1]=='(')//如果‘)’前一个是‘(’,证明括号里什么 //也没有,需要提示并补充个1.
{
QMessageBox::about(this,"输入错误","括号里面没有计算或者数字");
strin+="1";
ui->textEdit->textCursor().insertText("1");
}
strin+=")";//一切正常的话,则将‘)’加入strin
on_actionRight_triggered();
ui->textEdit->textCursor().insertText(")");
}
}
代码较长,我将解释都在上面以注释形式传递
第十步,加减乘除槽函数(类似,可Ctrl)
void myWidget::pushButton_jia()
{
strin+="+";
ui->textEdit->textCursor().insertText("+");
on_actionRight_triggered();
operror();
}
第十一步,关闭函数
void myWidget::pushButton_guanbi()
{
close();
}
这一步很好理解,就是在按turn off按钮时,直接退出计算器。
第十二步!(快结束了)
void myWidget::pushButton_C()
{
strin="#";
ui->textEdit->setText("0");
on_actionRight_triggered();
}
这一步是将所有数据都归零的意思,所以直接将strin变为初始的#,并且将textEdit清空,最后显示初始时的0,且右对齐。
第十三步 ,back函数编写
void myWidget::pushButton_jiantou()
{
strin.chop(1);
ui->textEdit->textCursor().deletePreviousChar();
if(strin=="#")ui->textEdit->setText("0");
}
首先,点击这个按钮,就是会删除一个数。删除所需要的首先是将它从strin中取出,就是chop函数,里面的1指的是取出一个。之后则需要把它在textEdit上显示的部分也删除,最后一步是啥?也就是如果本来就一个数的话,删除之后strin就空了,那就需要形成初始状况的0。
第十四步,“&&”和“||”编写
void myWidget::pushButton_and()
{
strin+="&&";
ui->textEdit->textCursor().insertText("&&");
//operror(1);
}
void myWidget::pushButton_huo()
{
strin+="||";
on_actionRight_triggered();
ui->textEdit->textCursor().insertText("||");
// operror(2);
}
对于逻辑运算,应该只有工大老师要求,所以就直接挂代码了
老规矩,先储存在strin中然后进行运算符正确性判断。
第十五步!!:逻辑非运算,有点不一样,单独讲
void myWidget::pushButton_fei()
{
if(strin=="#") ui->textEdit->clear();
char*s=strin.toLocal8Bit().data();
if(isdigit(s[strlen(s)-1])||s[strlen(s)-1]==')')
{
QMessageBox::about(this,"输入有误","逻辑否符号前是数");
strin+="*";
ui->textEdit->textCursor().insertText("*");
}
strin+="!";
on_actionRight_triggered();
ui->textEdit->textCursor().insertText("!");
}
这个需要先转化为char* 类型,之后判断“!”前面是不是数字或者右括号,如果是的话,例如“4!”或者“)!”都是错误的,所以给出弹窗并且给塔补充一个乘号:“4*!”,这样就可以通过在!后面加数字来避免错误。之后的内容同理,不再赘述。
好的!!!
那么四个判错函数到底是啥呢,为啥几乎每个槽函数下面都有?
第十六步,判错槽函数编写
void myWidget::operror(int num)
{
char*s=strin.toLocal8Bit().data();
if(!isdigit(s[strlen(s)-2])&&s[strlen(s)-2]!=')')
{
QMessageBox::about(this,"输入有误","您输入的双目运算符无左值");
strin.chop(1);
for(int i=1;i<=num;i++)
ui->textEdit->textCursor().deletePreviousChar();
}
}
细心的你会发现,我只有在输入运算符时才会调用这个槽函数
我们分析一下,先转化,如果strin中,我输入了一个+号或者任意符号,如果它前面一个符号不是数字也不是右括号,会是什么情形?很明显错误了,所以要爆出一个这样的错误。之后呢就需要把它在strin中弹出来,并且在textEdit进行删除处理。
倒数第3步!!!
int myWidget::bracketerror()
{
int l=0,r=0;
for(int i=0;i
这个函数主要测试括号数量是否对的上。
倒数第二步!!!
void myWidget::digitbracketerror()
{
char*s=strin.toLocal8Bit().data();
if(isdigit(s[strlen(s)-1])||s[strlen(s)-1]==')')
{
QMessageBox::about(this,"输入有误","您要输入的左括号前是数");
strin+="*";
ui->textEdit->textCursor().insertText("*");
}
}
void myWidget::bracketdigiterror()
{
char* s=strin.toLocal8Bit().data();
if(s[strlen(s)-1]==')')
{
QMessageBox::about(this,"输入有误","您要输入的数字前是右括号");
strin+="*";
ui->textEdit->textCursor().insertText("*");
}
}
这些可以照着之前所说的自行理解哦~~~
最后一步!!运行!!
我想现在运行的话,你就可以得到一颗可以运行的计算器啦!!!恭喜你!!!
如果你没有计算器具体编写方法的话,给下面这个链接,可以去看看,之后带着你写好的函数,来运行吧!
https://blog.csdn.net/weixin_45769058/article/details/106944806?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162236564916780261968904%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162236564916780261968904&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-4-106944806.pc_search_result_cache&utm_term=C%2B%2B%E5%AE%9E%E7%8E%B0%E8%AE%A1%E7%AE%97%E5%99%A8&spm=1018.2226.3001.4187
4、添加背景
现在,你已经有了一个属于你的计算器,但是有没有感觉不太好看?这时候,你需要按照以下步骤
(1)挑一张你觉得好看的照片放在一个文件夹里
(2)将文件夹移动到你计算器的文件下
(3)点击左上角 文件->新建文件或项目->
然后就多了个resource
(4)回到ui界面,在左边找到frame,拖一个出来,将它放大到和你的计算器一样大,然后消除里面的文字,之后在右边找到stylesheet
右键点击->添加资源->border-images
(5)之后你就可以找到你添加的图片啦!
Ps:如果没有,试试先编译一下。
5、总结
(1)这次饱含泪水的大作业着实耗费了大量的时间和精力,从啥也不会到逐步探索,经历了很多。思来想去,还是应该写一篇博客,虽然我们的大作业已经过去,但是希望以后的学弟学妹们能少一点辛酸
(2)本人弱鸡一枚,如有错误,请多指正。