03_信号与槽

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

一、信号

1. 基本概念

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

2.Qt框架类信号

在组件变量上按F1,可以打开帮助文档,查看此组件拥有的signals信号。有时候需要查看继承的父类中去查找可以使用哪些信号。

image

在ui文件上,右键点击组件,点击转到槽,也可以查看到可以使用的信号。

image

3.自定义信号

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

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

如下图所示,为自定义信号的定义及使用方法:在头文件中signals关键字下,进行申明。在app源文件中使用 emit来进行触发信号。

image

注意:只有继续自QObject的类及子类,才有信号与槽的功能。普通类无法使用信号与槽。

二、槽

1. 基本概念

槽是Qt对象中的一个成员函数,它可以被信号调用。当信号被发出时,与之关联的槽函数将被执行。槽函数可以

有返回值和参数,这使得它们比信号更加灵活。槽函数可以被任何对象调用,但通常它们是由信号触发的。

2.Qt框架类槽函数

在组件变量上按F1,可以打开帮助文档,查看此组件拥有的slots槽。有时候需要查看继承的父类中去查找可以使用哪些槽。

image

在ui组件上,右键转到槽,选择信号后,自动创建槽函数,并且自动与组件的信号进行关联。只需要实现槽函数即可。

image

3.自定义槽函数

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

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

如下图所示,为自定义槽函数的定义及使用方法:定义与实现都与普通的成员函数无差别。建议是使用public slots: ,以及参数不能多于要关联的信号。

image

三、信号与槽的关联

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

1.使用SIGNAL和SLOT宏(<=Qt4)

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

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

2.使用新语法(>=Qt5)

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

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

3.新老关联语法的区别

采用 QObject::connect(sender, SIGNAL(mySignal(int)), receiver, SLOT(mySlot(int)));​宏编程将传入的函数指针转成char* ,这样是不安全的:

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

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

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

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

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

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

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

4.Lambda表达式

Lambda表达式1

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

image

四、信号与槽参数传递

自定义信号的函数参数个数必须 >= 自定义槽函数的参数

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

image

上述可知,`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 使用字符串来标识信号和槽函数的名称,然后在运行时使用反射机制来建立连接。


  1. Lambda表达式

    C++

    在 C++ 中,Lambda 表达式(也称为匿名函数或闭包)是一个可以捕获局部变量的简短函数对象。Lambda 表达式可以定义在任何需要函数对象或可调用对象的地方,它们特别适用于需要定义小型、一次性的函数对象的情况。

    下面是一个 Lambda 表达式的基本结构和解析:

    1.基本语法

    [capture](parameters) -> return-type { body }
    
    • [capture]: 捕获子句,用于指定 Lambda 表达式体中可以使用哪些外部变量(按值或按引用)。
    • (parameters): 参数列表,与普通函数相同。
    • -> return-type: 可选的返回类型说明符,用于指定 Lambda 表达式的返回类型。如果 Lambda 表达式的体只包含一个返回语句,并且编译器能够推断出返回类型,则可以省略该部分。
    • { body }: Lambda 表达式的主体,包含函数体的代码。

    2.捕获子句

    捕获子句决定了哪些外部变量可以在 Lambda 表达式的体内被访问。捕获可以是按值(=)或按引用(&),或者是显式指定变量(例如 [x, &y])。

    • []​:不捕获任何变量。
    • [=]​:以值捕获所有外部变量。
    • [&]​:以引用捕获所有外部变量。
    • [x, &y]​:按值捕获 x​,按引用捕获 y​。

    3.使用示例

    简单的Lambda表达式

    这里定义了一个没有捕获任何外部变量的Lambda表达式,并在定义后立即调用。

    [] { std::cout << "Hello, World!<<std::endl"; }();
    

    image

    捕获外部变量

    在这个例子中,a​被值捕获,b​被引用捕获。Lambda表达式内部可以修改b​的值。

    int a = 1;
    int b = 2;
    auto lambda = [a, &b]() { b = a + b; };
    lambda();
    std::cout << b; // 输出 3,因为a被值捕获,b被引用捕获
    

    使用捕获列表

    Lambda表达式捕获了x​的值,并在调用时将其与参数y​相加。

    int x = 10;
    auto lambda = [x](int y) { return x + y; };
    std::cout << lambda(5); // 输出 15
    

    使用默认捕获

    [=]​表示默认以值的方式捕获所有外部变量。

    int x = 10, y = 20;
    auto lambda = [=](int z) { return x + y + z; };
    std::cout << lambda(5); // 输出 35
    

    使用mutable关键字

    默认情况下,值捕获的变量在Lambda内部是常量。如果需要修改它们,可以使用mutable​关键字。

    int count = 0;
    auto lambda = [count]() mutable { return ++count; };
    std::cout << lambda() << std::endl; // 输出 1
    std::cout << lambda() << std::endl; // 输出 2
    

    即使count​被值捕获,mutable​关键字允许我们在Lambda内部修改它。

    使用尾返回类型

    如果Lambda表达式的返回类型无法自动推导,或者返回类型比较复杂,可以使用尾返回类型。这里显式指定了Lambda表达式的返回类型为int​。

    auto lambda = [](int a, int b) -> int { return a + b; };
    std::cout << lambda(5, 3); // 输出 8
    

    Lambda 表达式提供了一种简洁、灵活的方式来定义和使用小型函数对象,使 C++ 代码更加简洁和易读。

    Qt

    1.基本语法

    在Qt中,Lambda表达式是一种方便的方式来定义匿名函数,通常用于连接信号和槽以及其他需要函数对象的地方。Lambda表达式提供了一种更简洁的方式来编写函数对象,而不必显式地编写函数定义。下面是对Qt中Lambda表达式的解析:

    [ captures ]( parameters ) mutable(optional) noexcept(optional) -> return_type(optional) { body }
    
    • captures: 捕获列表,用于捕获外部变量。可以是值捕获、引用捕获或混合捕获。
    • parameters: 参数列表,用于指定Lambda函数的参数。
    • mutable: 可选关键字,指示Lambda函数是否可以修改其捕获的变量。
    • noexcept: 可选关键字,指示Lambda函数是否不会抛出异常。
    • return_type: 可选的返回类型说明符,用于指定Lambda函数的返回类型。
    • body: Lambda函数的主体,即函数体。

    2.使用示例

    Lambda表达式通常用于连接信号和槽,以及在需要函数对象的地方,例如STL算法和标准库函数中。

    连接信号和槽

    在Qt中,Lambda表达式通常用于连接信号和槽,如下所示:

    QObject::connect(&button, &QPushButton::clicked, [&button]() {
            // 在Lambda表达式中,我们可以使用捕获的button对象
            button.setText("Clicked!");
            qDebug() << "Button was clicked!";
        });
    

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享