30、Qt综合项目:计算器或记事本实战,综合运用所学知识

终于走到这一步了。说实话,每次我带新人做这个综合项目时,都能看到他们眼睛一亮——前面学了一大堆零散的知识点,终于能串起来了。今天我们就拿计算器和记事本这两个经典项目开刀,把之前学的信号槽、布局、文件操作、事件处理这些东西,全部揉进一个完整的项目里。

项目一:简易计算器

先说说计算器。你别看它功能简单,里面坑可不少。我记得自己第一次写计算器时,被那个「连续运算」的逻辑绕晕了好几天。

核心功能拆解

一个计算器,说白了就三件事:

  • 输入数字:点击按钮,把数字显示到屏幕上
  • 选择运算符:记住用户按了哪个运算符
  • 计算结果:按等号时,把前面存的值和当前值做运算

嗯,这里要注意:用户可能会连续按运算符,比如「5 + 3 - 2 × 4 ÷ 2」。你得处理好运算优先级,或者干脆从左到右依次计算(简单计算器常用方式)。

界面布局

我个人习惯用 QGridLayout 来排按钮。0-9 数字键、加减乘除、等号、清空,一共 16 个按钮,4×4 的网格刚刚好。

// 创建按钮网格
QGridLayout *grid = new QGridLayout;
for (int i = 0; i < 4; ++i) {
    for (int j = 0; j < 4; ++j) {
        QPushButton *btn = new QPushButton(buttonTexts[i][j]);
        grid->addWidget(btn, i, j);
    }
}

你想想看,如果不用布局,手动调坐标,窗口一拉伸就全乱套了。布局就是干这个的——自动适应窗口大小变化。

信号槽连接

每个按钮点击后,都要触发对应的槽函数。这里有个小技巧:把所有数字按钮的 clicked 信号连到同一个槽,然后通过 sender() 获取是哪个按钮被按了。

void Calculator::onDigitClicked() {
    QPushButton *btn = qobject_cast<QPushButton*>(sender());
    if (!btn) return;
    QString digit = btn->text();
    // 追加到当前输入
    currentInput += digit;
    display->setText(currentInput);
}

我在项目中遇到过类似场景:一个界面有几十个按钮,如果每个按钮单独写一个槽,代码会膨胀到没法看。用 sender() 统一处理,清爽多了。

运算逻辑

计算器的核心逻辑其实是个状态机。我画个图帮你理解:

计算器核心状态机 状态A:输入第一个数 (等待数字按键) 状态B:选择运算符 (记住运算符) 状态C:输入第二个数 (继续追加数字) 状态D:显示结果 (按等号后触发) 按运算符 按数字 按等号 按清空

这个状态机搞清楚了,代码写起来就顺了。每个状态对应一个成员变量,记录当前处于哪个阶段。

核心变量

  • double leftOperand:左操作数(运算符左边的值)
  • QString operator:当前运算符
  • bool waitingForOperand:是否在等待输入第二个数

项目二:记事本

记事本这个项目,我更喜欢拿来做教学。为什么?因为它涉及的知识面更广——文件读写、文本编辑、菜单栏、工具栏、快捷键,几乎把 Qt 的基础组件全用上了。

界面结构

一个标准的记事本,从上到下是:菜单栏 → 工具栏 → 编辑区 → 状态栏。用 QMainWindow 自带的布局就能搞定。

// 创建菜单栏
QMenu *fileMenu = menuBar()->addMenu("文件(&F)");
QAction *openAction = fileMenu->addAction("打开(&O)");
openAction->setShortcut(QKeySequence::Open);

// 创建工具栏
QToolBar *toolBar = addToolBar("文件");
toolBar->addAction(openAction);

// 创建编辑区
QTextEdit *editor = new QTextEdit;
setCentralWidget(editor);

// 状态栏
statusBar()->showMessage("就绪");

你看,Qt 把这些东西封装得特别好。菜单、工具栏、快捷键,几行代码就串起来了。我曾经在做一个文本编辑器项目时,光快捷键就配了二十多个,全靠 QActionsetShortcut 搞定。

文件读写

文件操作是记事本的核心。打开文件用 QFileDialog::getOpenFileName,保存用 QFileDialog::getSaveFileName。读写用 QFile 配合 QTextStream

void Notepad::openFile() {
    QString fileName = QFileDialog::getOpenFileName(this, "打开文件", QString(), "文本文件 (*.txt);;所有文件 (*)");
    if (fileName.isEmpty()) return;

    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QMessageBox::warning(this, "错误", "无法打开文件:" + file.errorString());
        return;
    }

    QTextStream in(&file);
    editor->setPlainText(in.readAll());
    file.close();
    statusBar()->showMessage("已打开:" + fileName);
}

注意:文件打开后一定要检查是否成功。我曾经在项目里忘了检查 file.open() 的返回值,结果用户选了个只读文件,程序直接崩溃了。从那以后,我养成了「打开必检查」的习惯。

编辑功能

除了基本的输入,记事本还可以加一些实用功能:

  • 查找替换:用 QTextEdit::find() 实现
  • 字体设置:用 QFontDialog 让用户选字体
  • 自动换行editor->setLineWrapMode(QTextEdit::WidgetWidth)
  • 撤销/重做QTextEdit 自带 undo()redo()

你想想看,这些功能如果让你从头实现,得写多少代码?Qt 直接帮你封装好了,你只需要调用几个函数就行。

综合对比

这两个项目,我建议你都做一遍。计算器侧重逻辑和状态管理,记事本侧重界面和文件操作。做完之后,你对 Qt 的理解会上一个台阶。

对比项 计算器 记事本
核心知识点 信号槽、布局、状态机 菜单栏、文件IO、文本编辑
难点 运算逻辑、连续运算 文件编码、查找替换
推荐学习时长 2-3天 3-4天
实用价值 理解事件驱动编程 掌握桌面应用基本框架

我的建议:先做计算器,再做记事本。计算器代码量小(大概200-300行),适合练手。记事本代码量大一些(500-800行),但结构更清晰,适合巩固。

好了,这两个项目就讲到这里。代码我已经给了核心部分,剩下的细节你边写边查文档就行。记住,编程这东西,光看是学不会的,一定要动手敲一遍。

公众号:蓝海资料掘金营,微信deep3321