一、QObject核心定位
QObject是Qt所有可支持信号槽、内存自动管理、事件循环、元对象系统类的基类,位于<QObject>头文件。几乎所有Qt控件、网络、线程、定时器等组件都直接/间接继承自它。
核心特性(全部依赖元对象系统MOC):
信号与槽(Signal & Slot):Qt跨对象通信机制
父子内存管理:父对象销毁自动回收所有子对象
元对象反射:运行时获取类名、属性、方法、枚举
事件系统:接收、分发、过滤事件
对象名称+动态属性:objectName、setProperty
跨线程信号槽:支持多线程安全通信
二、使用前置必备:moc元对象宏
1. Q_OBJECT宏
只要类需要信号槽、属性、元对象反射,类定义内第一行必须写Q_OBJECT,否则moc不会生成元代码,信号槽直接失效。
#include <QObject> class MyObj : public QObject{ Q_OBJECT // 关键宏,放在类内部开头public: explicit MyObj(QObject *parent = nullptr);};注意事项:
(1) Q_OBJECT只能在继承QObject的类中使用
(2) 类必须是public继承QObject
(3) qmake/CMake会自动调用moc扫描带Q_OBJECT的头文件生成.moc源码
(4) 忘记加会报:undefined reference to vtable链接错误
2. 其他配套元对象宏
宏 | 作用 |
Q_PROPERTY | 定义可反射属性,支持QML /脚本读写 |
Q_SIGNALS | 标记信号函数段(可省略,仅可读性) |
Q_SLOTS | 标记槽函数(可省略) |
Q_ENUM | 导出枚举到元对象,运行时转字符串 |
Q_INVOKABLE | 普通成员函数可通过metaObject()->invokeMethod()动态调用 |
三、父子对象与内存自动管理(最常用特性)
1. 构造函数 parent 参数
所有 QObject 子类标准构造:
explicit MyObj(QObject *parent = nullptr);parent为父QObject对象指针
创建时传入父对象,自动加入父对象子对象列表
2. 销毁规则(核心)
父对象析构时,会自动delete所有子对象,递归销毁,无需手动释放内存,杜绝内存泄漏。
示例:
// 栈父对象,出作用域自动销毁子对象 void test(){ QObject parentObj; // child1、child2 归属 parentObj QObject *child1 = new QObject(&parentObj); QObject *child2 = new QObject(child1); // 函数结束 parentObj 析构 → 先删 child1 → 再删 child2 }3. 常用父子操作API
// 设置父对象 void setParent(QObject *parent); // 获取父对象 QObject *parent() const; // 获取所有一级子对象列表 QList<QObject*> children() const; // 根据 objectName 查找子对象(递归查找) template<typename T> T findChild(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindDirectChildrenOnly); QList<T> findChildren(...)findChild实用场景
界面中根据名称查找控件、动态管理子业务对象:
QPushButton *btn = parentWidget->findChild<QPushButton>("btn_submit");4. 手动释放注意
不要栈对象当父,子对象堆创建后手动delete父,会双重释放崩溃
多线程:父子不能跨线程,父和子必须在同一线程,跨线程会警告+内存异常
窗口控件(QWidget)自带父子管理,布局控件会自动设置 parent
四、信号与槽Signal&Slot(QObject核心通信)
1. 信号signals
关键字 signals: 下声明,无实现、无返回值、权限默认 protected
触发:emit 信号名(参数);
class MyObj : public QObject{ Q_OBJECT signals: void sendMsg(const QString &text); // 信号,只声明不写函数体public: void testEmit() { emit sendMsg("hello qobject"); // 发射信号 }};2. 槽slots
三种合法槽函数:
(1) public slots: / private slots: 显式标记
(2) 普通 public 成员函数(Qt5 新 connect 语法支持)
(3) 标记 Q_INVOKABLE 的函数
class Receiver : public QObject{ Q_OBJECTpublic slots: void onRecv(const QString &msg) { qDebug() << "收到:" << msg; }};3. connect四种写法(Qt4老式/Qt5新式推荐)
方式 1:Qt5函数指针(推荐,编译期类型检查)
MyObj sender; Receiver recv;// connect(发送者, 信号地址, 接收者, 槽地址) connect(&sender, &MyObj::sendMsg, &recv, &Receiver::onRecv); sender.testEmit(); // 触发打印方式 2:Lambda表达式(无接收对象,临时逻辑)
connect(&sender, &MyObj::sendMsg, this, [](QString s){ qDebug() << "lambda接收:" << s;});方式 3:Qt4字符串宏(运行时检查,易写错不推荐)
connect(&sender, SIGNAL(sendMsg(QString)), &recv, SLOT(onRecv(QString)));方式 4:QMetaObject::connectSlotsByName 自动绑定
规则:槽函数名 on_对象名_信号名,UI控件常用
// 控件 objectName="btn_ok",clicked 信号 void on_btn_ok_clicked();// 调用一次自动绑定所有匹配槽 QMetaObject::connectSlotsByName(this);4. connect 第五参数:连接类型(跨线程必用)
connect(sender, signal, receiver, slot, Qt::ConnectionType);枚举 | 适用场景 |
Qt::AutoConnection默认 | 同线程=直连;跨线程=队列 |
Qt::DirectConnection直连 | 信号发射线程直接执行槽,跨线程慎用(线程不安全) |
Qt::QueuedConnection队列 | 信号放入接收者线程事件循环,跨线程标准用法 |
Qt::BlockingQueuedConnection阻塞队列 | 发射线程阻塞直到槽执行完成,禁止同线程死锁 |
Qt::UniqueConnection | 防止重复连接,搭配或使用` |
5. 断开连接 disconnect
// 断开指定信号槽 disconnect(&sender, &MyObj::sendMsg, &recv, &Receiver::onRecv); // 断开sender所有连接 disconnect(&sender, nullptr, nullptr, nullptr); // 断开接收者所有连接 sender.disconnect(&recv);五、元对象系统MetaObject(反射能力)
1. 获取元对象
QObject *obj = new MyObj; const QMetaObject *meta = obj->metaObject(); // 静态获取,无需实例 const QMetaObject *metaStatic = &MyObj::staticMetaObject;2. 运行时反射常用API
// 获取类名 qDebug() << meta->className(); // 动态调用函数(函数需 Q_INVOKABLE / slot) QString res; meta->invokeMethod(obj, "getInfo", Qt::DirectConnection, Q_RETURN_ARG(QString, res)); // 获取属性数量、读写属性 int propCnt = meta->propertyCount(); QMetaProperty prop = meta->property(0); prop.read(obj); prop.write(obj, "newVal");3. Q_PROPERTY动态属性示例
class Person : public QObject{ Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)public: QString name() const { return m_name; } void setName(const QString &n) { if(m_name != n) { m_name = n; emit nameChanged(m_name); } } signals: void nameChanged(const QString &);private: QString m_name;};使用:
Person p; p.setProperty("name", "张三"); // 动态写 qDebug() << p.property("name").toString(); // 动态读4. 动态属性(无Q_PROPERTY也能用)
无需提前声明,任意QObject运行时附加键值:
obj->setProperty("tag", 100); int tag = obj->property("tag").toInt(); // 判断是否存在属性 obj->dynamicPropertyNames();六、事件处理机制(QObject事件循环)
所有 QObject 都能接收事件,依靠 event()、事件过滤器。
1. 重写事件处理函数
class MyObj : public QObject{ Q_OBJECTprotected: // 总事件分发入口 bool event(QEvent *event) override { if(event->type() == QEvent::Timer) { qDebug() << "定时器事件"; return true; // 消费事件,不再传递 } return QObject::event(event); // 交给基类默认处理 }};2. 事件过滤器 installEventFilter
A 对象监听 B 对象所有事件,拦截处理:
// 监听者this安装过滤器到 targetObj targetObj->installEventFilter(this); // 重写过滤函数 bool eventFilter(QObject *watched, QEvent *event) override{ if(watched == targetObj && event->type() == QEvent::MouseButtonPress) { return true; // 拦截鼠标点击,目标收不到 } return false; // 放行事件 }3. 发送事件
同步:QCoreApplication::sendEvent(obj, &event) 阻塞执行
异步:QCoreApplication::postEvent(obj, new QEvent(...)) 放入事件队列
七、QObject多线程规则
1. 对象归属线程
每个 QObject 绑定创建时所在线程,API:
// 返回对象依附的线程 QThread *thread() const; // 移动对象到新线程(必须无父对象!) bool moveToThread(QThread *targetThread);2. 强制铁则
(1)moveToThread 前必须 setParent (nullptr),有父对象移动失败
(2)不能跨线程父子对象,父和子必须同线程
(3)不要在子线程直接操作 UI 控件(UI 只能主线程)
(4)跨线程通信信号槽必须用 Qt::QueuedConnection
标准多线程模板
QThread *th = new QThread; Worker *work = new Worker; work->moveToThread(th); // 移至子线程 // 线程启动执行任务 connect(th, &QThread::started, work, &Worker::doWork); // 任务完成销毁线程 connect(work, &Worker::finished, th, &QThread::quit);connect(th, &QThread::finished, th, &QThread::deleteLater);connect(th, &QThread::finished, work, &Worker::deleteLater); th->start();3. deleteLater 延迟删除
多线程安全销毁,不会立即释放,等待当前事件循环完成再删除:
obj->deleteLater();推荐替代直接 delete obj,避免事件派发中崩溃。
八、对象名称objectName
// 设置/获取对象名void setObjectName(const QString &name); QString objectName() const;用途:
findChild 按名称查找对象
QSS 样式选择器 #objectName
日志、调试区分多个同类对象
九、禁用拷贝(QObject无法复制)
QObject 禁用拷贝构造、赋值运算符:
Q_DISABLE_COPY(MyObj) // 内部自动加,无需手动写错误写法:
MyObj a; MyObj b = a; // 编译报错,禁止拷贝原因:父子内存管理、信号槽绑定、线程归属无法复制,需要多实例只能 new 创建。
十、常用全局配套工具类
QCoreApplication:管理事件循环,所有QObjec 事件分发入口
QMetaType:自定义类型注册,信号槽传递自定义结构体需qRegisterMetaType<T>()
QVariant:配合 Q_PROPERTY 存储任意类型属性
十一、常见报错与踩坑总结
undefined reference to vtable:类有Q_OBJECT但未moc,或忘记执行 qmake/CMake
跨线程崩溃:父子跨线程、DirectConnection 跨线程直接调用 UI、未用 QueuedConnection
重复析构崩溃:栈对象作为父,子对象手动 delete;或 moveToThread 时有父对象
信号槽不触发:参数类型不匹配、connect 第五参数连接类型错误、对象提前销毁
invokeMethod 调用失败:函数未加 slot / Q_INVOKABLE,参数传参不匹配
十二、最简完整可运行示例
#include <QCoreApplication>#include <QObject>#include <QDebug> class Sender : public QObject{ Q_OBJECT signals: void msg(const QString &s); public: void send() { emit msg("测试信号"); } }; class Recv : public QObject { Q_OBJECT public slots: void onMsg(QString s) { qDebug() << "接收:" << s; } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Sender s; Recv r; QObject::connect(&s, &Sender::msg, &r, &Recv::onMsg); s.send(); return a.exec(); } #include "main.moc" // 单文件 moc 必需