【QT 一 | 信号和槽】
Qt5基本模块
Qt Creator 中的快捷键
• 注释:ctrl + /
• 运⾏:ctrl + R
• 编译:ctrl + B
• 字体缩放:ctrl + 鼠标滑轮
• 查找:ctrl + F
• 整行移动:ctrl + shift + ⬆/⬇
• 帮助⽂档:F1
• 自动对齐:ctrl + i;
• 同名之间的 .h 和 .cpp 的切换:F4
• 生成函数声明的对应定义: alt + enter
认识对象模型(对象树)
在 Qt 中,对象树是一种非常重要的概念,用于管理和组织对象之间的关系。以下是对 Qt 对象树的详细理解介绍:
1. 对象树的基本概念
- 定义:Qt 的对象树是一种层次化的对象管理机制,基于
QObject
类实现。每个QObject
对象可以有一个父对象(parent
),并且可以拥有多个子对象(children
)。 - 父子关系:当创建一个
QObject
对象时,可以通过构造函数指定一个父对象。如果指定了父对象,那么这个新创建的对象会自动成为父对象的子对象,并被添加到父对象的children()
列表中。
2. 对象树的作用
-
生命周期管理:父对象负责管理其子对象的生命周期。当父对象被销毁时,其所有子对象也会被自动销毁。这种机制避免了内存泄漏,同时也简化了对象的管理。
-
事件传播:在 GUI 程序中,事件(如鼠标点击、键盘输入等)会从父对象向下传播到子对象。这种机制使得事件处理更加高效和灵活。
-
组件化管理:对象树使得 GUI 组件的组织更加清晰。例如,一个窗口(
QWidget
)可以包含多个按钮、文本框等子组件。当窗口被销毁时,所有子组件也会被自动销毁。
3. 对象树的实现机制
- 构造函数中的父对象参数:
QObject
的构造函数接受一个QObject*
类型的参数,表示父对象。例如:
在这个例子中,QObject* parent = new QObject(); QObject* child = new QObject(parent);
child
的父对象是parent
,child
会被自动添加到parent
的children()
列表中。 children()
方法:每个QObject
对象都有一个children()
方法,返回一个包含所有子对象的列表。- 自动销毁机制:当父对象被销毁时,Qt 会自动调用子对象的析构函数,销毁所有子对象。
4. 对象树的使用场景
- GUI 组件管理:在 Qt 的 GUI 程序中,
QWidget
是所有可视组件的基类,它继承自QObject
。例如:
在这个例子中,QWidget* window = new QWidget(); QPushButton* button = new QPushButton(window);
button
是window
的子组件。当window
被销毁时,button
也会被自动销毁。 - 资源管理:对象树机制不仅适用于 GUI 组件,还可以用于管理其他资源,如文件句柄、网络连接等。
5. 注意事项
- 避免循环引用:在使用对象树时,要避免创建循环引用(即对象 A 是对象 B 的父对象,同时对象 B 也是对象 A 的父对象)。这会导致对象无法正确销毁,从而引发内存泄漏。
- 手动管理子对象:虽然对象树会自动管理子对象的生命周期,但也可以手动删除子对象。例如:
在这种情况下,子对象会从父对象的QObject* child = new QObject(parent); delete child; // 手动删除子对象
children()
列表中自动移除。
Qt 的对象树是一种强大的机制,用于管理和组织对象之间的关系。它不仅简化了内存管理,还使得 GUI 组件的组织更加清晰。通过合理使用对象树,可以提高代码的可维护性和可扩展性。
在 Qt 中,尽量在构造的时候就指定parent对象,并且⼤胆在堆上创建。
## 信号与槽
如果要使用信号与槽(signal 和 slot)的机制就必须加入Q_OBJECT宏;这个宏不仅用于支持信号与槽机制,还允许 Qt 的元对象系统(Meta-Object System)自动生成一些辅助代码。
信号的本质就是事件。
- 如:按钮单击、双击 ,窗⼝刷新等。
- 信号的呈现形式就是函数
- 信号函数只需要声明,不需要定义(实现)。
- 信号函数声明用 signals 关键字修饰
槽(Slot)就是对信号响应的函数。
- 槽是普通的成员函数,用于处理信号。槽函数可以手动实现,也可以通过 Qt 的机制自动生成。
- 可以定义在类的任何位置( public、protected 或 private)。
- 可以具有任何参数,可以被重载,也可以被直接调
用(但是不能有默认参数 - 槽函数需要定义(实现)。
- 槽函数可以与⼀个信号关联,当信号被发射时,关联的槽函数被自动执行。
- 槽函数用 public slots、protected lots 或者 private slots 修饰。
signals 和 slots 是 Qt 在 C++ 的基础上扩展的关键字
connect函数
在 Qt 中,QObject 类提供了⼀个静态成员函数connect(),该函数专门来关联指定的信号函数和槽函数。
connect函数原型
connect (const QObject *sender,
const char * signal ,
const QObject * receiver ,
const char * method ,
Qt::ConnectionType type = Qt::AutoConnection )
参数说明:
• sender:信号的发送者;
• signal:发送的信号(信号函数);
• receiver:信号的接收者;
• method:接收信号的槽函数;
• type:用于指定关联方式,默认的关方式为 Qt::AutoConnection,通常不需要手动设定。
自动生成槽函数的机制
- 步骤:
- 在 Qt Creator 中,添加一个信号(
mySignal(int value)
) - 右键点击信号或信号声明。
- 选择 “转到槽”(Go to Slot)或 “转到信号”(Go to Signal)。
- Qt Creator 会自动生成一个槽函数的声明和定义,并将其添加到类中。
- 在 Qt Creator 中,添加一个信号(
例如,对于信号 mySignal(int value)
,Qt Creator 会生成如下代码:
// 在头文件中自动生成槽函数声明
public slots:
void on_mySignal(int value);
// 在实现文件中自动生成槽函数定义
void MyObject::on_mySignal(int value)
{
// 在这里实现槽函数的逻辑
}
- 自动生成的槽函数:
- 当你在类中声明信号时,Qt 的元对象系统会自动为这些信号生成默认的槽函数。这些槽函数通常以
on_<SignalName>
的形式命名。 - 按照这种命名风格格定义的槽函数,会被 Qt ⾃动的和对应的信号进行连接。
日常写代码,最好还是不要依赖命名规则,而是显式
使用 connect 更好。1. connect 可以更清晰直观的描述信号和槽的连接关系。2. 防止信号或者槽的名字拼写错误导致连接失效.
- 当你在类中声明信号时,Qt 的元对象系统会自动为这些信号生成默认的槽函数。这些槽函数通常以
- 使用方法:
- 在类的声明中,添加信号声明。
- 在类的实现中,实现自动生成的槽函数。
- 使用
QObject::connect()
将信号连接到自动生成的槽函数。
通过 Qt 的 Q_INVOKABLE
宏
Q_INVOKABLE
宏可以将普通成员函数标记为可调用的槽函数。虽然这不是严格意义上的“自动生成”,但它允许开发者将任意函数作为槽函数使用。
- 使用方法:
- 在类的声明中,使用
Q_INVOKABLE
宏标记成员函数。 - 这些函数可以直接作为槽函数使用。
- 在类的声明中,使用
例如:
class MyObject : public QObject
{
Q_OBJECT
public:
MyObject(QObject* parent = nullptr);
Q_INVOKABLE void mySlotFunction(int value); // 使用 Q_INVOKABLE 标记
signals:
void mySignal(int value);
};
void MyObject::mySlotFunction(int value)
{
qDebug() << "Slot function called with value:" << value;
}
// 连接信号和槽
connect(this, &MyObject::mySignal, this, &MyObject::mySlotFunction);
自定义信号函数和槽函数
- ⾃定义信号函数书写规范
(1)⾃定义信号函数必须写到 “signals” 下;
(2)返回值为 void,只需要声明,不需要实现;
(3)可以有参数,也可以发生重载 - ⾃定义槽函数书写规范
(1)早期的 Qt 版本要求槽函数必须写到 “public slots” 下,但是现在⾼级版本的 Qt 允许写到类的 “public” 作用域中或者全局下;
(2)返回值为 void,需要声明,也需要实现;
(3)可以有参数,可以发生重载;
3、发送信号
使用 “emit” 关键字发送信号。“emit” 是⼀个空的宏。"emit"其实是可选的,没有什么含义,只
是为了提醒开发⼈员。
关联函数一定要写在发射信号之前
原因是,⾸先关联信号和槽,⼀旦检测到信号发射之后就会立即执行关联的槽函数。反之,若先发射信号,此时还没有关联槽函数,当信号发射之后槽函数不会响应。
信号与槽的断开
当"connect"函数第三个参数为"this" 时,第四个参数使⽤ Lambda表达式时,可以省略掉 “this”;