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

news2024/12/23 12:32:01

前言

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_NAMESPACE

class Calculator : public QWidget
{
    Q_OBJECT

public:
    Calculator(QWidget *parent = nullptr);
    ~Calculator();
	// 加载qss
    void 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';
    }

    // 如果上一次操作后有数字,则将其加入 sentence
    if (!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);

    // 如果移除后的文本为空,重置显示为 0
    if (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.coloradmin.cn/o/2132367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3分钟带你快速了解 Java 接口

我醉欲眠卿且去 明朝有意抱琴来 目录 接口的概念 定义接口 接口格式 接口的特性 &#xff08;1&#xff09;接口不能直接创建对象 &#xff08;2&#xff09;接口不能用 final 修饰 &#xff08;3&#xff09;接口中定义的变量 &#xff08;4&#xff09;接口中定义的方法 接…

Visual Studio Installer 2022 安装提示正在提取文件 进度条不动 0B每秒

Visual Studio Installer 稍等片刻...正在提取文件 进度条不动 0B每秒 一段时间后提示 循环下载安装文件 无法下载安装文件。请检查Internet 连接&#xff0c;然后重试 打开vs2017 或者vs2019或者vs2022的安装程序(visual studio installer)时&#xff0c;下载进度条不动&…

【C++】认识C++(前言)

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:C: 探索C编程精髓&#xff0c;打造高效代码仓库 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、本节概述 二、什么是C 三、C发展史 四…

为了准确计算延迟退休时间,我做了一个退休年龄计算器

延迟退休计算方法 原本退休分为三种情况&#xff0c;男性&#xff0c;女工人&#xff0c;女干部 男性&#xff1a;退休年龄为60岁。女干部&#xff1a;退休年龄为55岁。女工人&#xff1a;退休年龄为50岁。 现在延迟以后&#xff08;根据2024年9月13日公布的规则&#xff09…

武汉墨家人俱乐部

这里主要是墨家人聊科技的俱乐部&#xff0c;想来的在评论区报名吧&#xff01; 这里有VR 各种AI软件 绘图 炼丹

半导体AI硬件基础设施发展洞察

2024 半导体分析洞察&#xff1a;AI 硬件基础设施篇 一、引言随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;AI 硬件基础设施在整个 AI 生态系统中的地位日益凸显。半导体作为 AI 硬件基础设施的核心组成部分&#xff0c;正面临着前所未有的机遇和挑战。2…

作图神器!AI 免抠图生成工具,告别素材烦恼

在设计的道路上&#xff0c;相信大家都遇到过这样令人头疼的问题&#xff1a;好不容易找到合适的素材&#xff0c;结果下载的时候提示需要付费&#xff0c;更悲催的是&#xff0c;即使花钱下载了还有可能面临侵权风险。曾经的我也深陷这样的困境&#xff0c;直到我发现了一个超…

java重点学习-集合(Map)

七 集合&#xff08;Map&#xff09; 7.8 什么是二叉树 1.什么是二叉树 每个节点最多有两个“叉”&#xff0c;分别是左子节点和右子节点。不要求每个节点都有两个子节点&#xff0c;有的节点只有左子节点&#xff0c;有的节点只有右子节点。二叉树每个节点的左子树和右子树也…

红帽7—tomcat的部署方法

Tomcat 服务器是一个免费的开放源代码的Web 应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和 并发访问用户不是很多的场合下被普遍使用&#xff0c;Tomcat 具有处理HTML页面的功能&#xff0c;它还是一个Servlet和 JSP容器 一&#xff1a;安装 Tomcat …

中国将越南语集中在哪里?怎样更好的提高越南语口语能力?

中国与越南接壤&#xff0c;两国之间有着密切的文化交流。在中国&#xff0c;学习越南语的教育资源主要集中在一些大学的语言学院、专业的外语培训机构以及与越南有较多交流的边境地区&#xff0c;如广西壮族自治区和云南省。这些地区由于地理位置接近越南&#xff0c;因此越南…

Social Network Influence Maximization Based on Graph Attention Mechanisms

摘要 社交网络中的关键节点对整个网络具有重要的影响力&#xff0c;而社交网络中的一些节点可能位于网络的中心&#xff0c;而另一些节点则可能位于网络的边缘&#xff0c;传统的影响力最大化算法忽略了这种不平衡。通过忽略位于网络边缘但连接到中心的节点来导致信息的传播。为…

openai最新模型o1全面解读

OpenAI发布o1模型 1.1 o1模型的命名背景 在人工智能的浩瀚星空中&#xff0c;每一次新模型的发布都像是一颗新星的诞生&#xff0c;而OpenAI最新发布的o1模型&#xff0c;无疑是这星空中的璀璨新星。那么&#xff0c;这个神秘的“o1”究竟有何深意呢&#xff1f; 首先&#x…

HT760 2x30W1²S Input, Inductor Free, Stereo Class D Amplifier

FEATURE Power supply: -PVDD:4.5V-22V: -DVDD: 3.3V "Audio Performance -BTL,2x33W(PVDD22V,RL8Ω THDN10%) -PBTL, 50W(PVDD22V, RL4Ω,THDN1%) -THDN0.05%(PVDD12V,RL4Ω,PO1W) -Noise: 100uV(Gain 19dBV, A weighted) Low Quiescent Current -12mA at PVDD 12V, no…

单个 java 虚拟机 生产者消费者

一、通过 java.lang.Object#wait()&#xff0c;java.lang.Object#notify&#xff0c;java.lang.Object#notifyAll来实现 生产者&#xff0c;消费者 public abstract class Goods {protected String type;protected String goodName;protected int number;public abstract …

redis高级教程

一 关系型数据库和 NoSQL 数据库 数据库主要分为两大类&#xff1a;关系型数据库与 NoSQL 数据库 关系型数据库 &#xff0c;是建立在关系模型基础上的数据库&#xff0c;其借助于集合代数等数学概念和方法来处理数据库中的数据主流的 MySQL 、 Oracle 、 MS SQL Server 和 D…

基于SpringBoot+Vue+MySQL的美术馆管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着文化艺术产业的蓬勃发展&#xff0c;美术馆作为展示与传播艺术的重要场所&#xff0c;其管理工作变得日益复杂。为了提升美术馆的运营效率、优化参观体验并加强艺术品管理&#xff0c;我们开发了基于SpringBootVueMySQL的美…

树莓派!干农活!

农作物种植是一个需要精准操作的行业&#xff0c;而农业的长期趋势是朝着机械化方向发展。Directed Machines公司的土地护理机器人&#xff08;Land Care Robot&#xff09;&#xff0c;基于Raspberry Pi4和RP2040构建&#xff0c;是解放稀缺人力资本的一种经济高效方式。 Dir…

墨西哥海外仓市场如何?如何选择墨西哥海外仓系统?

随着全球电商市场的迅猛发展&#xff0c;墨西哥作为拉美地区的重要市场&#xff0c;其电商增速在2023年高达24.6%&#xff0c;位居世界第一&#xff0c;这一数据无疑展示了墨西哥电商市场的巨大潜力和繁荣景象。 作为拉美地区最大的电商平台&#xff0c;美客多在墨西哥市场的扩…

iPhone 16分辨率,屏幕尺寸,PPI 详细数据对比 iPhone 16 Plus、iPhone 16 Pro、iPhone 16 Pro Max

史上最全iPhone 机型分辨率&#xff0c;屏幕尺寸&#xff0c;PPI详细数据&#xff01;已更新到iPhone 16系列&#xff01; 点击放大查看高清图 &#xff01;