https://www.qt.io/ QT官网
跨平台的C++图形用户界面应用程序框架,Qt的本质就是一个C++类库。
QT概述
Qt 是一个跨平台的 C++ 应用程序开发框架,由挪威 Trolltech 公司(后被 Nokia 收购,现为 The Qt Company 所有)开发,旨在简化桌面、移动、嵌入式等多平台应用的开发。它不仅提供了丰富的 GUI 组件,还包含了网络、数据库、多媒体、绘图等一系列功能模块,是开发高效、美观的跨平台应用的重要工具。
一、Qt 的核心优势
跨平台性
一次编写,多平台运行。Qt 支持 Windows、macOS、Linux 等桌面系统,iOS、Android 等移动平台,以及嵌入式系统(如嵌入式 Linux、QNX 等),无需大量修改代码即可适配不同平台。信号与槽机制(Signals & Slots)
这是 Qt 最具特色的通信机制,用于对象间的解耦交互(如按钮点击触发函数、数据变化通知等),比传统的回调函数更灵活(之前已详细介绍)。元对象系统(Meta-Object System)
基于QObject
、Q_OBJECT
宏和元对象编译器(moc),支持动态类型识别、反射、信号槽等核心功能(之前已详细介绍)。丰富的 UI 组件
提供大量现成的界面控件(按钮、文本框、表格、树状视图等),且支持自定义控件,同时内置样式表(QSS)可轻松美化界面,类似 CSS。模块化设计
功能按模块划分,按需使用,核心模块包括:Qt Core
:核心功能(元对象、容器、事件等);Qt GUI
:图形相关(绘图、字体、颜色等);Qt Widgets
:传统桌面 UI 组件;Qt QML
:用于开发现代、流畅的界面(适合移动和嵌入式);Qt Network
:网络通信(TCP/UDP、HTTP 等);Qt Sql
:数据库操作;Qt Multimedia
:音视频处理等。
Qt的第一个程序
文件类型 | 示例文件名 | 作用说明 |
---|---|---|
.pro 文件 |
MyProject.pro |
项目配置文件(Qt 的 “工程文件”),用于告诉 Qt 构建系统项目的基本信息: - 依赖的 Qt 模块(如 core 、gui 、widgets )- 源文件( .cpp )、头文件(.h )、UI 文件(.ui )的列表- 编译选项(如 C++ 标准、宏定义) - 部署路径等。 是项目的 “入口”,必须保留。 |
主程序入口 | main.cpp |
程序的入口函数(main 函数)所在文件,负责:- 创建 QApplication 实例(Qt 应用程序的核心对象,管理事件循环)- 创建主窗口对象(如 MainWindow 或Widget )- 显示主窗口 - 启动应用程序的事件循环( a.exec() )。 |
窗口类头文件 | mainwindow.h 或 widget.h |
主窗口类(如MainWindow )的声明文件,包含:- 类的继承关系(通常继承自 QMainWindow 或QWidget )- 成员变量(如界面控件的指针、业务数据) - 成员函数(如槽函数、自定义方法)的声明 - Q_OBJECT 宏(启用元对象功能,支持信号与槽)。 |
窗口类源文件 | mainwindow.cpp 或 widget.cpp |
主窗口类的实现文件,包含: - 构造函数(初始化窗口、调用 ui->setupUi(this) 关联 UI 文件)- 析构函数(释放 ui 指针)- 槽函数、自定义方法的具体实现逻辑。 |
UI 设计文件 | mainwindow.ui 或 widget.ui |
由 Qt Designer 可视化设计的界面文件,本质是 XML 格式,描述界面中的控件(如按钮、标签、输入框)及其属性(位置、大小、文本等)。 编译时会被 Qt 的 UI 编译器( uic )自动转换为ui_mainwindow.h 头文件(存储在构建目录),供mainwindow.cpp 调用。 |
*.pro文件
# Qt项目配置文件
# 添加Qt核心模块和GUI模块
QT += core gui
# 如果Qt主版本大于4,则添加widgets模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
# 启用C++11标准支持
CONFIG += c++11
# 以下定义让编译器在使用已被标记为弃用的Qt特性时发出警告
# (具体警告内容取决于你的编译器)。请查阅已弃用API的文档
# 以了解如何迁移你的代码。
DEFINES += QT_DEPRECATED_WARNINGS
# 你也可以让代码在使用已弃用API时编译失败。
# 为此,请取消注释以下行。
# 你还可以选择仅禁用特定Qt版本之前已弃用的API。
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # 禁用Qt 6.0.0之前所有已弃用的API
# 添加源文件列表
SOURCES += \
main.cpp \ # 主程序入口文件
widget.cpp # 自定义窗口部件实现文件
# 添加头文件列表
HEADERS += \
widget.h # 自定义窗口部件头文件
# 添加UI表单文件列表
FORMS += \
widget.ui # 自定义窗口部件的Qt Designer界面文件
# 默认的部署规则
# QNX系统下的目标部署路径
qnx: target.path = /tmp/$${TARGET}/bin
# Unix系统(非Android)下的目标部署路径
else: unix:!android: target.path = /opt/$${TARGET}/bin
# 如果目标路径不为空,则添加到安装列表
!isEmpty(target.path): INSTALLS += target
widget.h
#ifndef WIDGET_H // 防止头文件被重复包含的预处理指令
#define WIDGET_H // 定义头文件保护宏
#include <QWidget> // 包含Qt窗口部件基类
QT_BEGIN_NAMESPACE // 开始Qt命名空间声明
namespace Ui { class Widget; } // 前向声明Ui命名空间中的Widget类
QT_END_NAMESPACE // 结束Qt命名空间声明
class Widget : public QWidget // 定义Widget类,继承自QWidget
{
Q_OBJECT // Qt元对象系统宏,启用信号槽机制
public:
Widget(QWidget *parent = nullptr); // 构造函数,参数为父窗口指针
~Widget(); // 析构函数
private:
Ui::Widget *ui; // 指向UI界面类的指针
};
#endif // WIDGET_H // 结束头文件保护宏
widget.cpp
#include "widget.h" // 包含Widget类的头文件,声明了Widget类的成员变量和成员函数
#include "ui_widget.h" // 包含UI设计文件生成的头文件,定义了界面元素
// Widget类的构造函数,parent参数指定父窗口,默认为nullptr
Widget::Widget(QWidget *parent)
: QWidget(parent) // 初始化列表,调用父类QWidget的构造函数,传入父窗口指针
, ui(new Ui::Widget) // 初始化列表,创建Ui::Widget对象,并赋值给ui指针
{
ui->setupUi(this); // 调用UI对象的setupUi方法,将界面元素应用到当前窗口
}
// Widget类的析构函数,用于释放资源
Widget::~Widget()
{
delete ui; // 释放ui指针指向的UI对象,防止内存泄漏
}
main.cpp
#include "widget.h" // 包含自定义窗口部件头文件
#include <QApplication> // 包含Qt应用程序类头文件
int main(int argc, char *argv[]) // 程序主入口函数
{
QApplication a(argc, argv); // 创建Qt应用程序对象,初始化应用程序
Widget w; // 创建自定义窗口部件对象
w.show(); // 显示窗口部件
return a.exec(); // 进入Qt事件循环,等待用户交互
}
元对象系统
在 Qt 框架中,元对象系统(Meta-Object System) 是一套为 C++ 语言扩展动态特性的机制,它弥补了 C++ 在反射、动态类型识别等方面的不足,是 Qt 信号与槽、属性系统、动态_cast 等核心功能的基础。
元对象系统的 3 个核心组成部分
组成部分 | 核心作用 | 主要特点 |
---|---|---|
QObject 类 | 元对象系统的基类,所有需要元对象功能的类必须直接 / 间接继承它 | 1. 提供metaObject() 方法获取元对象2. 支持对象树管理(父子对象生命周期关联) 3. 提供 connect() 等信号槽连接接口 |
Q_OBJECT 宏 | 声明类需要启用元对象功能,触发 moc(元对象编译器)处理 | 1. 必须放在类的私有成员区域 2. 启用信号、槽、动态属性、元信息等功能 3. 若类未声明此宏,将无法使用信号槽等核心功能 |
元对象编译器(moc) | 预处理包含Q_OBJECT 宏的类,自动生成元对象代码(如moc_xxx.cpp 文件) |
1. 自动运行(集成在 Qt 构建流程中) 2. 生成信号槽的底层实现、类元信息(类名、方法列表等) 3. 开发者无需手动编写生成的代码 |
1.QObject 类
所有要使用元对象功能的类,必须直接或间接继承自 QObject
(Qt 中几乎所有核心类,如 QWidget
、QPushButton
、QTimer
等,都默认继承自 QObject
)。
QObject
提供了元对象系统的 “基础接口”,例如:
metaObject()
:返回当前对象的 “元对象”(QMetaObject
类型),通过它可访问类的动态信息;
qobject_cast<T>()
:基于元对象信息的安全类型转换(类似 C++ 的 dynamic_cast
,但更高效且不依赖编译器 RTTI 开关);
setProperty()
/ property()
:动态设置 / 获取对象的属性。
QObject类是Qt框架中最基础的类之一,几乎所有Qt类都直接或间接继承自QObject类
Q_OBJECT 宏
在继承 QObject
的类的私有区域(private) 中,必须声明 Q_OBJECT
宏(哪怕类中暂时没有信号 / 槽,若要使用元对象功能,也建议加上)。
Q_OBJECT
的作用是:
- 告诉 Qt 编译器:“这个类需要启用元对象功能”,后续会由 MOC 工具处理;
- 隐式声明一些元对象相关的成员,例如
metaObject()
函数、qt_metacall()
函数(信号槽的底层调用入口)等。
⚠️ 注意:如果忘记加
Q_OBJECT
宏,可能会导致信号槽无法连接、qobject_cast
失败、动态属性无法使用等问题。
#define Q_OBJECT \
public: \
// 编译器警告控制:压入当前警告状态
QT_WARNING_PUSH \
// 禁止重写警告
Q_OBJECT_NO_OVERRIDE_WARNING \
// 静态元对象,存储类的元信息
static const QMetaObject staticMetaObject; \
// 虚函数:返回对象的元对象指针
virtual const QMetaObject *metaObject() const; \
// 虚函数:运行时类型转换
virtual void *qt_metacast(const char *); \
// 虚函数:处理元对象调用(信号、槽、属性)
virtual int qt_metacall(QMetaObject::Call, int, void **); \
// 国际化相关的函数宏
QT_TR_FUNCTIONS \
private: \
// 属性警告控制
Q_OBJECT_NO_ATTRIBUTES_WARNING \
// 隐藏的静态元对象调用函数
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
// 恢复之前的警告状态
QT_WARNING_POP \
// 私有信号标记结构体
struct QPrivateSignal {}; \
// 类注解宏
QT_ANNOTATE_CLASS(qt_qobject, "")
元对象编译器(moc,Meta-Object Compiler)
MOC 是 Qt 自带的一个 “预编译工具”,它会扫描项目中所有包含 Q_OBJECT
宏的头文件(或 cpp 文件),并为每个类生成一个额外的 .moc
后缀的 C++ 文件(例如 widget.moc
)。
这个 .moc
文件中包含了:
- 元对象信息的具体实现(如类名、父类名、信号 / 槽列表、属性列表等);
- 信号槽机制的底层代码(如
signal
函数的触发逻辑、qt_metacall
函数的调用分发)。
最终,Qt 的构建系统(如 qmake、CMake)会将 .moc
文件与项目其他代码一起编译,让元对象功能生效。
系统信号与槽
在 Qt 中,信号与槽(Signals & Slots) 是元对象系统提供的核心通信机制,用于实现对象间的松散耦合交互。它解决了传统回调函数的紧耦合问题,让对象之间可以灵活通信而无需知道彼此的具体实现。
一、基本概念
二、核心用法:连接信号与槽
通过QObject::connect()
函数建立信号与槽的关联,语法如下:
connect(
发送者对象指针, // 发出信号的对象(QObject*)
&发送者类名::信号名, // 要发送的信号(函数指针)
接收者对象指针, // 接收信号的对象(QObject*)
&接收者类名::槽名 // 处理信号的槽(函数指针)
);
系统信号与槽(Qt 内置组件)
#include <QPushButton>
#include <QWidget>
// 创建一个窗口和按钮
QWidget *window = new QWidget;
QPushButton *button = new QPushButton("关闭窗口", window);
// 连接:按钮的点击信号(clicked) -> 窗口的关闭槽(close)
connect(button, &QPushButton::clicked, window, &QWidget::close);
window->show();
- 当按钮被点击时,会自动发射
clicked
信号,触发窗口的close
槽,实现窗口关闭。
自定义信号与槽
类型 | 定义 | 特点 |
---|---|---|
信号(Signal) | 当对象状态发生特定变化时 “发射”(emit)的通知 | - 由signals 关键字声明(位于类的任何访问权限区域,通常放在开头)- 无需手动实现,由元对象编译器(moc)自动生成代码 - 本质是特殊的成员函数,返回值必须为 void - 可带参数,用于传递状态信息 |
槽(Slot) | 接收并处理信号的函数 | - 由slots 关键字声明(需指定访问权限:public slots /private slots /protected slots )- 需要手动实现(与普通成员函数类似) - 可像普通函数一样直接调用 - 返回值必须为 void ,参数需与所连接的信号兼容 |
#include <QObject>
#include <QDebug>
// 自定义发送者类
class Sender : public QObject {
Q_OBJECT // 必须声明Q_OBJECT宏
signals:
// 自定义信号:发送字符串消息
void messageSent(const QString &text);
};
// 自定义接收者类
class Receiver : public QObject {
Q_OBJECT
public slots:
// 自定义槽:处理消息
void onMessageReceived(const QString &text) {
qDebug() << "收到消息:" << text;
}
};
// 使用自定义信号与槽
int main() {
Sender sender;
Receiver receiver;
// 连接:sender的messageSent信号 -> receiver的onMessageReceived槽
connect(&sender, &Sender::messageSent, &receiver, &Receiver::onMessageReceived);
// 发射信号(触发通信)
emit sender.messageSent("Hello, 信号与槽!"); // 输出:"收到消息: Hello, 信号与槽!"
return 0;
}
信号与槽其实都是独立的函数,但是可以通过connect将信号与槽连接在一起,但是连接之后并不能调用槽函数,只有当信号被触发时,连接的槽函数才会自动调用,并将信号中的参数传递到连接的槽函数中。
一.类的基础要求
必须继承自
QObject
(直接或间接)- 信号与槽是 Qt 元对象系统的功能,而元对象系统仅对
QObject
派生类生效。 - 示例:
class MySender : public QObject { ... }
(正确);class MySender { ... }
(错误,无法使用信号槽)。
- 信号与槽是 Qt 元对象系统的功能,而元对象系统仅对
必须声明
Q_OBJECT
宏- 宏需放在类的私有成员区域(通常是类声明的开头),用于触发元对象编译器(moc)生成信号槽的底层代码。
- 遗漏此宏会导致:信号 / 槽无法被识别、
connect
函数报错、链接错误(如undefined reference to vtable for XXX
)。 - 示例:
class MySender : public QObject { Q_OBJECT // 必须添加,且位置在类声明的私有区域(默认私有) signals: void mySignal(); };
二、信号的声明规则
声明位置:必须在
signals:
关键字下signals
是 Qt 的特殊关键字(非 C++ 原生),用于标记信号,无需指定访问修饰符(默认是public
)。- 错误示例:在
public:
或private:
下声明信号(编译不报错,但 moc 可能无法正确处理)。
信号仅声明,无需实现
- 信号的函数体由 moc 自动生成(在
moc_xxx.cpp
中),手动实现会导致编译错误。 - 示例:
signals: void dataUpdated(int value); // 正确:仅声明 // void dataUpdated(int value) { ... } // 错误:不能有实现
- 信号的函数体由 moc 自动生成(在
返回值必须为
void
- 信号无法返回任何值(因信号是 “通知”,接收者可能有多个,返回值无意义)。
- 错误示例:
int dataUpdated(int value);
(编译报错)。
参数需与槽兼容
- 信号的参数列表可以多于或等于槽,但类型和顺序必须完全匹配(槽可忽略信号的尾部参数)。
- 示例:
- 信号:
void sendData(int a, QString b);
- 合法槽:
void receiveData(int a);
(忽略第二个参数)、void receiveData(int a, QString b);
(完全匹配) - 非法槽:
void receiveData(QString b, int a);
(参数顺序错误)、void receiveData(double a);
(类型不匹配)。
- 信号:
三、槽的声明规则
声明位置:必须在
slots
关键字下,并指定访问修饰符- 槽需用
public slots
、private slots
或protected slots
声明(控制槽的访问权限)。 - 错误示例:在
public:
下直接声明槽(编译不报错,但无法被元对象系统识别为槽)。
- 槽需用
槽需要手动实现
- 槽本质是普通成员函数,必须提供函数体(否则链接错误)。
- 示例:
public slots: void onDataReceived(int value) { // 正确:有实现 qDebug() << "Received:" << value; }
返回值必须为
void
- 与信号一致,槽的返回值只能是
void
(Qt 不处理槽的返回值)。
- 与信号一致,槽的返回值只能是
四、connect
函数的使用规则
参数顺序:发送者 → 信号 → 接收者 → 槽
- 错误示例:颠倒信号和槽的所属类(如
connect(&receiver, &Receiver::signal, &sender, &Sender::slot)
,逻辑上信号应属于发送者)。
- 错误示例:颠倒信号和槽的所属类(如
语法正确:使用函数指针形式(Qt5 及以上推荐)
- 推荐:
QObject::connect(sender, &Sender::signalName, receiver, &Receiver::slotName);
(类型安全,编译时检查)。 - 避免:Qt4 的
SIGNAL()
/SLOT()
宏(如connect(sender, SIGNAL(signal()), receiver, SLOT(slot()))
),无编译时检查,拼写错误仅在运行时暴露。
- 推荐:
确保对象指针有效
- 发送者和接收者必须是有效指针(非
nullptr
),否则connect
失败(返回false
)。 - 注意:若对象被销毁,Qt5 及以上会自动断开连接(避免野指针),但仍建议在对象销毁前手动
disconnect
(尤其跨线程场景)。
- 发送者和接收者必须是有效指针(非
五、其他关键细节
类定义必须放在
.h
头文件中- 包含
Q_OBJECT
宏的类若定义在.cpp
文件中,moc 可能无法扫描到,导致元对象代码生成失败(链接错误)。 - 解决:将类声明移至
.h
头文件,并在.pro
的HEADERS
中添加该文件。
- 包含
信号发射需用
emit
关键字emit
是 Qt 的宏(实际编译时会被忽略),用于标记信号发射,提高代码可读性。- 示例:
emit dataUpdated(100);
(正确);dataUpdated(100);
(语法允许,但不推荐)。
避免信号 / 槽名与 Qt 关键字冲突
- 信号或槽的名称若与 Qt 内部关键字(如
signal
、slot
)重复,可能导致编译错误。 - 示例:避免声明
void signal();
或void slot();
这样的信号 / 槽。
- 信号或槽的名称若与 Qt 内部关键字(如
跨线程连接需指定连接类型
- 若发送者和接收者在不同线程,默认连接类型(
Qt::AutoConnection
)会自动选择线程安全的Qt::QueuedConnection
,但需确保参数类型可被 Qt 元对象系统序列化(如基本类型、QString
等,自定义类型需用Q_DECLARE_METATYPE
注册)。
- 若发送者和接收者在不同线程,默认连接类型(
总结
自定义信号与槽的核心是遵循元对象系统的规则:正确继承 QObject
、添加 Q_OBJECT
宏、规范声明信号 / 槽、确保 connect
语法和参数兼容。这些细节直接影响信号槽能否正常工作,也是初学者最易出错的地方。遵循上述规则,可有效避免 90% 以上的信号槽相关问题。