Qt中的信号与槽机制:深入解析

什么是信号和槽?

在生活中,我们会收到各种各样的信号,交通信号灯就是一个很好的信号与槽的示例:红灯、黄灯和绿灯是信号灯发出的信号,这些信号告诉行人和车辆何时停止、何时准备停止、何时通过。车辆和行人对这些信号的响应(如停车、行走)就是槽。

在Qt中,信号(signals)和槽(slots)是一种用于对象间通信的机制。信号是对象发出的消息,而槽是用于处理这些消息的方法。当一个对象的状态发生变化时,它可以发出一个信号,其他对象可以连接到这个信号并执行相应的操作。

解析connect函数

在Qt框架中,connect函数用于将对象的信号(signal)连接到另一个对象的槽(slot)上。当信号被发射(emit)时,与之连接的槽就会被调用。Qt的connect函数有多个重载版本,但最常见的形式接受四个或五个参数:

  1. 发送者(Sender):

    • 这是发出信号的对象。在Qt中,这通常是一个QObject或其子类的实例。
    • 参数类型:QObject *
  2. 信号(Signal):

    • 这是发送者对象中定义的一个信号。信号是类的成员函数,使用signals关键字在类的声明中定义。
    • 参数类型:通常是一个指向成员函数指针的类型,如void (SenderClass::*)(Args...),其中SenderClass是发送者的类名,Args...是信号的参数类型列表。
    • 注意:在Qt 5及以后版本中,你也可以使用&SenderClass::signalName这样的语法来指定信号。
  3. 接收者(Receiver):

    • 这是接收信号并调用槽的对象。它同样是一个QObject或其子类的实例。如果接收者为nullptr(或QCoreApplication::instance()),则槽函数将在全局范围内调用(即,作为静态成员函数或全局函数调用)。
    • 参数类型:QObject *
  4. 槽(Slot):

    • 这是接收者对象中定义的一个槽。槽是类的成员函数,使用slots关键字在类的声明中定义(但在Qt 5中,slots关键字是可选的)。
    • 参数类型:与信号相同的成员函数指针类型。
    • 注意:在Qt 5及以后版本中,你也可以使用&ReceiverClass::slotName这样的语法来指定槽。
  5. 连接类型(Connection Type)(可选参数):

    • 这个参数定义了信号和槽之间的连接类型。Qt支持几种不同的连接类型,如Qt::DirectConnection(直接连接)、Qt::QueuedConnection(排队连接)等。在Qt 5中,这个参数是可选的,并且默认是Qt::AutoConnection
    • 参数类型:Qt::ConnectionType

下面是一个使用connect函数的示例:

// 假设有两个类:Sender 和 Receiver,它们都是 QObject 的子类
// Sender 类有一个信号 signalEmitted()
// Receiver 类有一个槽 slotCalled()

Sender *sender = new Sender;
Receiver *receiver = new Receiver;

// 连接信号和槽
QObject::connect(sender, &Sender::signalEmitted, receiver, &Receiver::slotCalled);

// 在某个地方,当 signalEmitted() 被调用时,slotCalled() 也会被调用
sender->emit signalEmitted();

请注意,Qt 5引入了一种新的信号和槽语法,它使用函数指针而不是字符串来指定信号和槽,这提高了类型安全性和编译时检查。在上面的示例中,我们使用了这种新语法。如果你正在使用较旧的Qt版本,你可能需要使用基于字符串的旧语法。但是,新语法更受欢迎,因为它更安全且易于使用。

一、槽(Slots)

1. 基本概念

槽是Qt对象中的一个成员函数,它可以被信号调用。当信号被发出时,与之关联的槽函数将被执行。槽函数可以有返回值和参数,这使得它们比信号更加灵活。槽函数可以被任何对象调用,但通常它们是由信号触发的。

在这里插入图片描述
在这里插入图片描述
也可以采用ui创建
在这里插入图片描述
在这里插入图片描述
实现和connect一样的效果,名称必须一致!
在这里插入图片描述

2. 自定义槽

自定义槽的定义与普通成员函数类似,但需要满足一些特定要求:

  • 参数:槽函数的参数个数和类型必须与它所连接的信号的参数相匹配或更少。也就是说,槽函数的参数可以少于信号的参数,但不能多于。
  • 返回值类型:槽函数的返回值类型可以为任何类型,但通常建议返回void。这是因为槽函数通常是由信号触发的,而信号没有返回值。
  • 访问权限:槽函数可以是public slots:protected slots:或普通的成员函数。但是,槽函数的访问权限决定了谁可以和它进行连接。例如,public slots:可以被任何信号连接,而private slots:只能被该类本身的信号连接。

示例:

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    // ...

public slots:	//对于Qt5之前的版本 需要显示写public/private/protect slots
				//Qt5后可以直接声明槽函数
    void mySlot() {
        // 处理信号的代码
    } // 自定义槽,处理int参数

    // ...
};

在这里插入图片描述
点击后widgetTitle被修改
在这里插入图片描述

二、信号(Signals)

1. 基本概念

信号是Qt对象在特定事件发生时发出的。它们可以由Qt框架中的类定义,也可以由用户自定义的类定义。信号是事件驱动的,即它们只在特定事件发生时发出,并且不包含返回类型(即它们是单向的)。信号的发出类似于广播,没有特定的接收者,只要有对象对这个信号感兴趣并进行了连接,就可以接收到这个信号。

在这里插入图片描述
依旧采用上述示例,此时我们发送的信号为QPushButton下的clicked信号

tips:click() vs clicked 的语义区别:
click()是一个方法调用,它会直接模拟按钮的点击行为并可能触发相应的信号。而 clicked
是一个信号,它在按钮被用户或程序点击时自动发射,你需要通过 connect 函数将它连接到槽函数上以便处理点击事件。

2. 自定义信号

自定义信号的定义遵循以下规则:

  • 返回值类型:信号的返回值类型必须为void。这是因为信号是事件驱动的,不需要返回值。
  • 参数:信号可以有参数,但参数类型必须是Qt元类型系统所支持的。这包括Qt的核心类型(如int、QString等)以及用户自定义的类型(但需要注册到Qt元类型系统中)。
  • 声明方式:在类的声明中,使用signals:关键字来声明信号。这告诉Qt编译器这个类将包含一些信号。

示例:

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    // ...

signals:
    void mySignal(); 

    // ...
};

发出信号用emit关键字,Qt5后可以不显示写出

在这里插入图片描述
因为发送信号在构造函数中,所以在构造过程中完成了信号和槽的操作。
在这里插入图片描述

有参的信号和槽

信号和槽都有参
在这里插入图片描述
在这里插入图片描述
信号有参槽无参
在这里插入图片描述
在这里插入图片描述
信号无参槽有参
在这里插入图片描述
在这里插入图片描述
信号函数参数>槽函数参数
在这里插入图片描述
在这里插入图片描述

综上可知:自定义信号的函数参数个数必须 >= 自定义槽函数的参数

三、信号与槽的关联

在Qt中,使用QObject::connect()函数可以将信号与槽进行关联。当信号被发出时,与之关联的槽函数将被自动调用。Qt提供了两种连接信号与槽的方式:使用SIGNAL和SLOT宏(Qt 4及更早版本)和使用新语法(Qt 5及更高版本)。

1. 使用SIGNAL和SLOT宏

这种方式在Qt 4及更早版本中广泛使用,但在Qt 5及更高版本中已被视为过时。使用这种方式时,需要传入信号和槽的字符串标识符,这可能会导致一些类型安全问题。

QObject::connect(sender, SIGNAL(mySignal(int)), receiver, SLOT(mySlot(int)));

2. 使用新语法

Qt 5引入了基于函数指针的新语法来连接信号与槽。这种方式更加类型安全且易于使用。它允许在编译时检查信号和槽的参数类型是否匹配。

QObject::connect(sender, &SenderClass::mySignal, receiver, &ReceiverClass::mySlot);

此外,Qt还支持将Lambda表达式作为槽函数进行连接,这使得代码更加简洁和灵活。

四、信号和槽的参数传递

信号和槽可以传递任意数量和类型的参数。当连接信号和槽时,它们的参数必须兼容。例如,如果信号有一个int参数,那么槽函数也必须接受一个int参数。使用QObject::connect函数连接信号和槽时,Qt会在参数类型不匹配时进行编译时检查。
在这里插入图片描述

上述可知,connect的第2、4个参数为char*类型的指针,但是我们传参时传的确是函数指针,那么这两个是一个类型吗?

  • const char *signal:这个参数指定了信号的名称。在C++中,信号实际上是一个普通的C字符串,用来指示发送者正在发出的信号的名称。信号的名称通常是一个在发送者类中声明的函数名,但它们不是普通的C++函数,而是使用Qt宏来声明的,比如signals。因此,它们以字符串的形式传递给connect()函数。

  • const char *member:这个参数指定了槽函数的名称。与信号类似,槽函数也是作为普通的C字符串传递给connect()函数的。槽函数是接收者类中的一个普通函数,用于处理信号发出的动作。

总的来说,const char *signal和const char *member参数中使用字符串表示信号和槽函数的名称是因为在C++中,无法直接通过函数指针来传递函数的名称。因此,Qt 使用字符串来标识信号和槽函数的名称,然后在运行时使用反射机制来建立连接。

  • 如上述第三点所说,采用
QObject::connect(sender, SIGNAL(mySignal(int)), receiver, SLOT(mySlot(int)));

宏编程将传入的函数指针转成char* ,这样是不安全的:

  1. 类型检查缺失:在编译时,编译器不会检查信号和槽的参数类型和数量是否匹配。这可能导致运行时错误,如果信号和槽的参数不匹配,但在编译时却不会报错。

  2. 字符串拼写错误:由于 SIGNALSLOT 宏接收的是字符串字面量,因此如果信号或槽的名称拼写错误,编译器也不会报错,直到运行时才会出现问题。

  3. 不易读和维护:使用字符串字面量来表示信号和槽的名称不如直接使用函数指针或成员函数指针来得直观和易于维护。

在 Qt 5 中,引入了新的基于函数指针的信号和槽连接语法,这种语法具有以下优势:

  1. 类型安全:新的连接语法在编译时会检查信号和槽的参数类型和数量是否匹配,从而避免了因类型不匹配而导致的运行时错误。(假如传入的第一个参数和第二个参数不匹配,或者第三个和第四个参数不匹配(不匹配指的是:2、4参数的函数指针,不是1、3参数的成员函数)会编译出错)

  2. 易读和易维护:使用函数指针或成员函数指针来指定信号和槽使得代码更加直观和易于理解。此外,当信号或槽的签名发生更改时,编译器会立即报错,从而更容易发现和修复问题。

  3. 支持 lambda 表达式:新的连接语法还支持使用 lambda 表达式作为槽,这使得可以在连接时直接定义槽的行为,而无需单独定义一个槽函数。

使用Lambda表达式连接信号和槽

复习lambda表达式
在这里插入图片描述

总结
通过信号和槽,Qt提供了一种简单而强大的对象间通信机制。它使得编写灵活、可扩展的应用程序变得更加容易。通过本文的介绍,希望您对Qt中的信号和槽有了更深入的理解,并能够在实际项目中灵活运用它们。

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt信号机制是其最重要的特性之一,它提供了一种灵活而高效的通信方式。信号可以在不同对象之间进行通信,使得这些对象能够相互响应和交互。 信号Qt定义的一种特殊的函数,用于表示某个事件的发生。一个类可以定义一个或多个信号信号可以在特定的情况下被触发,比如用户点击了一个按钮或者其他的操作发生了一些特定的事件。信号的声明通常位于类的头文件是一个特殊的成员函数,用于接收信号并对其进行响应。一个类可以定义一个或多个函数,用于处理不同的信号函数可以有任意的参数和返回值,但是需要与信号的参数列表和返回值类型匹配。函数的声明通常位于类的头文件或者源文件信号通过Qt的元对象系统来进行连接。当信号被触发时,与之相关联的函数会被调用。信号之间的连接可以通过Qt提供的connect函数来实现,也可以在Qt Creator通过可视化界面来进行连接。 信号之间的连接是动态的,可以在运行时进行创建、修改和断开。这种机制使得对象之间的通信变得非常灵活,能够很好地支持Qt的事件驱动编程模型。 总结起来,Qt信号机制是一种通过信号函数来实现对象间通信的灵活机制信号用于表示事件的发生,函数用于对信号进行响应。通过Qt的元对象系统,可以在运行时动态地连接、修改和断开信号之间的关联。这种机制使得对象之间的通信变得简单而高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值