C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > Qt 6 跨平台

Qt 6 跨平台开发完全指南

作者:翎_鸢

本文详解Qt6最新LTS版本特性、环境搭建、信号槽机制与多线程避坑指南,帮你快速上手C++跨平台桌面开发,具有一定的参考价值,感兴趣的额可以了解一下

一、背景:为什么Qt仍是C++ GUI开发的首选

1.1 Qt 的历史

Qt 诞生于 1991 年,由挪威公司 Trolltech(奇趣科技)开发。此后经历了几次重要的"东家"变更:

时间事件
1991Qt 诞生于 Trolltech,最初面向跨平台 GUI 开发
2008被 Nokia 收购,Qt 进入移动互联网时代(Symbian、Maemo)
2012被 Digia 收购,Qt 转向桌面与嵌入式双主线
2014成立独立的 The Qt Company,Qt 5 成为绝对主流
2020Qt 6 正式发布,全面重构底层架构
2024Qt 6.8 LTS 发布,成为新一代长期支持版本

三十多年过去,Qt 从一个小众的 GUI 框架成长为覆盖桌面、嵌入式、移动端、汽车座舱的全栈跨平台框架,是 C++ 生态中最具生命力的 UI 框架之一。

1.2 为什么选择 Qt

对比常见的 C++ GUI 方案,Qt 的优势非常明显:

方案跨平台能力开发效率社区与生态商业授权成本
QtWindows/Linux/macOS/嵌入式/移动端全覆盖高(信号槽、QSS、Designer)大而活跃LGPL 免费,商业授权可选
wxWidgets桌面三平台中(纯 C++,无元对象系统)中宽松免费
GTK+以 Linux 为主中(C 语言为主)中LGPL 免费
Dear ImGui依赖第三方后端极高(仅限工具类 UI)大MIT 免费
Win32/MFC仅 Windows低萎缩免费

Qt 的核心竞争力可以概括为三句话:

  1. 一套代码,多端编译:源码级跨平台,编译到哪个平台就生成哪个平台的原生程序,无需 WebView 壳。
  2. 信号槽机制:用对象间解耦的事件通信替代传统回调,是 Qt 最深入骨髓的设计。
  3. 生态完备:官方提供网络、数据库、多媒体、3D、图表、蓝牙等模块,基本开箱即用。

1.3 开源协议要点

注意:Qt 6 的某些模块(如 Qt Charts、Qt Data Visualization)在 LGPL 下是付费的,开源版不可用,商用需购买商业授权。

二、环境搭建与使用方式

2.1 安装 Qt

推荐使用官方在线安装器:https://www.qt.io/download-open-source

安装时选择组件:

2.2 构建系统:qmake 已退场,CMake 是标准

Qt 5 时代 qmake 是主流,Qt 6 全面转向 CMake:

对比项qmakeCMake
官方支持Qt 6 中已不再演进Qt 6 默认推荐
语法简单但有局限复杂但功能强
生态仅 Qt 项目使用整个 C++ 社区通用
IDE 支持一般VS Code / CLion / VS 全部支持

CMake 构建一个 Qt 6 项目的最小配置:

cmake_minimum_required(VERSION 3.16)
project(HelloQt VERSION 1.0 LANGUAGES CXX)
 
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)   # 自动处理 Q_OBJECT 元对象编译
 
find_package(Qt6 REQUIRED COMPONENTS Widgets)
qt_standard_project_setup()
 
qt_add_executable(HelloQt
    main.cpp
    MainWindow.cpp
    MainWindow.h
)
 
target_link_libraries(HelloQt PRIVATE Qt6::Widgets)

关键点:

2.3 第一个 Qt 程序

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QLabel label("Hello, Qt 6!");
    label.resize(320, 120);
    label.show();

    return app.exec();
}

程序执行流程:

  1. QApplication 初始化 Qt 运行环境(事件循环、资源系统、平台插件)。
  2. 创建控件并 show()。
  3. app.exec() 进入事件循环,阻塞直到窗口关闭。

2.4 工程中的三个"隐形"工具

Qt 项目背后有四个编译期工具,理解它们才能看懂 Qt 的构建过程:

工具全称作用
mocMeta-Object Compiler解析 Q_OBJECT,生成元对象代码(信号槽、属性、类型信息)
uicUser Interface Compiler把 .ui 文件编译为 C++ 代码
rccResource Compiler把资源文件(图片、qss、qml)编译为二进制
qmake/qtpaths构建与路径工具生成 Makefile、查询安装路径

三、核心API详解

3.1 QObject:Qt 对象体系的基石

QObject 是所有 Qt 对象(包括所有控件)的基类,它提供了:

一个自定义 QObject 子类:

#include <QObject>
#include <QDebug>

class Counter : public QObject
{
    Q_OBJECT
public:
    explicit Counter(QObject *parent = nullptr) : QObject(parent), m_value(0) {}

    int value() const { return m_value; }

public slots:
    void setValue(int v)
    {
        if (v != m_value) {
            m_value = v;
            emit valueChanged(v);   // 发射信号
        }
    }

signals:
    void valueChanged(int newValue);

private:
    int m_value;
};

凡是使用了 Q_OBJECT 宏的类,头文件必须经过 moc 处理(CMake 中 AUTOMOC ON 即为此)。

3.2 信号槽机制:Qt 的灵魂

信号槽本质是观察者模式的类型安全实现。Qt 6 推荐使用函数指针新语法:

// 新语法(编译期检查,推荐)
connect(&counter, &Counter::valueChanged,
        &label, &QLabel::setNum);

// Lambda 写法
connect(&counter, &Counter::valueChanged, this, [](int v) {
    qDebug() << "new value:" << v;
});

// 老式字符串语法(运行时解析,不推荐,易因拼写错误静默失败)
connect(&counter, SIGNAL(valueChanged(int)),
        &label, SLOT(setNum(int)));

信号槽的连接规则:

跨线程信号槽注意:跨线程连接时,参数必须能通过 Qt 元类型系统复制(注册自定义类型用 Q_DECLARE_METATYPE + qRegisterMetaType),否则队列连接会失败。

3.3 常用核心类

类用途典型 API
QString不可变 Unicode 字符串arg()、toInt()、split()、toStdString()
QByteArray字节数组,网络/文件操作常用fromHex()、toBase64()、data()
QVariant万能容器,可存任意注册类型toInt()、toString()、canConvert()
QList<T>动态数组(Qt 6 中 QVector 并入)append()、at()、removeAt()
QMap<K,V>有序键值表insert()、value()、keys()
QHash<K,V>哈希表,查找更快insert()、value()、contains()
QTimer定时器start()、setInterval()、timeout 信号
QFile文件读写open()、readAll()、write()
QSettings配置文件(注册表/ini/plist)setValue()、value()

QString 格式化示例:

QString name = "Alice";
int score = 98;
QString msg = QString("%1 的成绩是 %2 分").arg(name).arg(score);
// 输出:Alice 的成绩是 98 分

3.4 窗口体系

类说明
QWidget所有控件的基类,空窗口
QMainWindow主窗口,内置菜单栏、工具栏、状态栏、停靠区
QDialog对话框基类,模态/非模态
QLabel / QPushButton / QLineEdit基础控件
QTableWidget / QTreeWidget便捷型表格/树(基于 Model 但内置数据)
QTableView / QListView模型视图架构下的视图组件

一个主窗口骨架:

#include <QMainWindow>
#include <QMenuBar>
#include <QStatusBar>
#include <QTextEdit>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr)
        : QMainWindow(parent)
    {
        auto *editor = new QTextEdit(this);
        setCentralWidget(editor);                    // 中央控件

        auto *fileMenu = menuBar()->addMenu("文件"); // 菜单栏
        fileMenu->addAction("退出", this, &QWidget::close);

        statusBar()->showMessage("就绪");            // 状态栏
        setWindowTitle("文档编辑器");
        resize(800, 600);
    }
};

3.5 布局管理

布局是 Qt 的自动排版方案,控件随窗口缩放自动调整:

#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QLineEdit>

auto *edit = new QLineEdit;
auto *btn  = new QPushButton("发送");
auto *list = new QListWidget;

auto *topLayout = new QHBoxLayout;   // 水平:输入框 + 按钮
topLayout->addWidget(edit, 1);       // stretch=1,输入框占据剩余空间
topLayout->addWidget(btn);

auto *mainLayout = new QVBoxLayout;  // 垂直:顶部一行 + 列表
mainLayout->addLayout(topLayout);
mainLayout->addWidget(list, 1);

auto *window = new QWidget;
window->setLayout(mainLayout);
window->show();

布局建议:

3.6 事件系统

Qt 事件与信号槽是两套机制:信号槽用于对象间通信,事件系统处理来自系统的事件(鼠标、键盘、绘制、定时器)。

bool MainWindow::eventFilter(QObject *watched, QEvent *event) override
{
    if (watched == m_input && event->type() == QEvent::KeyPress) {
        auto *keyEvent = static_cast<QKeyEvent *>(event);
        if (keyEvent->key() == Qt::Key_Return) {
            onSendClicked();          // 拦截回车键
            return true;              // 事件被消费
        }
    }
    return QMainWindow::eventFilter(watched, event);
}

安装事件过滤器:m_input->installEventFilter(this);

3.7 多线程

Qt 多线程有三种主流方式:

方式一:继承 QThread(不推荐重写 run 的旧写法)

class Worker : public QThread
{
    Q_OBJECT
protected:
    void run() override
    {
        // 耗时的计算任务
        emit progress(42);
    }
signals:
    void progress(int percent);
};

方式二:Worker 对象 + moveToThread(推荐)

auto *thread = new QThread;
auto *worker = new Worker;                 // 普通 QObject

worker->moveToThread(thread);              // 把 worker 移到子线程
connect(thread, &QThread::started, worker, &Worker::doWork);
connect(worker, &Worker::finished, thread, &QThread::quit);
thread->start();

方式三:QtConcurrent 并行计算(最省事)

#include <QtConcurrent>
#include <QFutureWatcher>

QFuture<int> future = QtConcurrent::run([this] {
    return heavyCompute();                 // 自动在线程池中执行
});
QFutureWatcher<int> *watcher = new QFutureWatcher<int>(this);
connect(watcher, &QFutureWatcher<int>::finished, this, [watcher] {
    qDebug() << "结果:" << watcher->result();
});
watcher->setFuture(future);

铁律:永远不要在子线程中直接操作 UI 控件,必须通过信号槽(队列连接)或 QMetaObject::invokeMethod 回到主线程更新界面。

3.8 网络编程

#include <QNetworkAccessManager>
#include <QNetworkReply>

QNetworkAccessManager *manager = new QNetworkAccessManager(this);

QNetworkRequest request(QUrl("https://api.example.com/data"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

QNetworkReply *reply = manager->post(request, QByteArray("{\"q\":\"qt\"}"));

connect(reply, &QNetworkReply::finished, this, [reply] {
    if (reply->error() == QNetworkReply::NoError) {
        qDebug() << reply->readAll();      // 读取响应体
    } else {
        qDebug() << "请求失败:" << reply->errorString();
    }
    reply->deleteLater();                  // 必须释放
});

注意事项:QNetworkReply 使用后必须 deleteLater(),否则内存泄漏;QNetworkAccessManager 整个应用通常只创建一个实例。

3.9 模型视图架构

Qt 用 Model/View 架构把数据与展示解耦,适合表格、列表、树:

class StringListModel : public QAbstractListModel
{
    Q_OBJECT
public:
    int rowCount(const QModelIndex &parent = QModelIndex()) const override
    {
        return parent.isValid() ? 0 : m_data.size();
    }

    QVariant data(const QModelIndex &index, int role) const override
    {
        if (!index.isValid() || role != Qt::DisplayRole)
            return QVariant();
        return m_data.at(index.row());
    }

private:
    QStringList m_data;
};

使用:

QListView *view = new QListView;
StringListModel *model = new StringListModel;
view->setModel(model);

数据量小用 QListWidget/QTableWidget 便捷类即可;数据量大、需要排序筛选时务必上 Model/View。

四、典型使用场景

4.1 桌面应用开发

Qt 最成熟的领域。代表作:

软件说明
Wireshark抓包分析工具,Qt 界面
VirtualBox虚拟机管理界面
OBS Studio直播推流软件(Qt 6 重构)
VLC 部分组件媒体播放
Telegram 桌面版即时通讯

4.2 工业控制与仪器仪表

工业场景要求稳定、离线、跨平台,Qt 是 HMI(人机界面)主流选择:

4.3 汽车智能座舱

Qt 在车载领域地位特殊:Qt for MCU、Qt Safe Renderer、AUTOSAR 集成方案,几乎成了汽车 HMI 的事实标准。主流车厂的仪表盘、中控屏大量使用 QML/Qt Quick 开发。

4.4 嵌入式与 IoT

4.5 跨语言与移动端

4.6 场景选择建议

需求推荐技术栈
经典桌面工具、控件密集C++ + Widgets
视觉复杂、动效丰富C++/Python + QML
快速原型、脚本化PySide6 + Widgets/QML
嵌入式触控屏Qt for Embedded / Qt for MCU
汽车仪表/座舱Qt for Device Creation + Safe Renderer

五、常见问题与避坑指南

5.1 中文乱码

现象:界面中文显示为问号或乱码。

原因与解决:

  1. 源文件编码问题:Qt 6 默认 UTF-8,源码文件必须保存为 UTF-8(带 BOM 会更好),VS 中注意"另存为 → UTF-8 带签名"。
  2. 字符串字面量:QString("中文") 在源码为 UTF-8 时正常;不要使用 tr() 之外的编码转换。
  3. 老项目(MSVC 环境)可用 #pragma execution_character_set("utf-8") 或 QString::fromUtf8()。

5.2 信号槽没反应

排查步骤:

  1. 类是否写了 Q_OBJECT?没有则信号槽无法工作。
  2. 用新语法 connect 是否编译报错?若编译通过但无反应,检查对象是否被提前销毁。
  3. 信号是否真的发出来了?临时在 emit 处加 qDebug()。
  4. 跨线程连接时参数类型是否注册了元类型?未注册则队列连接静默失败(控制台会打印警告)。
  5. connect 的 context 对象是否还活着?lambda 的 context 对象销毁后连接自动断开。

5.3 程序启动崩溃:窗口一闪而过

原因:栈上创建的 QWidget 在 exec() 之前被析构。

// 错误:widget 是栈对象,main 返回前就被销毁
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QWidget w;          // 栈对象
    w.show();
    return app.exec();  // 如果 show 后立刻被销毁就会闪退
}

解决:用 new 创建并 setAttribute(Qt::WA_DeleteOnClose),或用 QPointer 持有指针。

5.4 对象被提前删除导致野指针

场景:父对象析构自动删除子对象,外部仍持有子对象指针。

解决:使用 QPointer<T> 智能指针,对象被销毁后自动置空:

QPointer<QLabel> label = new QLabel;
delete label;          // 别处删除了 label
if (label) {           // QPointer 已自动置空,安全
    label->setText("...");
}

5.5 高 DPI 下界面模糊

Qt 6 默认启用高 DPI 缩放,但常见问题:

// 启用高 DPI 支持(Qt 6 默认开启,无需手动设置)
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
    Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);

5.6 子线程更新 UI 崩溃

原因:直接跨线程调用 UI 方法,破坏事件循环。

解决:

5.7 内存泄漏

Qt 对象树能释放子对象,但常见的坑:

排查工具:MSVC 下用 VLD(Visual Leak Detector),Linux 下用 valgrind,Qt 自带 QObject 泄漏检测可开启 Q_COREAPP_STARTUP_FUNCTION。

5.8 程序部署与打包

平台工具命令示例
Windowswindeployqtwindeployqt --release --no-translations MyApp.exe
Linuxlinuxdeployqt / AppImagelinuxdeployqt AppDir/usr/bin/MyApp -appimage
macOSmacdeployqtmacdeployqt MyApp.app -dmg

打包要点:

5.9 编译报错找不到 Qt 头文件

原因:CMake 未找到 Qt 路径。

解决:

# Windows 下指定 Qt 安装路径
cmake -DCMAKE_PREFIX_PATH=C:/Qt/6.8.0/msvc2022_64 ..

或设置环境变量 CMAKE_PREFIX_PATH。

5.10 Q_OBJECT 相关编译错误(moc 失败)

现象:undefined reference to vtable 或 No rule to make target ...moc.cpp。

解决:

六、FAQ速查表

问题一句话答案
Qt 6 和 Qt 5 怎么选?新项目选 Qt 6(当前最新 LTS 为 6.8),维护老项目用 Qt 5.15
qmake 还能用吗?能,但 Qt 6 官方已转向 CMake,新项目用 CMake
信号槽连接没反应?先查 Q_OBJECT、对象生命周期、跨线程元类型注册
子线程能操作 UI 吗?绝对不能,用信号槽回主线程
中文字符串乱码?源码保存为 UTF-8,Qt 6 默认 UTF-8
界面在高 DPI 下模糊?Qt 6 默认高 DPI 缩放,图片资源准备多倍图
如何给按钮加点击事件?connect(btn, &QPushButton::clicked, this, &Class::slot)
如何定时执行任务?QTimer + 超时信号
如何读写配置文件?QSettings 一行搞定
程序怎么打包发布?Windows 用 windeployqt,Linux 用 linuxdeployqt,macOS 用 macdeployqt
Qt 商用收费吗?LGPL 免费(动态链接),静态链接/商用模块需购买商业授权
QML 和 Widgets 怎么选?桌面工具类选 Widgets,动效复杂选 QML
如何监听键盘/鼠标全局事件?事件过滤器 installEventFilter
QNetworkReply 需要释放吗?需要,deleteLater()
程序启动窗口闪退?检查栈对象生命周期,控件用 new + DeleteOnClose

结语

Qt 6 已经是一个非常成熟、稳定的跨平台框架。它的学习曲线不算平缓(元对象系统、信号槽、事件循环是三道门槛),但只要理解了这三个核心概念,后续的开发就是查文档与堆代码的快乐过程。

建议的学习路径:

  1. 掌握 QObject、信号槽、事件循环三大概念。
  2. 用 Widgets 写一个完整的小工具(如待办清单、简易记事本)。
  3. 了解 Model/View 与多线程,应对真实项目。
  4. 按需学习 QML / 网络 / 数据库 / 图表模块。

最后送一句实践心得:先理解对象生命周期,再写业务代码——Qt 程序中 80% 的崩溃都源于对对象所有权理解不到位。

到此这篇关于Qt 6 跨平台开发完全指南的文章就介绍到这了,更多相关Qt 6 跨平台内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文