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

前言

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

Calculator计算器


设计界面

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

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

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

随后给整个页面进行垂直布局,并将水平垂直策略设为expanding(大小跟随程序页面大小)

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

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


程序设计分析

对于整个计算器的功能,都封装在一个calculator类中;

下面是头文件:

cpp 复制代码
#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中即可,通过判断用户是否点击小数点,进行小数点的统计与显示。

cpp 复制代码
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

cpp 复制代码
void Calculator::btnSpotClicked()
{
    if(isFirstElement || hasDecimalPoint) {
        return;
    }

    hasDecimalPoint = true;
}

操作符点击

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

cpp 复制代码
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上显示的内容

cpp 复制代码
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进行运算:

计算结果后将内容回显到显示器中。

cpp 复制代码
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()并更改显示器上的显示内容;

cpp 复制代码
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即可。

cpp 复制代码
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位小数
}

结果演示

相关推荐
用户805533698033 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner3 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz8 天前
QML Hello World 入门示例
qt
xcyxiner11 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner12 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner13 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00614 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术14 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript