前置记住:Qt 是C++ 框架,下面这些是 Qt 额外增加的宏 / 关键字,不是标准 C++,靠 moc(元对象编译器)处理。普通 C++ 语法(if、for、class、指针、vector 等)照常使用。 环境:Qt Creator + Qt Widgets 项目
一、Q_OBJECT【最重要!90% 新手报错根源】
写法
#include <QObject> class MyClass : public QObject // 必须继承QObject或者它的子类(QWidget/QMainWindow等) { Q_OBJECT // 这一行! public: explicit MyClass(QObject *parent = nullptr); };详细解释
- 作用:开启 Qt【元对象系统】。有了它,才能使用信号槽、反射、Q_PROPERTY。
- 强制规则(新手必背)
Q_OBJECT必须写在头文件 (.h)的类里面;- 这个类必须继承 QObject(窗口类 QMainWindow、QWidget 都是 QObject 子类);
- 类名后面大括号内第一行附近写;
- ⚠️ 踩坑:加了 Q_OBJECT 之后,一定要 qmake/CMake 重新构建,不然会报
undefined reference to vtable虚表错误。
简单一句话:要用信号槽,就必须写 Q_OBJECT;不用信号槽,可以不写。
二、signals:信号
写法
signals: void sendData(int num); // 信号函数声明详细解释
- 信号:类对外发出的通知,代表 “发生了某个事件”。 例:按钮点击
clicked()就是 QPushButton 自带的信号。 - 特点:
- 只写声明,不用写函数实现(不需要写 {} 函数体),moc 自动生成代码;
- 信号可以带参数,用来传递数据;
- 信号函数返回值一般是
void;
- 触发信号用
emit。
理解类比:信号 = 报警器,出事了就响,只负责通知,不做处理。
三、slots:槽函数(旧写法,了解即可)
Qt5 之后推荐用普通成员函数当槽,
slots:属于老式语法
public slots: void onRecvData(int num);- 槽函数:用来接收信号、执行具体业务逻辑的函数。
- 特点:必须自己写函数实现(.cpp 里写函数体);参数数量、类型最好和发送的信号匹配。
类比:槽 = 接到报警器通知后,执行的动作(关门、报警提示)
✅ Qt5 新写法推荐:不用写
slots:,普通 public 函数就可以作为槽,直接 connect 连接。
四、emit发射信号
emit sendData(100);解释
emit是一个空宏(编译后直接消失,只是给人看标记),作用:触发信号。- 当代码执行
emit xxx(),就会把这个信号发送出去,所有连接到这个信号的槽函数会被调用。 - 只能在信号所在的类的成员函数内部emit。
一句话:emit = 按下报警器开关。
信号槽完整最小示例(h 文件)
#ifndef MYCLASS_H #define MYCLASS_H #include <QObject> class MyClass : public QObject { Q_OBJECT public: explicit MyClass(QObject *parent = nullptr); void testEmit(); // 普通函数,用来发射信号 signals: void sendMsg(QString str); // 信号,不带实现 public: void recvMsg(QString str); // Qt5新方式:普通函数直接做槽 }; #endif // MYCLASS_Hcpp 文件:
#include "myclass.h" #include <QDebug> MyClass::MyClass(QObject *parent) : QObject(parent) { } void MyClass::testEmit() { emit sendMsg("Hello Qt"); // 发射信号 } void MyClass::recvMsg(QString str) { qDebug() << "收到消息:" << str; }五、connect()信号槽连接函数【最常用】
Qt5 新式写法(推荐,新手优先学这个!类型安全)
connect(objA, &MyClass::sendMsg, objB, &MyClass::recvMsg);参数拆解:
connect(发送者对象, &类名::信号名, 接收者对象, &类名::槽函数)
- objA:信号是谁发出来的对象
&MyClass::sendMsg:取信号函数的地址- objB:谁接收信号
&MyClass::recvMsg:接收后执行的函数地址
旧版 Qt4 字符串写法(不推荐,容易写错,类型不检查)
// Qt4老写法,尽量不要用 connect(objA, SIGNAL(sendMsg(QString)), objB, SLOT(recvMsg(QString)));connect 第五个可选参数(连接方式,了解)
connect(a, &A::sig, b, &B::slot, Qt::QueuedConnection);Qt::AutoConnection默认:自动判断同线程 / 跨线程Qt::DirectConnection:同步,发射信号立刻执行槽(同线程)Qt::QueuedConnection:异步,放进接收者事件队列(跨线程必须用这个)
六、explicit构造函数(类构造函数前面)
explicit MyClass(QObject *parent = nullptr);- 作用:禁止隐式类型转换。Qt 所有继承 QObject 的类构造,基本都会加 explicit。
- 新手记住:写继承 QObject 的类,构造函数前面加上
explicit就对了。 parent = nullptr:父对象,Qt 内存管理核心。
七、parent 父对象机制(Qt 内存管理,非常重要)
// new一个对象,指定父对象为this QPushButton *btn = new QPushButton("按钮", this);- 普通 C++:new 出来的对象,必须手动 delete,否则内存泄漏
- Qt QObject 子类:指定 parent 父对象,父对象销毁时,自动 delete 所有子对象,不用手动 delete!
this代表当前类实例。
注意:只对继承 QObject 的类生效,普通 C++ 类没用。
八、QDebug打印输出,代替 cout
#include <QDebug> qDebug() << "数字:" << 123 << "字符串" << QString("测试");qDebug()Qt 自带打印,专门输出 Qt 类型(QString、QVariant)- 可以连续
<<拼接,不需要 endl,自动换行 - 发布版本可以关掉 qDebug 输出。
九、QStringQt 字符串(高频使用)
标准 C++ 是std::string;Qt 界面用QString
QString str = "hello"; // 互转 std::string s = str.toStdString(); QString str2 = QString::fromStdString(s);常用方法:
str.isEmpty(); // 判断是否空 str.size(); // 字符长度 str.arg(100); // 字符串占位替换,超级常用 QString info = QString("当前数值:%1").arg(99); // info结果:"当前数值:99"十、this关键字(和 C++ 一样,Qt 大量使用)
this指向当前实例对象。 常见场景:
- 构造函数初始化列表
- connect 里传递当前对象作为发送 / 接收者
- new 控件时,设置 parent = this
十一、初始化列表(C++ 语法,但 Qt 构造函数天天用)
MyClass::MyClass(QObject *parent) : QObject(parent) // 初始化列表,调用父类构造函数 { }: QObject(parent):子类构造时,先调用父类 QObject 的构造,把 parent 传给父对象。
所有继承 QObject 的类,构造函数都要写这个。
二、新手高频踩坑汇总
- 加了
Q_OBJECT后,忘记qmake 重新构建,报虚表 undefined reference。解决:【构建】->【qmake】再重新构建项目 - 信号参数多于槽函数参数:可以;槽参数多于信号参数:不行!
- QObject 的子类不能拷贝、不能赋值,不能写
MyClass a = b;,QObject 禁用拷贝构造。 - 界面控件(QPushButton、QLabel)操作必须在主线程,子线程不能直接操作 UI 控件。
三、最简完整可运行 Demo(控制台 Qt 项目)
myclass.h
#ifndef MYCLASS_H #define MYCLASS_H #include <QObject> class MyClass : public QObject { Q_OBJECT public: explicit MyClass(QObject *parent = nullptr); void test(); signals: void sendText(QString txt); public: void slotRecv(QString txt); }; #endif // MYCLASS_Hmyclass.cpp
#include "myclass.h" #include <QDebug> MyClass::MyClass(QObject *parent) : QObject(parent) { } void MyClass::test() { emit sendText("Qt信号槽测试"); } void MyClass::slotRecv(QString txt) { qDebug() << "收到信号内容:" << txt; }main.cpp
#include <QCoreApplication> #include "myclass.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); MyClass obj1; MyClass obj2; // 连接信号槽 connect(&obj1, &MyClass::sendText, &obj2, &MyClass::slotRecv); obj1.test(); // 调用函数,发射信号 return a.exec(); }运行输出:收到信号内容:"Qt信号槽测试"