C语言快要结课了,自然少不了大作业,在众多选项中看中了简易计算器(其实每一项都研究了,发现自己太菜只能选计算器)。
虽然是C语言大作业,但代码是C++,作业要求推荐图形设计使用MFC,在B站看了几个教程后感觉实在接受不了(本来时间就少,MFC学起来太花时间了,而且不支持跨平台),最后看上了QT(太香了)。QT入门是看的吴健老师的教程。简单了解后就开始编写代码了,教程不知道什么原因,后面都缺失了,不过前面的基础篇也够用了,不会的就查手册,或者上网查询,没必要都记下来,也记不下来,边写边查最好了。
计算器的主要实现就是通过槽函数获取按钮信息,用QString储存表达式,经过检查和运算后将结果显示再labl上,表达式的运算利用逆波兰表达式(后缀表达式)实现,不知道该算法的指路C++栈的应用——后缀表达式求值、中缀表达式到后缀表达式的转换,本人也是在此博客基础上修改实现(其实跟没改一样)。计算器的核心也是这个算法和检查机制(之前并没有加检查机制,后面自己找BUG,出现一个改一个)。
首先是dialog,因为计算器只有一个界面,所有没有使用mianwindow
主要是链接模型代码
#include "dialog.h"
#include "ui_dialog.h"
#include "doexpr.h"
#include "check.h"
#include
#include
const double PI = 3.14159265358;
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::Dialog) //构造函数
{
ui->setupUi(this);
this->setWindowFlags(Qt::CustomizeWindowHint|Qt::WindowCloseButtonHint); //去除窗口右上角“?”
this->player->setMedia(QUrl("music/background.mp3"));
this->player->setVolume(50);
this->temp = "";
this->doExpr = false;
this->loopExpr = false;
}
Dialog::~Dialog() //析构函数
{
delete ui;
}
void Dialog::on_btn_0_clicked()
{
//按钮0槽函数
if(this->temp != "" && this->temp != "0") //防止出现多位0
{
this->temp += this->ui->btn_0->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp); //判断是否已经进行过运算
else
{
this->temp = ""; //若已进行运算,将文本置空
this->temp += this->ui->btn_0->text();
this->ui->lbl_display->setText(this->temp); //显示文本
this->loopExpr = false;
}
}
else //若未输入其他按钮,只显示一个0
{
this->temp = "0";
this->ui->lbl_display->setText(this->temp);
}
}
void Dialog::on_btn_1_clicked()
{
//按钮1槽函数
this->temp += this->ui->btn_1->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += this->ui->btn_1->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_2_clicked()
{
//按钮2槽函数
this->temp += this->ui->btn_2->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += this->ui->btn_2->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_3_clicked()
{
//按钮3槽函数
this->temp += this->ui->btn_3->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += this->ui->btn_3->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_4_clicked()
{
//按钮4槽函数
this->temp += this->ui->btn_4->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += this->ui->btn_4->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_5_clicked()
{
//按钮5槽函数
this->temp += this->ui->btn_5->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += this->ui->btn_5->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_6_clicked()
{
//按钮6槽函数
this->temp += this->ui->btn_6->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += this->ui->btn_6->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_7_clicked()
{
//按钮7槽函数
this->temp += this->ui->btn_7->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += this->ui->btn_7->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_8_clicked()
{
//按钮8槽函数
this->temp += this->ui->btn_8->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += this->ui->btn_8->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_9_clicked()
{
//按钮9槽函数
this->temp += this->ui->btn_9->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += this->ui->btn_9->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_pi_clicked()
{
//按钮pi槽函数
this->temp += QString::number(PI,10,7);
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += QString::number(PI,10,7);
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_point_clicked()
{
//按钮 "."槽函数
if(this->temp == "") //若直接按point,则加上前导0
this->temp = "0";
this->temp += this->ui->btn_point->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "0";
this->temp += this->ui->btn_point->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_left_clicked()
{
//左括号槽函数
this->temp += this->ui->btn_left->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += this->ui->btn_left->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_right_clicked()
{
//右括号槽函数
this->temp += this->ui->btn_right->text();
if(!this->loopExpr)
this->ui->lbl_display->setText(this->temp);
else
{
this->temp = "";
this->temp += this->ui->btn_right->text();
this->ui->lbl_display->setText(this->temp);
this->loopExpr = false;
}
}
void Dialog::on_btn_div_clicked()
{
//除法槽函数
this->temp += this->ui->btn_div->text();
this->ui->lbl_display->setText(this->temp);
if(this->doExpr)
this->loopExpr = false;
}
void Dialog::on_btn_mul_clicked()
{
//乘法槽函数
this->temp += this->ui->btn_mul->text();
this->ui->lbl_display->setText(this->temp);
if(this->doExpr)
this->loopExpr = false;
}
void Dialog::on_btn_sub_clicked()
{
//减法槽函数
this->temp += this->ui->btn_sub->text();
this->ui->lbl_display->setText(this->temp);
if(this->doExpr)
this->loopExpr = false;
}
void Dialog::on_btn_add_clicked()
{
//加法槽函数
this->temp += this->ui->btn_add->text();
this->ui->lbl_display->setText(this->temp);
if(this->doExpr)
this->loopExpr = false;
}
void Dialog::on_btn_clear_clicked()
{
//CE槽函数
this->temp = "";
this->ui->lbl_display->setText("0");
}
void Dialog::on_btn_backspace_clicked()
{
//退格键槽函数
this->temp.chop(1);
this->ui->lbl_display->setText(this->temp);
}
void Dialog::on_btn_reci_clicked()
{
//求倒数槽函数,与等号相似
if(!check(this->temp)) //判断表达式是否有误
{
QMessageBox::warning(NULL, "错误", "输入表达式有误!o((>ω< ))o");
}
else
{
if(!checkSignal(this->temp)) //判断是否存在正负号表达式
{
//若存在,则改变表达式使其符合算法
//如(-5)-> (0-5)
this->temp = insert_zero(this->temp);
}
if(!check(this->temp)) //再次检修改后查表达式是否正确
{
QMessageBox::warning(NULL, "错误", "输入表达式有误!o((>ω< ))o");
}
else
{
string reci = this->temp.toStdString(); //QString转为string
reci = InfixToPostfi(reci); //将中缀表达式转为后缀表达式
double res_reci = postfixExpression(reci); //计算后缀表达式
res_reci = 1 / res_reci; //取倒数
if(res_reci != (long long)res_reci) //判断是否为整型
this->temp = QString::number(res_reci,10,7); //精度为小数点后7位,可调
else
this->temp = QString::number(res_reci,10,0);
if(this->temp == "inf" ||this->temp == "nan") //判断分母是否为0
{
QMessageBox::warning(NULL, "错误", "注意,分母不能为零哦!(ง •_•)ง");
this->temp = "0";
}
this->ui->lbl_display->setText(this->temp); //将结果显示到display
this->doExpr = true; //记录已经进行运算
this->loopExpr = true;
}
}
}
void Dialog::on_btn_equal_clicked()
{
//等号槽函数,与求倒数相同,缺少求倒数过程
if(!check(this->temp))
{
QMessageBox::warning(NULL, "错误", "注意,表达式输入有误!(@_@;)");
}
else
{
if(!checkSignal(this->temp))
{
this->temp = insert_zero(this->temp);
}
if(!check(this->temp))
{
QMessageBox::warning(NULL, "错误", "注意,表达式输入有误!(@_@;)");
}
else
{
string res = this->temp.toStdString();
res = InfixToPostfi(res);
double res_equal = postfixExpression(res);
if(res_equal != (long long)res_equal)
this->temp = QString::number(res_equal,10,7);
else
this->temp = QString::number(res_equal,10,0);
if(this->temp == "inf" ||this->temp == "nan")
{
QMessageBox::warning(NULL, "错误", "注意,分母不能为零哦!(ง •_•)ง");
this->temp = "0";
}
this->ui->lbl_display->setText(this->temp);
this->doExpr = true;
this->loopExpr = true;
}
}
}
void Dialog::on_btn_musicOn_clicked()
{
//开启音乐槽函数
//音乐结束后再次点击此按钮继续播放
this->player->play();
}
void Dialog::on_btn_musicOff_clicked()
{
//暂停音乐槽函数
this->player->pause();
}
主要是逆波兰算法,先将中缀表达式转换成后缀表达式,然后计算
用到了STL中的栈和向量
#include "doexpr.h"
#include
#include
#include
using namespace std;
/*********************后缀表达式求值(直接利用C++STL提供的Stack实现)**************************/
double postfixExpression(const string &str)
{
stack<double> mystack; //栈空间
string s = ".0123456789+-*/";
string empty = " ";
string numbers = ".0123456789";
string c = "+-*/";
double firstnum; //左侧操作数
double secondnum; //右侧操作数
double result; //结果
for(size_t i=0; i<str.size(); )
{
size_t start = str.find_first_of(s,i); //查找第一个数字或算术符号
size_t end = str.find_first_of(empty,i); //查找第一个空格
string tempstr = str.substr(start, end-start); //取出这一个元素
//判断元素
if(tempstr == "+" || tempstr == "-" || tempstr == "*" || tempstr == "/")
{
secondnum = mystack.top(); //取当前栈顶元素,由于栈的先进后出特性,当前栈顶元素其实是二元操作符中右侧的操作数,
mystack.pop(); //如表达式3-2的后缀表达式为“3 2 -”,这里secondnum取得数就是2
firstnum = mystack.top();
mystack.pop();
if(tempstr == "+") //做运算,并将结果压入栈中
{
result = firstnum + secondnum;
mystack.push(result);
}
if(tempstr == "-")
{
result = firstnum - secondnum;
mystack.push(result);
}
if(tempstr == "*")
{
result = firstnum * secondnum;
mystack.push(result);
}
if(tempstr == "/")
{
result = firstnum / secondnum;
mystack.push(result);
}
}
else //若不是运算符,将元素转为double后压入栈中
{
double num = stod(tempstr);
mystack.push(num);
}
//控制迭代,从空格后开始
i = end + 1;
}
return mystack.top(); //返回最后结果
}
//设置操作符优先级,这里考虑到括号("("、")")匹配,定义设置左括号"("的优先级最高,且只有在遇到右括号时才弹出左括号
int priority(const string str)
{
const char *op = str.c_str();//生成C字符串数组
switch(*op) //设置优先级
{
case ')':
return 0;
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
case '(':
return 3;
default :
return -1;
}
}
/*********************中缀表达式转为后缀表达式**************************/
string InfixToPostfi(const string &str)
{
string operatorstr = "*-/+()"; //用于string搜索
string numbers = "0123456789.";
//对输入的中缀表达式中每个元素进行切片,每个元素存储到vectorInputvec
vector<string> Inputvec; //用向量存储切片结果
for(size_t i=0; i<str.size(); )
{
size_t operatorindex = str.find_first_of(operatorstr,i); //搜索str中从i开始的第一个操作符
if(operatorindex != string::npos)
{
//如果从i开始搜索到了操作符
if(operatorindex == i)
{
Inputvec.push_back(str.substr(operatorindex,1)); //substr参数:起始字符序号,剪切个数
i = i+1;
}
else
{
//将操作数和操作符都加入向量中
Inputvec.push_back(str.substr(i,operatorindex-i));
Inputvec.push_back(str.substr(operatorindex,1));
i = operatorindex+1;
}
}
else
{
//如果从i开始搜索到了操作符,即输入的中缀表达式以操作数结尾,不是以操作符结尾
//(其实一个表达式以操作符结尾的情况只可能是以右括号")"结尾,这里就是为防止这种特殊情况)
Inputvec.push_back(str.substr(i,str.size()-i));
i = str.size();
}
}
//遍历切片结果vector中每个元素
stack<string> operatorstack; //创建空栈,用来存储操作符
vector<string> PostfiOutvec; //存储中缀输出,这里是存储到vector
for(size_t i=0; i<Inputvec.size(); i++)
{
//如果当前元素是操作符
if(Inputvec[i].find_first_of(operatorstr) != string::npos)
{
if(operatorstack.empty())
{
operatorstack.push(Inputvec[i]); //如果操作符栈空,则直接入栈
}
else
{
if(Inputvec[i] == ")") //如果当前操作符是右括号
{
while(operatorstack.top() != "(")
{
PostfiOutvec.push_back(operatorstack.top()); //将栈顶操作符输出
operatorstack.pop(); //删除栈顶元素
}
operatorstack.pop(); //删除栈顶元素(这里是删除左括号"(")
}
else
{
int curpri = priority(Inputvec[i]); //获取操作符的优先级
//比较当前操作符与栈顶元素优先级,如果小于或等于栈顶元素优先级则弹出栈顶元素,否则当前操作符入栈
while(!operatorstack.empty())
{
string top = operatorstack.top(); //返回栈顶元素
int toppor = priority(top); //栈顶元素优先级
if((curpri <= toppor) && top!="(") //左括号优先级最大,但是它只有遇到右括号才输出
{
PostfiOutvec.push_back(top);
operatorstack.pop(); //删除栈顶元素
}
else
break;
}
operatorstack.push(Inputvec[i]);
}
}
}
//如果当前元素是操作数,直接输出
else
{
PostfiOutvec.push_back(Inputvec[i]);
}
}
while(!operatorstack.empty())
{
PostfiOutvec.push_back(operatorstack.top()); //输出操作符栈中的其他操作符
operatorstack.pop();
}
//在输出中插入空格
vector<string>::const_iterator itr=PostfiOutvec.begin()+1;
while(itr!=PostfiOutvec.end())
{
itr = PostfiOutvec.insert(itr," "); //这里一定要返回insert之后的指针,因为改变容器的操作会使迭代器失效
itr+=2;
}
PostfiOutvec.push_back(" "); //添加最后一个空格
//vector输出为string,作为后缀表达式结果返回
string result;
for(size_t i=0; i<PostfiOutvec.size(); i++)
{
result += PostfiOutvec[i];
}
return result;
}
看注释应该可以看懂,每一项检查都是由可能遇到的错误表达式得来,可能还有部分检查没有遇到,如有BUG请自行改写,也欢迎告诉我(我也不想自己的程序有BUG)。
#include "check.h"
using namespace std;
bool flag(char temp) //判断字符是否为运算符
{
if(temp == '+'||temp == '-'||temp == '*'||temp=='/'||temp == '.')
return true;
else
return false;
}
bool check(QString labl_temp) //表达式检查函数
{
string temp = labl_temp.toStdString();
if(temp == "")
return false;
/****************括号检查***********************/
int leftCount = 0; //记录括号个数
int rightCount = 0;
int leftFlag = 0;
for(size_t i = 0; i < temp.size();i++ )
{
if(temp[i]=='(')
{
if(temp[i+1] == ')') //检查"()"情况
return false;
if(i>0&&!flag(temp[i-1])) //检查除首字符左括号前无运算符
return false;
leftCount++;
leftFlag++;
}
if(temp[i] == ')') //出现右括号,左括号与之匹配,未匹配左括号数减一
leftFlag--;
if(leftFlag < 0) //先出现右括号
return false;
}
for(size_t i = 0; i < temp.size();i++ )
{
if(temp[i]==')')
rightCount++;
}
if(leftCount != rightCount) //检查左右括号数是否匹配
return false;
/*****************运算符及小数点检查**********************/
for(size_t i = 0;i<temp.size() - 1;i++)
{
if(flag(temp[i]) && flag(temp[i+1])) //检查连续运算符错误表达式
return false;
if(temp[i] == '(' && (temp[i+1] == '*' || temp[i+1] == '/')) //检查 “(*” 和“(/”错误表达式
return false;
if(flag(temp[i]) && temp[i+1] == ')') //检查类似 “+)”错误表达式
return false;
}
if(temp[0] == '*' || temp[0] == '/' ||flag(temp[temp.size() - 1])) //检查首和尾部尾运算符
{
return false;
}
return true;
}
/**************正负号检查**********/
bool checkSignal(QString labl_temp)
{
string temp = labl_temp.toStdString();
for(size_t i = 0; i<temp.size() - 1;i++)
{
//检查首字符为正负号或左括号后为正负号
if((temp[0] == '-'||temp[0] == '+')||(temp[i] == '(' && (temp[i+1] == '-' || temp[i+1] == '+')))
return false;
}
return true;
}
/************插入函数(使算法支持带正负号表达式)*********/
QString insert_zero(QString temp)
{
QString str = temp;
if(temp[0] == '-') //首字符为负号,加上前导0
{
str = "0";
str += temp;
}
else if(temp[0] == '+') //首字符为正号,去除正号
{
str = temp;
str = str.right(str.size() - 1);
}
for(int i = 0;i < str.size() - 1;i++) //遍历文本
{
QString str2 = str;
if(str[i] == '(' && str[i+1] == '-')
{
//左括号后为负号,加上前导0
str2 = str.mid(0,i + 1);
str2 += "0";
str2 += str.mid(i+1,str.size()-(i+1));
}
else if(str[i] == '(' && str[i+1] == '+')
{
//左括号后为正号,去除正号
str2 = str.mid(0,i + 1);
str2 += str.mid(i+2,str.size()-(i+2));
}
str = str2;
}
return str; //返回修改后表达式
}
至于主函数没啥要讲的,默认就行。
首次使用QT开发,很方便,自己也需要学习更多,比较粗糙的一个程序,不是很会美化。C++确实难,不过计算器用到的类还比较简单,大多数代码是C风格。
源码链接:https://download.csdn.net/download/What_ever_Y/12382221
有大佬有吴健老师后面部分的视频资源请一定帮帮孩子,孩子很需要(孩子快哭了)。