1. 项目概述:为什么是C++与Qt的组合?
如果你是一名C++开发者,正在寻找一个既能构建高性能后端逻辑,又能快速开发出漂亮、稳定桌面客户端的解决方案,那么C++与Qt的组合几乎是绕不开的黄金搭档。我过去十多年的项目经验里,从工业控制软件到复杂的图形图像处理工具,这个组合的出场率极高。它解决的痛点非常明确:C++负责处理计算密集、对性能要求苛刻的核心业务,而Qt则提供了一个成熟、跨平台的框架,让你能用同一套代码,高效地构建出在Windows、macOS、Linux上都能原生运行的图形界面。
很多人对C++的印象还停留在“难学、难用、GUI开发更是噩梦”的阶段,这其实是对现代C++和成熟框架的误解。Qt不仅仅是一个GUI库,它是一个完整的应用程序框架,涵盖了从界面控件、网络通信、数据库访问到多线程、XML/JSON解析等方方面面。当你用C++写好了核心算法模块,Qt能让你像搭积木一样,把这些模块封装成带界面的、可交互的应用程序,整个过程非常顺畅。
这次,我不打算只讲空洞的理论。我会通过一个完整的Demo项目,手把手带你走一遍从零开始,结合C++与Qt进行高效编程的全过程。这个Demo模拟了一个“实时数据监控仪表盘”,它包含了核心数据模型(纯C++)、数据处理逻辑、以及用Qt构建的图形界面。你会看到如何将两者优雅地集成,并学到一系列提升开发效率的实战技巧。
2. 环境准备与项目骨架搭建
工欲善其事,必先利其器。一个稳定、高效的开发环境是项目成功的第一步。对于C++和Qt开发,工具链的选择至关重要。
2.1 开发工具选型与安装
首先,你需要安装以下核心组件:
C++编译器:在Windows上,我强烈推荐使用MSVC(Microsoft Visual C++),它是Visual Studio的一部分,与Qt的兼容性最好。你可以安装Visual Studio 2022 Community版(免费),在安装时勾选“使用C++的桌面开发”工作负载。在macOS和Linux上,可以使用Clang或GCC。
Qt框架本身:前往Qt官网的下载页面。这里有一个关键选择:在线安装器还是离线安装包?对于新手和大多数开发者,我推荐使用在线安装器(Qt Maintenance Tool)。它允许你自由选择需要的Qt版本和组件,非常灵活。
- 版本选择:对于新项目,建议选择Qt 6的LTS(长期支持)版本,如Qt 6.6 LTS。它提供了最新的特性和更好的性能。如果你的项目需要兼容一些旧的第三方库,也可以考虑Qt 5.15 LTS。
- 组件选择:在安装时,务必为你选择的编译器(如MSVC 2022 64-bit)勾选对应的Qt套件。此外,“Qt Creator”这个集成开发环境(IDE)一定要装,它是Qt开发的“官方外挂”,能极大提升效率。其他如Qt Charts、Qt Data Visualization等模块,可以根据Demo需求选择性安装。
集成开发环境(IDE):Qt Creator是首选。它深度集成了Qt的构建系统(qmake或CMake)、UI设计器(Qt Designer)、调试器以及帮助文档。打开Qt Creator后,在“工具”->“选项”->“Kits”中,确保检测到了你的编译器和Qt版本,并配置好一个可用的“Kit”。
注意:避免在系统路径中安装多个不同版本的Qt或编译器,这可能导致构建时链接到错误的库,引发难以排查的“undefined reference”或运行时崩溃。使用Qt Creator的Kit管理功能来清晰地隔离不同环境。
2.2 创建项目与理解Qt的构建系统
打开Qt Creator,点击“新建项目”。我们将创建一个“Qt Widgets Application”。在项目配置中,有几处需要留意:
- 构建系统:你会看到qmake和CMake两个选项。Qt 6开始,官方更推荐使用CMake。CMake更现代、功能更强大,且是C++社区的通用标准。我们的Demo将使用CMake。如果你对CMake不熟,没关系,Qt Creator会生成一个基础的
CMakeLists.txt文件,我们后续会详细解读。 - 类名:默认会生成
MainWindow类。保持默认即可。 - 翻译文件:如果你的项目有国际化需求,可以勾选。本次Demo暂不涉及。
点击完成后,Qt Creator会为你生成一个基本的项目骨架。让我们快速浏览一下关键文件:
CMakeLists.txt: 项目的“总说明书”,定义了项目名称、需要的Qt模块、包含的源文件、生成的可执行文件等。main.cpp: 程序入口,创建并显示主窗口。mainwindow.h/mainwindow.cpp: 主窗口类的头文件和实现文件。mainwindow.ui: Qt Designer的UI文件,以XML格式存储了窗口的界面布局和控件属性。这是一个关键文件,它实现了界面与逻辑的分离。
2.3 初识信号与槽:Qt的核心通信机制
在深入Demo之前,必须理解Qt的“信号与槽”(Signals & Slots)机制。这是Qt区别于其他GUI框架的灵魂所在,也是实现C++逻辑与Qt界面解耦的关键。
你可以把它想象成一个“发布-订阅”系统,或者一个更灵活、类型安全的回调函数机制。
- 信号(Signal):由对象在某种特定事件发生时“发出”(emit)。比如,一个按钮被点击时,会发出
clicked()信号。 - 槽(Slot):就是一个普通的成员函数,可以被调用来响应某个信号。
它们通过QObject::connect函数连接起来。这种连接是松耦合的:发出信号的对象不需要知道谁接收,接收槽的对象也不需要知道信号来自哪里。
// 一个经典的连接示例:按钮点击后关闭窗口 QPushButton *button = new QPushButton(“关闭”, this); connect(button, &QPushButton::clicked, this, &MainWindow::close); // 解释:当 button 发出 clicked() 信号时,调用 this(MainWindow)的 close() 槽函数。在Demo中,我们将大量使用信号与槽来让界面上的操作(如点击按钮、滑动滑块)触发我们C++核心模块的计算逻辑,并将计算结果显示回界面。
3. Demo项目核心设计:实时数据监控仪表盘
为了让演示更具体,我们设计一个“实时数据监控仪表盘”应用。这个应用模拟从某个数据源(如传感器、网络)持续获取数据,经过C++核心模块处理(如滤波、统计),最后通过Qt界面实时展示为图表和数值。
3.1 需求分析与模块划分
我们将项目清晰地划分为三个层次,这是保持代码清晰、可维护的关键:
数据核心层(纯C++):
DataPoint: 表示一个数据点,包含时间戳和数值。DataProcessor: 核心数据处理类。负责模拟数据生成、实现数据滤波算法(如移动平均)、计算统计数据(最大值、最小值、平均值)。这个类不包含任何Qt代码,是纯粹的业务逻辑。
模型适配层(Qt Model):
DataModel: 继承自QAbstractTableModel。它的作用是作为数据核心层与Qt视图控件(如QTableView)之间的桥梁。它持有DataProcessor生成的数据,并按照Qt Model/View框架的约定,为视图提供数据。当DataProcessor的数据更新时,DataModel会发出相应的信号通知视图刷新。
视图呈现层(Qt Widgets):
- 主窗口(
MainWindow):使用Qt Designer设计界面,包含:- 一个
QTableView用于以表格形式展示历史数据。 - 一个
QChartView(来自Qt Charts模块)用于绘制实时折线图。 - 几个
QLabel用于显示当前值、平均值等统计数据。 - 控制按钮:如“开始/停止”模拟、“清空数据”。
- 一个
QSlider用于调整数据更新频率。
- 一个
- 主窗口(
3.2 数据核心层实现:DataProcessor类
首先,我们实现不依赖Qt的纯C++核心类。在项目中创建dataprocessor.h和dataprocessor.cpp。
// dataprocessor.h #ifndef DATAPROCESSOR_H #define DATAPROCESSOR_H #include <vector> #include <chrono> #include <mutex> #include <atomic> struct DataPoint { std::chrono::system_clock::time_point timestamp; double value; DataPoint(double v) : timestamp(std::chrono::system_clock::now()), value(v) {} }; class DataProcessor { public: DataProcessor(); ~DataProcessor(); // 模拟生成一个新的数据点(例如,正弦波加随机噪声) DataPoint generateNextData(); // 对历史数据进行移动平均滤波 double calculateMovingAverage(size_t windowSize = 5) const; // 获取统计数据 double getCurrentValue() const; double getMaxValue() const; double getMinValue() const; double getAverageValue() const; // 获取所有历史数据(用于模型和图表) const std::vector<DataPoint>& getHistoryData() const; // 清空历史数据 void clearHistory(); // 控制模拟线程的运行 void startSimulation(int intervalMs = 100); void stopSimulation(); bool isRunning() const; // 供外部(如Qt槽函数)连接,当有新数据点时发出信号 // 注意:这是一个自定义信号,需要在类声明末尾使用 `signals:` 区域(QObject派生类) // 但DataProcessor是纯C++类,我们稍后会通过适配器模式与Qt集成。 // 此处先预留一个回调函数指针或std::function,更优雅的方式见下文。 private: void simulationThreadFunc(int intervalMs); // 模拟数据生成线程函数 std::vector<DataPoint> m_historyData; mutable std::mutex m_dataMutex; // 保护共享数据m_historyData std::atomic<bool> m_simulationRunning{false}; std::thread m_simulationThread; // 内部状态,用于模拟数据生成 double m_simulationPhase{0.0}; }; #endif // DATAPROCESSOR_H在实现文件dataprocessor.cpp中,我们需要实现数据生成、滤波算法和线程控制。这里的关键点是线程安全。因为数据生成在一个后台线程,而UI线程需要读取这些数据来更新界面,所以对m_historyData的访问必须用互斥锁std::mutex保护。
// dataprocessor.cpp 部分关键实现 DataPoint DataProcessor::generateNextData() { std::lock_guard<std::mutex> lock(m_dataMutex); // 模拟一个正弦波信号加上一些随机噪声 m_simulationPhase += 0.1; double noise = (std::rand() % 100) / 100.0 - 0.5; // [-0.5, 0.5] double value = 10.0 * std::sin(m_simulationPhase) + noise; DataPoint newPoint(value); m_historyData.push_back(newPoint); // 限制历史数据长度,防止内存无限增长 const size_t maxHistory = 1000; if (m_historyData.size() > maxHistory) { m_historyData.erase(m_historyData.begin()); } return newPoint; } void DataProcessor::simulationThreadFunc(int intervalMs) { while (m_simulationRunning) { auto newPoint = generateNextData(); // 问题:如何通知UI线程数据已更新? // 纯C++类不能直接发射Qt信号。我们需要一个桥梁。 std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs)); } }这里暴露了一个关键集成问题:后台线程生成了新数据,如何安全地通知到Qt的UI线程去更新界面?直接在线程中调用UI控件的方法是危险的,会导致程序崩溃。解决方案是使用Qt的线程间通信机制。
4. 集成关键:连接C++核心与Qt界面
这是整个项目的精髓所在。我们需要让DataProcessor的数据更新事件,安全地传递到Qt的主线程,从而更新图表、表格和标签。
4.1 创建适配器类:DataController
最佳实践是引入一个中间层——DataController类。它继承自QObject,因此可以拥有信号与槽。它内部持有一个DataProcessor实例,并管理其生命周期和线程。
// datacontroller.h #include <QObject> #include <memory> #include “dataprocessor.h” class DataController : public QObject { Q_OBJECT // 必须的宏,用于启用信号槽机制和元对象系统 public: explicit DataController(QObject *parent = nullptr); ~DataController(); public slots: // 供UI调用的槽函数 void startAcquisition(int intervalMs); void stopAcquisition(); void clearData(); signals: // 定义信号,通知UI更新 void dataUpdated(); // 有新数据 void statsUpdated(double current, double avg, double max, double min); // 统计信息更新 private slots: void onSimulationTimer(); // 定时器触发,用于轮询或触发数据生成 private: std::unique_ptr<DataProcessor> m_processor; QTimer *m_timer; // Qt的定时器,运行在主线程 // 使用QTimer在主线程中定期从DataProcessor获取数据,避免了复杂的线程同步。 // 另一种方式是让DataProcessor在独立线程运行,通过信号槽跨线程通信,稍复杂但更实时。 };在DataController的实现中,我们使用一个QTimer。QTimer的超时信号timeout()连接到DataController的私有槽onSimulationTimer()。在这个槽函数中,我们调用DataProcessor::generateNextData(),然后发射自定义的dataUpdated()和statsUpdated()信号。
// datacontroller.cpp 关键部分 void DataController::startAcquisition(int intervalMs) { if (m_timer->isActive()) { m_timer->stop(); } m_processor->startSimulation(intervalMs); // 启动后台线程(如果采用线程方案) // 本例采用主线程定时器方案,简化线程管理 m_timer->start(intervalMs); } void DataController::onSimulationTimer() { if (!m_processor) return; // 生成数据 auto newPoint = m_processor->generateNextData(); // 获取最新统计 double current = m_processor->getCurrentValue(); double avg = m_processor->getAverageValue(); double max = m_processor->getMaxValue(); double min = m_processor->getMinValue(); // 发出信号,触发UI更新 emit dataUpdated(); emit statsUpdated(current, avg, max, min); }实操心得:在涉及多线程数据更新UI时,我强烈推荐“主线程定时器轮询”模式作为起点。它逻辑简单,避免了复杂的线程同步和跨线程信号槽连接(需要
Qt::QueuedConnection)。虽然实时性稍差(取决于定时器间隔),但对于大多数数据监控场景完全足够。当性能成为瓶颈时,再考虑更复杂的“工作线程+信号槽”模式。
4.2 实现模型层:DataModel
DataModel继承自QAbstractTableModel,它需要实现几个关键的虚函数,以告诉QTableView如何显示数据。
// datamodel.h #include <QAbstractTableModel> #include “dataprocessor.h” class DataModel : public QAbstractTableModel { Q_OBJECT public: explicit DataModel(QObject *parent = nullptr); void setDataProcessor(DataProcessor *processor); void refresh(); // 手动刷新视图 // QAbstractItemModel interface int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; private: DataProcessor *m_processor{nullptr}; };在data()函数中,我们将DataPoint的时间戳和数值转换为QVariant(Qt的通用数据容器)返回给视图。当DataController发出dataUpdated()信号时,我们可以连接到一个槽函数,调用DataModel的refresh()方法,该方法内部会调用beginInsertRows/endInsertRows或dataChanged等函数来通知视图更新。
4.3 构建用户界面与连接一切
现在,我们使用Qt Designer来设计主界面。打开mainwindow.ui文件,你可以通过拖拽的方式添加以下控件:
- 一个
QTableView(对象名设为tableView)。 - 一个
QChartView(需要先在.pro或CMakeLists.txt中添加charts模块)。 - 几个
QLabel,用于显示当前值、平均值等。 - 两个
QPushButton,命名为startButton和stopButton。 - 一个
QSlider,命名为intervalSlider,用于调整频率。 - 一个
QLCDNumber或另一个QLabel用于醒目显示当前值。
设计好界面后,回到mainwindow.cpp,在MainWindow的构造函数中,我们需要完成最后的“布线”工作:
// mainwindow.cpp 构造函数部分 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // 1. 创建核心对象 m_dataProcessor = std::make_unique<DataProcessor>(); m_dataController = new DataController(this); m_dataModel = new DataModel(this); // 2. 建立关联 m_dataModel->setDataProcessor(m_dataProcessor.get()); m_dataController->setDataProcessor(m_dataProcessor.get()); // 假设DataController也有此方法 // 3. 设置视图的模型 ui->tableView->setModel(m_dataModel); ui->tableView->horizontalHeader()->setStretchLastSection(true); // 美观设置 // 4. 初始化图表 setupChart(); // 5. 连接信号与槽 // UI控件 -> Controller connect(ui->startButton, &QPushButton::clicked, this, [this]() { int interval = ui->intervalSlider->value(); m_dataController->startAcquisition(interval); }); connect(ui->stopButton, &QPushButton::clicked, m_dataController, &DataController::stopAcquisition); connect(ui->intervalSlider, &QSlider::valueChanged, this, &MainWindow::onIntervalChanged); // Controller -> UI更新 connect(m_dataController, &DataController::dataUpdated, m_dataModel, &DataModel::refresh); connect(m_dataController, &DataController::dataUpdated, this, &MainWindow::updateChart); connect(m_dataController, &DataController::statsUpdated, this, &MainWindow::updateStatsDisplay); }在updateChart()函数中,我们从DataProcessor获取最新的历史数据,并更新QChart中的QLineSeries。在updateStatsDisplay()函数中,我们将接收到的统计值设置到对应的QLabel上。
至此,一个完整的C++核心逻辑与Qt界面集成的应用程序骨架就搭建完成了。数据流形成了一个闭环:用户点击按钮 -> Controller -> 触发Processor生成数据 -> Controller发出信号 -> Model和View(图表、标签)更新。
5. 高效编程技巧与深度优化
掌握了基本集成后,我们来探讨一些能显著提升开发效率和代码质量的高级技巧。
5.1 利用Qt Designer提升界面开发效率
- 提升自定义控件:你可以将一组常用的控件(例如,一个带有标签和单位的数值输入框)在Qt Designer中设计好,然后右键“提升为...”,创建一个自定义的C++控件类。这样可以在多个界面中复用,保持风格统一。
- 使用样式表(QSS):Qt的样式表类似于CSS,可以极大地美化界面。不要满足于默认的灰色窗口。通过
setStyleSheet方法,你可以轻松改变控件颜色、字体、边框、圆角等。为你的Demo设计一套简洁现代的深色或浅色主题。 - 布局管理:坚决使用布局管理器(
QHBoxLayout,QVBoxLayout,QGridLayout)而不是固定坐标(setGeometry)。布局管理器能自动处理控件大小和位置,当窗口缩放时,界面能自适应,这是专业桌面应用的基本要求。
5.2 内存管理与资源释放
C++要求手动管理内存,而Qt在QObject派生类中提供了基于对象树的自动内存管理机制。
QObject父子关系:在创建QObject派生类对象(如各种Widget、Controller)时,指定一个父对象(parent)。当父对象被销毁时,它会自动删除所有子对象。在MainWindow的构造函数中创建的对象,通常将this(即MainWindow实例)作为父对象。- 智能指针结合:对于非
QObject的纯C++类(如我们的DataProcessor),使用std::unique_ptr或std::shared_ptr来管理生命周期。在我们的Demo中,MainWindow持有std::unique_ptr<DataProcessor>,当MainWindow析构时,unique_ptr会自动释放DataProcessor。 - 注意循环引用:如果两个
QObject相互引用,即使没有父对象关系,也可能因为Qt的元对象系统导致无法正确释放。使用QPointer(一个弱引用指针)可以安全地解决这类问题。
5.3 性能考量:数据频繁更新下的优化
我们的Demo涉及实时数据更新,性能优化很重要。
图表优化:
QChart在动态添加大量数据点时性能会下降。- 限制数据点数量:就像我们在
DataProcessor里做的,只保留最近N个点。 - 使用
QLineSeries::replace():与其频繁调用append(),不如在数据量固定时,直接替换整个序列的数据,然后调用chart->update()。 - 关闭动画:
chart->setAnimationOptions(QChart::NoAnimation);。 - 减少刷新频率:并非每次有新数据都要立刻更新图表。可以设置一个阈值或定时器,每积累一定数量的点或固定时间间隔再更新一次视图。
- 限制数据点数量:就像我们在
模型/视图优化:当表格数据行数很多时,滚动可能会卡顿。
- 在
DataModel::data()函数中,确保你的数据获取操作是O(1)复杂度的。避免在data()内部进行复杂的计算或遍历。 - 对于只读的大量数据,考虑使用
QAbstractItemModel的fetchMore机制实现懒加载。
- 在
多线程的进阶使用:如果数据处理非常耗时(例如复杂的图像处理或数值计算),必须放在后台线程,否则会阻塞UI,导致界面“假死”。
- 使用
QtConcurrent:对于独立的、无状态的函数式任务,QtConcurrent::run是最简单的选择。 - 使用
QThread和Worker对象:更经典的模式是创建一个继承自QObject的工作者类,将其moveToThread到一个QThread中。工作者对象的所有槽函数将在新线程中执行。主线程通过信号触发工作者开始工作,工作者完成后通过信号将结果传回主线程更新UI。 - 关键规则:永远不要在任何非主线程中直接访问或修改UI控件。所有对UI的更新请求都必须通过信号槽排队到主线程执行。
- 使用
6. 常见问题排查与调试心得
即使按照最佳实践开发,也难免会遇到问题。以下是我在多年Qt/C++开发中积累的一些常见问题排查清单。
6.1 编译与链接问题
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
undefined reference to ‘vtable for ClassName’ | 1. 该类继承自QObject,但头文件中忘了写Q_OBJECT宏。2. 没有在 CMakeLists.txt或.pro文件中添加对应的源文件。 | 1. 检查头文件,确保在类声明开头有Q_OBJECT。2. 运行 qmake(如果是qmake项目)或清理构建目录后重新CMake。 |
cannot find -lQt5Core(或类似) | Qt库路径没有正确链接。 | 检查Qt Creator中的Kit配置,确保选择了正确的Qt版本。在CMakeLists.txt中,确保find_package(Qt6 COMPONENTS Core Widgets Charts REQUIRED)已正确执行。 |
error: C1083: Cannot open include file: ‘ui_mainwindow.h’ | UI文件编译后生成的头文件缺失。 | 确保mainwindow.ui文件存在,并且构建系统已正确配置。对于CMake,需要使用qt_add_executable和qt_wrap_ui(旧版)或自动处理UI文件。Qt Creator新建的项目通常已配置好。 |
6.2 运行时问题
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 程序启动立即崩溃 | 1. 访问空指针(nullptr)。 2. 在对象构造完成前调用了虚函数。 3. 动态库加载失败(DLL Hell)。 | 1. 使用调试器(如GDB或VS Debugger)查看崩溃堆栈,定位到具体代码行。 2. 检查构造函数初始化列表的顺序,确保成员变量先于可能使用它们的代码被初始化。 3. 将程序依赖的Qt DLL(如 Qt6Core.dll,Qt6Widgets.dll)拷贝到可执行文件同级目录,或确保系统PATH包含Qt的bin目录。 |
| 界面显示不正常,控件错位或看不见 | 1. 忘记调用ui->setupUi(this)。2. 没有为窗口中央部件设置布局管理器。 3. 样式表(QSS)语法错误或覆盖了重要属性。 | 1. 检查MainWindow构造函数。2. 在Qt Designer中,确保顶层窗口有布局(右键窗口空白处->布局)。 3. 暂时注释掉样式表代码,看是否恢复正常。使用 QSS语法检查工具。 |
| 点击按钮无反应 | 1. 信号与槽连接失败。 2. 按钮被禁用( setEnabled(false))。3. 事件被父控件拦截。 | 1.使用运行时检查:connect函数可以连接到一个Lambda,在Lambda里打印日志,确认是否触发。检查信号槽的签名(参数类型)是否完全匹配。对于重载的信号,需要使用QOverload进行转换,例如QOverload<int>::of(&QSlider::valueChanged)。2. 检查按钮属性。 |
| 多线程下程序随机崩溃 | 从非主线程直接访问了UI控件。 | 严格遵守“只在主线程操作UI”的原则。所有从工作线程到UI的通信都必须通过信号槽(连接类型为Qt::QueuedConnection,这是跨线程连接的默认行为)。使用QMetaObject::invokeMethod也可以将调用排队到主线程。 |
6.3 调试技巧
- 充分利用Qt Creator的调试器:设置断点、观察变量、查看调用堆栈是最基本的能力。对于Qt对象,调试器可以展开显示其丰富的属性。
- 使用
qDebug()进行日志输出:#include <QDebug>,然后使用qDebug() << “Value:” << someValue;。它线程安全,并且会自动输出到Qt Creator的“应用程序输出”面板。比std::cout更方便。 - 检查信号槽连接:在
connect调用后,可以检查其返回值(一个QMetaObject::Connection对象)。虽然很少失败,但在动态连接时检查一下是好的习惯。更实用的方法是在连接的槽函数里第一时间加日志。 - 内存泄漏检查:在Linux/macOS下,可以使用Valgrind。在Windows下,Visual Studio的诊断工具非常强大。Qt本身在调试模式下也会在程序退出时输出未释放的
QObject对象列表,这是一个很好的起点。
这个Demo项目虽然不大,但涵盖了C++与Qt高效集成的核心模式:清晰的层次分离、信号槽解耦、模型/视图架构、以及基本的线程安全考量。你可以以此为基础,扩展更复杂的功能,例如添加数据库存储(Qt SQL)、网络通信(Qt Network)、或者更炫酷的3D展示(Qt Data Visualization)。记住,好的架构是成功的一半,而Qt提供的这套工具链,能让你的C++桌面应用开发事半功倍。