【Qt 编程入门】如何用 Qt 实现一个基本的计算器

ops/2024/9/23 4:21:02/

前言

QT中实现一个简单的计算器是一个比较好的练手项目,下面是源码:

Calculator计算器


设计界面

待实现的界面主要包含两个部分:

  • 输入输出栏
  • 用户点击的按钮

输入输出栏通过QLabel类实现,而用户点击按钮通过QPushButton或QToolButton,按钮的布局我们使用网格布局(grid)

在这里插入图片描述
随后给整个页面进行垂直布局,并将水平垂直策略设为expanding(大小跟随程序页面大小)

再通过qss进行样式表美化,最终效果为:

在这里插入图片描述

用户输入的数字会显示在下面的displayLabel中,当点击等号时,结果会被计算显示到上面的outputLabel中。


程序设计分析

对于整个计算器的功能,都封装在一个calculator类中;
在这里插入图片描述

下面是头文件:

#ifndef CALCULATOR_H
#define CALCULATOR_H#include "ui_calculator.h"#include <QWidget>
#include <QFile>
#include <QMessageBox>
#include <QString>
#include <QStack>
#include <QStack>
#include <QMap>
#include <cmath>
#include <QDebug>
#include <QtMath>
#include <QRegularExpression>
#include <QRegularExpressionMatch>QT_BEGIN_NAMESPACE
namespace Ui { class Calculator; }
QT_END_NAMESPACEclass Calculator : public QWidget
{Q_OBJECTpublic:Calculator(QWidget *parent = nullptr);~Calculator();// 加载qssvoid loadStyleSheetFile(const QString &styleSheetFile);// 各种按钮点击的槽函数void btnNumClicked(double x);void btnOperatorClicked(char op);void btnCClicked();void btnCEClicked();void btnBackClicked();void btnSquareClicked();void btnSqrtClicked();void btnSpotClicked();void btnFractionClicked();void btnPerClicked();void btnReverseClicked();bool btnEqualClicked();private:// 初始化以及其他功能void initConnections();void initMembers();bool isPureNumber(const QString& sen);// int getDecimalPlaces(const QString &text) const;private slots:private:Ui::Calculator *ui;size_t decimalCount;double num;double decimalPlaces;double result;char operation;QString sentence;bool isNewOperation;bool isFirstElement;bool hasDecimalPoint;bool EqualBtnClicked;};
#endif // CALCULATOR_H

在创建calculator对象时,对槽函数以及相关事件进行绑定连接,随后就可以正式编写代码:


代码部分

按钮点击

该槽函数用于当用户点击按钮时的操作,将用户输入的数字存储到成员变量num中即可,通过判断用户是否点击小数点,进行小数点的统计与显示。

void Calculator::btnNumClicked(double x)
{if(EqualBtnClicked) {sentence = "";num = '\0';}// 处理数字输入if (isFirstElement) {num = x;isFirstElement = false;ui->displayLabel->setText(QString::number(num, 'f', decimalCount));} else {if (hasDecimalPoint) {// 处理小数部分num += x * decimalPlaces;decimalPlaces /= 10; // 减少小数点后面的位数decimalCount++;qDebug() << "decimalConut: " << decimalCount;ui->displayLabel->setText(QString::number(num, 'f', decimalCount));} else {// 处理整数部分num = num * 10 + static_cast<int>(x);ui->displayLabel->setText(QString::number(num, 'f', decimalCount));}}
}

小数点点击

判断用户点击小数点前是否有数字 / 点击过了,随后将标识符设为true

void Calculator::btnSpotClicked()
{if(isFirstElement || hasDecimalPoint) {return;}hasDecimalPoint = true;
}

操作符点击

sentence 变量负责存储算式,最后由equal计算,用户点击操作符后,会根据情况将操作符加载到算是后面。

void Calculator::btnOperatorClicked(char op)
{if(EqualBtnClicked) {sentence = "";num = '\0';}// 如果上一次操作后有数字,则将其加入 sentenceif (!isFirstElement) {if ((!sentence.isEmpty() && !sentence.back().isDigit()) || sentence.isEmpty())sentence += QString::number(num, 'f', decimalCount);qDebug() << "num: " << num;num = 0;  // 重置 num 以便输入新的数字isFirstElement = true;  // 标记新的数字输入开始}// 检查 sentence 是否以运算符结尾if (!sentence.isEmpty() && (sentence.endsWith('+') || sentence.endsWith('-') || sentence.endsWith('*') || sentence.endsWith('/'))) {// 如果最后一位是运算符,替换运算符sentence.chop(1);  // 删除最后一个运算符}// 追加新的运算符sentence += op;// 更新显示ui->outputLabel->setText(sentence);ui->displayLabel->setText("");size_t tmp = decimalCount;initMembers();decimalCount = tmp;
}

退格

退格键是对用户当前正在输入的数字进行尾删,根据情况更新num,并重设displayLabel上显示的内容

void Calculator::btnBackClicked()
{if(EqualBtnClicked) {sentence = "";num = '\0';}QString currentText = ui->displayLabel->text(); // 获取当前显示的文本if (currentText.isEmpty()) {return; // 如果没有文本,直接返回}// 移除最后一个字符currentText.chop(1); // 或者使用 remove() 方法:currentText.remove(currentText.length() - 1, 1);// 如果移除后的文本为空,重置显示为 0if (currentText.isEmpty()) {currentText = "0";}// 更新显示标签ui->displayLabel->setText(currentText);// 更新 num 变量bool ok;num = currentText.toDouble(&ok);if (!ok) {num = 0; // 如果转换失败,则将 num 设置为 0}
}

等号(计算)

等号功能进行实际的运算,通过将用户输入的sentence进行运算:
计算结果后将内容回显到显示器中。

bool Calculator::btnEqualClicked() {if(isPureNumber(sentence)) {ui->outputLabel->setText(QString::number(num, 'f', decimalCount));sentence = QString::number(num, 'f', decimalCount);return true;}// 将当前数字(num)转换为字符串,并添加到当前计算表达式(sentence)中sentence += QString::number(num, 'f', decimalCount);QString str = sentence; // 将表达式字符串复制到局部变量QStack<double> numbers; // 用于存储操作数的栈QStack<QChar> operators; // 用于存储操作符的栈if(!sentence.begin()->isDigit()) {ui->outputLabel->setText("Error: There are no numbers before the operator."); // 显示结果(保留两位小数)ui->displayLabel->setText("");initMembers();sentence = "";return false;}// 定义操作符的优先级QMap<QChar, int> precedence = {{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};str = str.trimmed(); // 去除前后的空白字符int length = str.length(); // 获取表达式的长度for (int i = 0; i < length; ++i) {QChar ch = str[i]; // 当前字符// 处理数字或小数点if (ch.isDigit() || (ch == '.' && (i > 0 && str[i - 1].isDigit()))) {QString numberStr;while (i < length && (str[i].isDigit() || str[i] == '.')) {numberStr += str[i++]; // 读取完整的数字(包括小数点)}--i; // 回退一个字符,因为循环结束时 i 已经多加了一次numbers.push(numberStr.toDouble()); // 将数字转换为 double 并压入操作数栈}// 处理负号(负数)else if (ch == '-' && (i == 0 || (i > 0 && str[i - 1] == '('))) {QString numberStr;numberStr += ch; // 负号while (i + 1 < length && (str[i + 1].isDigit() || str[i + 1] == '.')) {numberStr += str[++i]; // 读取负数后的数字}numbers.push(numberStr.toDouble()); // 将负数转换为 double 并压入操作数栈}// 处理操作符(+ - * /)else if (precedence.contains(ch)) {// 处理当前操作符前已有的操作符(具有相同或更高优先级的)while (!operators.isEmpty() && precedence[operators.top()] >= precedence[ch]) {double right = numbers.pop(); // 从操作数栈弹出右操作数double left = numbers.pop(); // 从操作数栈弹出左操作数QChar op = operators.pop(); // 从操作符栈弹出操作符double result;if (op == '+') result = left + right; // 计算加法else if (op == '-') result = left - right; // 计算减法else if (op == '*') result = left * right; // 计算乘法else if (op == '/') {if (right == 0) {  // 处理除以零的情况ui->outputLabel->setText("Error: Division by zero");return false;}result = left / right; // 计算除法}numbers.push(result); // 将结果压入操作数栈}operators.push(ch); // 将当前操作符压入操作符栈}}// 处理剩余的操作符while (!operators.isEmpty()) {double right = numbers.pop(); // 从操作数栈弹出右操作数double left = numbers.pop(); // 从操作数栈弹出左操作数QChar op = operators.pop(); // 从操作符栈弹出操作符double result;if (op == '+') result = left + right; // 计算加法else if (op == '-') result = left - right; // 计算减法else if (op == '*') result = left * right; // 计算乘法else if (op == '/') {if (right == 0) {  // 处理除以零的情况ui->outputLabel->setText("Error: Division by zero");return false;}result = left / right; // 计算除法}numbers.push(result); // 将结果压入操作数栈}EqualBtnClicked = true;// 确保栈中仅剩一个操作数,即计算结果if (numbers.size() == 1) {double result = numbers.pop(); // 获取计算结果qDebug() << "ret: " << result;ui->outputLabel->setText(QString::number(result, 'f', decimalCount)); // 显示结果(保留两位小数)ui->displayLabel->setText("");sentence = QString::number(result, 'f', decimalCount);num = result;operation = '\0';// isNewOperation = true;isFirstElement = true;hasDecimalPoint = false;decimalPlaces = 0.1;decimalCount = 0;EqualBtnClicked = false;return true;} else {ui->outputLabel->setText("Error"); // 显示错误信息return false;}
}void Calculator::initConnections()
{// 初始化数字按钮for (int i = 0; i <= 9; ++i){connect(findChild<QToolButton*>(QString("btn%1").arg(i)), &QToolButton::clicked, [this, i]() { btnNumClicked(i); });}// 初始化运算符connect(ui->btnAdd, &QToolButton::clicked, [this]() { btnOperatorClicked('+'); });connect(ui->btnSub, &QToolButton::clicked, [this]() { btnOperatorClicked('-'); });connect(ui->btnMul, &QToolButton::clicked, [this]() { btnOperatorClicked('*'); });connect(ui->btnDiv, &QToolButton::clicked, [this]() { btnOperatorClicked('/'); });// 绑定 等于按钮connect(ui->btnEqual, &QToolButton::clicked, [this]() { btnEqualClicked(); });// 绑定其他功能按钮connect(ui->btnC, &QToolButton::clicked, [this]() { btnCClicked(); });connect(ui->btnCE, &QToolButton::clicked, [this]() { btnCEClicked(); });connect(ui->btnBack, &QToolButton::clicked, [this]() { btnBackClicked(); });connect(ui->btnSquare, &QToolButton::clicked, [this]() { btnSquareClicked(); });connect(ui->btnFraction, &QToolButton::clicked, [this]() { btnFractionClicked(); });connect(ui->btnSqrt, &QToolButton::clicked, [this]() { btnSqrtClicked(); });connect(ui->btnPer, &QToolButton::clicked, [this]() { btnPerClicked(); });connect(ui->btnSpot, &QToolButton::clicked, [this]() { btnSpotClicked(); });connect(ui->btnReverse, &QToolButton::clicked, [this]() { btnReverseClicked(); });}

初始化 C / CE

C、CE分别为完全初始化与初始化当前输入,通过调用自实现的initMembers()并更改显示器上的显示内容;

oid Calculator::btnCClicked()
{// 清空全部内容initMembers();sentence = "";ui-> displayLabel->setText("");ui->outputLabel->setText("");
}void Calculator::btnCEClicked()
{initMembers();ui->displayLabel->setText("");
}void Calculator::initMembers()
{num = 0;operation = '\0';isNewOperation = true;isFirstElement = true;hasDecimalPoint = false;decimalPlaces = 0.1;decimalCount = 0;EqualBtnClicked = false;
}

其他功能

其他的功能诸如平方数、开根号、取分数等,都只需要简单计算一下再设置num即可。

void Calculator::btnCClicked()
{// 清空全部内容initMembers();sentence = "";// ui-> displayLabel->setText("displayLabel");// ui->outputLabel->setText("outputLabel");ui-> displayLabel->setText("");ui->outputLabel->setText("");
}void Calculator::btnCEClicked()
{initMembers();ui->displayLabel->setText("");
}void Calculator::btnSquareClicked()
{num *= num;ui->displayLabel->setText(QString::number(num, 'f', decimalCount + 6));
}void Calculator::btnSqrtClicked()
{if (num >= 0) { // 防止负数开根号的错误num = sqrt(num); // 计算平方根ui->displayLabel->setText(QString::number(num, 'f', decimalCount + 6)); // 显示6位小数} else {ui->displayLabel->setText("Error: 负数不能开根号"); // 处理负数开根号的情况}
}void Calculator::btnFractionClicked()
{if (num != 0) { // 防止除以0的错误num = 1.0 / num;ui->displayLabel->setText(QString::number(num, 'f', decimalCount + 6)); // 显示6位小数} else {ui->displayLabel->setText("Error: 除数不能为0"); // 处理除以0的情况}
}void Calculator::btnPerClicked()
{num = num / 100.0; // 将当前值转换为百分比ui->displayLabel->setText(QString::number(num, 'f', decimalCount + 6)); // 显示6位小数
}void Calculator::btnReverseClicked()
{num = -num;ui->displayLabel->setText(QString::number(num, 'f', decimalCount)); // 更新显示,6位小数
}

结果演示

在这里插入图片描述


http://www.ppmy.cn/ops/110690.html

相关文章

基于JavaWeb开发的java springboot+mybatis电影售票网站管理系统前台+后台设计和实现

基于JavaWeb开发的java springbootmybatis电影售票网站管理系统前台后台设计和实现 &#x1f345; 作者主页 网顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获…

python测试开发---vue基础

一、什么是 Vue.js&#xff1f; Vue.js&#xff08;读作 /vjuː/ 像 view&#xff09;是一个用于构建用户界面的渐进式 JavaScript 框架。它的核心是一个可以渐进使用的库&#xff0c;既可以应用在一个简单的小项目里&#xff0c;也可以用来开发复杂的单页面应用&#xff08;S…

小程序——生命周期

文章目录 运行机制更新机制生命周期介绍应用级别生命周期页面级别生命周期组件生命周期生命周期两个细节补充说明总结 运行机制 用一张图简要概述一下小程序的运行机制 冷启动与热启动&#xff1a; 小程序启动可以分为两种情况&#xff0c;一种是冷启动&#xff0c;一种是热…

限流,流量整形算法

写在前面 源码 。 本文看下流量整形相关算法。 目前流量整形算法主要有三种&#xff0c;计数器&#xff0c;漏桶&#xff0c;令牌桶。分别看下咯&#xff01; 1&#xff1a;计数器 1.1&#xff1a;描述 单位时间内只允许指定数量的请求&#xff0c;如果是时间区间内超过指…

LSTM处理时序数据:深入解析与实战

大家好&#xff0c;我是你们的深度学习老群群。今天&#xff0c;我们来聊一聊LSTM&#xff08;长短期记忆网络&#xff09;是如何处理时序数据并得到预测结果的。LSTM作为循环神经网络&#xff08;RNN&#xff09;的一种变体&#xff0c;因其能够有效捕捉长期依赖关系&#xff…

vmware esxi 6.5 开启 snmp 服务

学习目标&#xff1a; 如何开启 vmware esxi 6.xx 开启 snmp 服务 查看SNMP 是否开启状态&#xff1a; 如何开启SNMP&#xff1a; 1.用 MAC、Linux SSH 工具 (如 SecureCRT) 连接 esxi 2、修改 SNMP 配置文件 vi /etc/vmware/snmp.xml3 、将标签 false 改为 true 在 后加上…

20、网络数据安全管理条例

第一章 总则 第一条 为了规范网络数据处理活动, 保障数据安全, 保护个人、 组织在网络空间的合法权益, 维护国家安全、 公共利益, 根据《中华人民共和国网络安全法》《中华人民共和国数据安全法》《中华人民共和国个人信息保护法》等法律,制定本条例。 第二条 在中华人民…

JAVA相关知识

JAVA基础知识 说一下对象创建的过程&#xff1f; 类加载检查&#xff1a;当Java虚拟机&#xff08;JVM&#xff09;遇到一个类的new指令时&#xff0c;它首先检查这个类是否已经被加载、链接和初始化。如果没有&#xff0c;JVM会通过类加载器&#xff08;ClassLoader&#xff…