前言:距离上一次摸QT已经快10年了,时光匆匆,现在已经到6.9版本了
一、安装QT
1.1、下载链接
https://mirrors.tuna.tsinghua.edu.cn/qt/official_releases/online_installers/
这是国内镜像,比官网快很多了,官网那个可以用龟速来形如。
目前已采用在线下载方式,类似visual studio 2022,依稀记得,最开始接触时,需要下载源代码,然后自己适配,编译。
二、计算器
先写个计算器先熟悉下
功能包含:四则运算,含小数点,可进行浮点数计算,包含括号
界面设计:使用QT的UI工具进行布局
大致如图所示,由于控件较多,也比较密集,所以采用网格布局(grid layout),然后为每个控件设置信号与对应的槽<=============>Windows下的win32或者MFC的事件与相关回调函数
当然除了在ui界面一个一个设置相关的槽,还可以直接通过代码来进行设置
//连接信号与槽,对C按钮使用
connect(ui->clear_Button, &QPushButton::clicked, this, [this](){
express.clear();
ui->display_LineEdit->setText(express);
});
//连接信号与槽,对Del按钮使用
connect(ui->del_Button, &QPushButton::clicked, this, [this](){
if(!express.isEmpty()){
express.chop(1);
ui->display_LineEdit->setText(express);
}
});
- 核心功能:实现计算器,采用逆波兰式
-
- 先了解中缀表达式:形如
A+B
运算符在两个操作数中间,所以就是中缀表达式,日常生活中使用的就是这种
- 先了解中缀表达式:形如
-
- 后缀表达式:运算符在操作数后面就是,如
A B +
- 后缀表达式:运算符在操作数后面就是,如
-
- 逆波兰式的核心:就是将中缀表达式转变为后缀表达式,然后对后缀表达式进行计算
-
- 一般要使用两个栈,一个栈存放操作数,另外一个栈存放运算符。
-
- 计算终止条件,运算符栈为空,就弹出操作数栈的栈顶元素
- 计算终止条件,运算符栈为空,就弹出操作数栈的栈顶元素
-
//核心代码如下
// 等号的槽函数
void Widget::on_equval_Button_clicked()
{
if(express.isEmpty()){
express += '0';
ui->display_LineEdit->setText(express);
return;
}
//逆波兰式进行计算
try{
//替换掉表达式中的X
QString str = express;
str.replace('X', '*');
//先将中缀表达式转为后缀表达式
QString post_str = infixToPostFix(str);
//计算后缀表达式的值
double result = calPostFix(post_str);
QString temp = QString::number(result, 'g', 10); //将其转为10位有效数字
ui->display_LineEdit->setText(temp);
}catch(...){
express = "Error";
ui->display_LineEdit->setText(express);
}
express.clear();
}
// 中缀转后缀
QString Widget::infixToPostFix(const QString &str)
{
QStack<QChar> ops;
QString ans = "";
auto priority = [](const QChar c)->int{ //需要进行比较运算符的优先级,优先计算优先级高的
if(c == '+' || c== '-') return 1;
if(c == '*' || c== '/') return 2;
return 0;
};
int len = str.length();
for(int i = 0; i < len; ++i){
QChar c= str[i];
//处理数字和小数点
if(c == '.' || c.isDigit()){
while(i < len && (str[i] == '.' || str[i].isDigit())){
ans += str[i];
++i;
}
i--; //再回退一格
ans += ' '; //用空格分隔数字
}else if(c == '('){
ops.push(c);
}else if(c == ')'){
while(!ops.isEmpty() && ops.top() != '('){
ans += ops.top();
ops.pop();
ans += ' ';
}
if(!ops.isEmpty() && ops.top() == '('){
ops.pop();
}
}else{ //处理四则运算符
while(!ops.isEmpty() && (priority(ops.top()) > priority(c))){
ans += ops.top();
ops.pop();
ans += ' ';
}
ops.push(c);
}
}
//将剩下的运算符全部弹出
while(!ops.isEmpty()){
ans += ops.top();
ops.pop();
ans += ' ';
}
return ans.trimmed();
}
// 计算后缀表达式的值
double Widget::calPostFix(const QString &str)
{
QStack<double> nums;
QString num_str;
int len = str.length();
auto isOperator = [](const QChar& c)->bool{
if(c == '+' || c == '-' || c == '*' || c == '/') return true;
return false;
};
for(int i = 0; i < len; ++i){
QChar c = str[i];
//先处理数字和小数点
if(c == '.' || c.isDigit()){
num_str.clear();
while(i < len && (str[i].isDigit() || str[i] == '.')){
num_str += str[i];
++i;
}
i--;
bool flag = false;
double num = num_str.toDouble(&flag);
if(flag){
nums.push(num);
}
}else if(isOperator(c)){
//操作如果<2不允许计算
if(nums.size() < 2){
return 0;
}
double num_B = nums.top();
nums.pop();
double num_A = nums.top();
nums.pop();
double result = 0.0f;
switch(c.unicode()){
case '+': result = num_A + num_B; break;
case '-': result = num_A - num_B; break;
case '*': result = num_A * num_B; break;
case '/': {
if(num_B == 0){
return 0;
}
result = num_A / num_B;
}
break;
}
nums.push(result);
}
}
if(nums.isEmpty()){
return 0;
}
return nums.top();
}
之前逆波兰式说是两个栈,但这边优化了下,在中缀转后缀以及计算后缀时,都分别只使用了一个栈和一个字符串,用字符串模拟栈操作,减少点代码
三、小结:
3.1. 从代码中可以看到,对于堆上申请的内存,比如ui
,我没有进行手动释放内存,难道不怕造成内存泄漏吗?
QT拥有自动删除机制:
- 当QT父对象被删除时,它会自动删除所有子对象
- 只有当对象没有父对象时,才需要手动delete, 不过大部分对象都会被QT接管,所以不必过分关注,使用`QObject::deleteLater`来进行手动删除<br>