当前位置: 首页 > news >正文

QT元对象系统(未完)

https://www.qt.io/   QT官网

跨平台的C++图形用户界面应用程序框架,Qt的本质就是一个C++类库。

QT概述

Qt 是一个跨平台的 C++ 应用程序开发框架,由挪威 Trolltech 公司(后被 Nokia 收购,现为 The Qt Company 所有)开发,旨在简化桌面、移动、嵌入式等多平台应用的开发。它不仅提供了丰富的 GUI 组件,还包含了网络、数据库、多媒体、绘图等一系列功能模块,是开发高效、美观的跨平台应用的重要工具。

一、Qt 的核心优势

  1. 跨平台性
    一次编写,多平台运行。Qt 支持 Windows、macOS、Linux 等桌面系统,iOS、Android 等移动平台,以及嵌入式系统(如嵌入式 Linux、QNX 等),无需大量修改代码即可适配不同平台。

  2. 信号与槽机制(Signals & Slots)
    这是 Qt 最具特色的通信机制,用于对象间的解耦交互(如按钮点击触发函数、数据变化通知等),比传统的回调函数更灵活(之前已详细介绍)。

  3. 元对象系统(Meta-Object System)
    基于 QObjectQ_OBJECT 宏和元对象编译器(moc),支持动态类型识别、反射、信号槽等核心功能(之前已详细介绍)。

  4. 丰富的 UI 组件
    提供大量现成的界面控件(按钮、文本框、表格、树状视图等),且支持自定义控件,同时内置样式表(QSS)可轻松美化界面,类似 CSS。

  5. 模块化设计
    功能按模块划分,按需使用,核心模块包括:

    • 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 模块(如coreguiwidgets
- 源文件(.cpp)、头文件(.h)、UI 文件(.ui)的列表
- 编译选项(如 C++ 标准、宏定义)
- 部署路径等。
是项目的 “入口”,必须保留
主程序入口main.cpp程序的入口函数(main函数)所在文件,负责:
- 创建QApplication实例(Qt 应用程序的核心对象,管理事件循环)
- 创建主窗口对象(如MainWindowWidget
- 显示主窗口
- 启动应用程序的事件循环(a.exec())。
窗口类头文件mainwindow.h 或 widget.h主窗口类(如MainWindow)的声明文件,包含:
- 类的继承关系(通常继承自QMainWindowQWidget
- 成员变量(如界面控件的指针、业务数据)
- 成员函数(如槽函数、自定义方法)的声明
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 中几乎所有核心类,如 QWidgetQPushButtonQTimer 等,都默认继承自 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将信号与槽连接在一起,但是连接之后并不能调用槽函数,只有当信号被触发时,连接的槽函数才会自动调用,并将信号中的参数传递到连接的槽函数中

一.类的基础要求

  1. 必须继承自 QObject(直接或间接)

    • 信号与槽是 Qt 元对象系统的功能,而元对象系统仅对 QObject 派生类生效。
    • 示例:class MySender : public QObject { ... }(正确);class MySender { ... }(错误,无法使用信号槽)。
  2. 必须声明 Q_OBJECT 宏

    • 宏需放在类的私有成员区域(通常是类声明的开头),用于触发元对象编译器(moc)生成信号槽的底层代码。
    • 遗漏此宏会导致:信号 / 槽无法被识别、connect 函数报错、链接错误(如 undefined reference to vtable for XXX)。
    • 示例:
      class MySender : public QObject {Q_OBJECT  // 必须添加,且位置在类声明的私有区域(默认私有)
      signals:void mySignal();
      };
      

二、信号的声明规则

  1. 声明位置:必须在 signals: 关键字下

    • signals 是 Qt 的特殊关键字(非 C++ 原生),用于标记信号,无需指定访问修饰符(默认是 public)。
    • 错误示例:在 public: 或 private: 下声明信号(编译不报错,但 moc 可能无法正确处理)。
  2. 信号仅声明,无需实现

    • 信号的函数体由 moc 自动生成(在 moc_xxx.cpp 中),手动实现会导致编译错误。
    • 示例:
      signals:void dataUpdated(int value);  // 正确:仅声明// void dataUpdated(int value) { ... }  // 错误:不能有实现
      
  3. 返回值必须为 void

    • 信号无法返回任何值(因信号是 “通知”,接收者可能有多个,返回值无意义)。
    • 错误示例:int dataUpdated(int value);(编译报错)。
  4. 参数需与槽兼容

    • 信号的参数列表可以多于或等于槽,但类型和顺序必须完全匹配(槽可忽略信号的尾部参数)。
    • 示例:
      • 信号: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);(类型不匹配)。

三、槽的声明规则

  1. 声明位置:必须在 slots 关键字下,并指定访问修饰符

    • 槽需用 public slotsprivate slots 或 protected slots 声明(控制槽的访问权限)。
    • 错误示例:在 public: 下直接声明槽(编译不报错,但无法被元对象系统识别为槽)。
  2. 槽需要手动实现

    • 槽本质是普通成员函数,必须提供函数体(否则链接错误)。
    • 示例:
      public slots:void onDataReceived(int value) {  // 正确:有实现qDebug() << "Received:" << value;}
      
  3. 返回值必须为 void

    • 与信号一致,槽的返回值只能是 void(Qt 不处理槽的返回值)。

四、connect 函数的使用规则

  1. 参数顺序:发送者 → 信号 → 接收者 → 槽

    • 错误示例:颠倒信号和槽的所属类(如 connect(&receiver, &Receiver::signal, &sender, &Sender::slot),逻辑上信号应属于发送者)。
  2. 语法正确:使用函数指针形式(Qt5 及以上推荐)

    • 推荐:QObject::connect(sender, &Sender::signalName, receiver, &Receiver::slotName);(类型安全,编译时检查)。
    • 避免:Qt4 的 SIGNAL()/SLOT() 宏(如 connect(sender, SIGNAL(signal()), receiver, SLOT(slot()))),无编译时检查,拼写错误仅在运行时暴露。
  3. 确保对象指针有效

    • 发送者和接收者必须是有效指针(非 nullptr),否则 connect 失败(返回 false)。
    • 注意:若对象被销毁,Qt5 及以上会自动断开连接(避免野指针),但仍建议在对象销毁前手动 disconnect(尤其跨线程场景)。

五、其他关键细节

  1. 类定义必须放在 .h 头文件中

    • 包含 Q_OBJECT 宏的类若定义在 .cpp 文件中,moc 可能无法扫描到,导致元对象代码生成失败(链接错误)。
    • 解决:将类声明移至 .h 头文件,并在 .pro 的 HEADERS 中添加该文件。
  2. 信号发射需用 emit 关键字

    • emit 是 Qt 的宏(实际编译时会被忽略),用于标记信号发射,提高代码可读性。
    • 示例:emit dataUpdated(100);(正确);dataUpdated(100);(语法允许,但不推荐)。
  3. 避免信号 / 槽名与 Qt 关键字冲突

    • 信号或槽的名称若与 Qt 内部关键字(如 signalslot)重复,可能导致编译错误。
    • 示例:避免声明 void signal(); 或 void slot(); 这样的信号 / 槽。
  4. 跨线程连接需指定连接类型

    • 若发送者和接收者在不同线程,默认连接类型(Qt::AutoConnection)会自动选择线程安全的 Qt::QueuedConnection,但需确保参数类型可被 Qt 元对象系统序列化(如基本类型、QString 等,自定义类型需用 Q_DECLARE_METATYPE 注册)。

总结

自定义信号与槽的核心是遵循元对象系统的规则:正确继承 QObject、添加 Q_OBJECT 宏、规范声明信号 / 槽、确保 connect 语法和参数兼容。这些细节直接影响信号槽能否正常工作,也是初学者最易出错的地方。遵循上述规则,可有效避免 90% 以上的信号槽相关问题。


文章转载自:

http://ztF5TKgB.knzmb.cn
http://RWCxAElX.knzmb.cn
http://wAEGHDdW.knzmb.cn
http://VzgQ2eeu.knzmb.cn
http://WI87H6rN.knzmb.cn
http://7pSFpCPR.knzmb.cn
http://Pm3h0DtY.knzmb.cn
http://9nX8lBsU.knzmb.cn
http://6wYabLY4.knzmb.cn
http://mKaiut7O.knzmb.cn
http://RlPXbQVy.knzmb.cn
http://juKy0dBN.knzmb.cn
http://eGFDwtOW.knzmb.cn
http://4xj1wl9x.knzmb.cn
http://LEN7QmD0.knzmb.cn
http://AA8tqaM2.knzmb.cn
http://TSFlCNQR.knzmb.cn
http://fAQsehTu.knzmb.cn
http://Kn52o5gx.knzmb.cn
http://pmPLr41v.knzmb.cn
http://Exy8ytMj.knzmb.cn
http://DeShDd0P.knzmb.cn
http://ik3QaSh6.knzmb.cn
http://oGo29kWk.knzmb.cn
http://CiP6dZNv.knzmb.cn
http://5RoGKib7.knzmb.cn
http://SBLWwSIP.knzmb.cn
http://OdtiPRee.knzmb.cn
http://QlEua86m.knzmb.cn
http://f7nmKhRB.knzmb.cn
http://www.dtcms.com/a/381889.html

相关文章:

  • Netty 针对 Java NIO Selector 优化:SelectedSelectionKeySet
  • 抑制信号突变(模拟量采集+斜坡函数)
  • C语言入门指南:字符函数和字符串函数
  • JVM从入门到实战:从字节码组成、类生命周期到双亲委派及打破双亲委派机制
  • SQL-用户管理与操作权限
  • Airtable与Python:轻量级ETL数据管道实战
  • JavaScript 对象:一份全面的回顾
  • 逐时nc数据批量处理为日平均
  • ffmpeg推流测试
  • SQL注入常见攻击点与防御详解
  • 后端(FastAPI)学习笔记(CLASS 3):Tortoise ORM
  • C++-STL
  • Java 大视界 -- Java 大数据在智能家居场景联动与用户行为模式挖掘中的应用
  • XCKU15P-2FFVA1760I AMD 赛灵思 Xilinx Kintex UltraScale+ FPGA
  • 图论基础知识
  • DMA硬件架构解析:总线矩阵与核心组件
  • 从军用到掌心:固态硬盘(SSD)的演进与革命
  • 通俗解释redis高级:redis持久化(RDB持久化、AOF持久化)、redis主从、redis哨兵、redis分片集群
  • 【C++】类和对象——(上)
  • 解决Windows系统“‘php‘ 不是内部或外部命令”报错的完整指南
  • 用 Go 打造一个服务器资源指标采集器:结合 Prometheus Exporter 实战
  • Unity学习----【进阶】TextMeshPro学习(二)--进阶知识点(样式表,颜色渐变预设,精灵图片资源)
  • 从理论到落地:神经网络稀疏化设计构架中网络剪枝的深度实践与创新
  • ARM、AArch64、amd64、x86_64、x86有什么区别?
  • 机器学习项目-南方电网电力负荷预测
  • python标准库有哪些模块,简单总结下。
  • 文献阅读·MCformer:基于混合通道变换的多变量时间序列预测
  • 【软件操作】飞牛nas系统:笔记本息屏、合盖均不关机
  • 【SPI】【二】SPI控制器驱动代码详解
  • pandas读取复合列名列头及数据和处理