QT基础:信号槽

1. 信号和槽概述

信号和槽是QT自行定义的一种通信机制,实现对象之间的数据交互

信号和槽的连接是通过connect函数实现的,该函数将发送者的信号与接收者的槽函数连接起来。当发送者发出信号时,所有与之连接的槽函数都会被调用。这种连接可以是多对多的,即一个信号可以连接多个槽,一个槽也可以接收多个信号。

信号的本质就是事件

  • 当用户在图形用户界面(GUI)中进行某些操作时,比如点击按钮、移动鼠标或输入键盘等,这些操作会产生特定的事件。Qt框架中的窗口或控件在识别到这些事件后,会发出相应的信号。

  • 信号的呈现形式就是函数, 也就是说某个事件产生了, Qt框架就会调用某个对应的信号函数, 通知使用者。

注意:信号函数可以只声明,不需要实现

槽函数是一类特殊的功能的函数

槽函数个职责就是对Qt框架中产生的信号进行处理。

槽和普通成员函数几乎没有太多区别,可以是共有的、保护的或私有的,可以被重载,也可以被覆盖,其参数可以是任意类型,并可以像普通成员函数一样调用

2. 信号和槽的使用

如何实现点击按钮后关闭整个窗口呢?

功能实现: 点击窗口上的按钮, 关闭窗口
功能分析:
	- 按钮: 信号发出者          -> QPushButton 类型
	- 窗口: 信号的接收者和处理者  -> QWidget 类型

需要使用的标准信号槽函数

// 单击按钮发出的信号
[signal] void QAbstractButton::clicked(bool checked = false)
// 关闭窗口的槽函数
[slot] bool QWidget::close();

只需要写一句代码就能实现这个效果

// 单击按钮关闭窗口
connect(ui->closewindow, &QPushButton::clicked, this, &MainWindow::close);

2.1 connect()函数

QMetaObject::Connection QObject::connect(
    	const QObject *sender, PointerToMemberFunction signal, 
        const QObject *receiver, PointerToMemberFunction method, 
		Qt::ConnectionType type = Qt::AutoConnection);
// PointerToMemberFunction 是一个指向函数地址的指针
/*参数:
  - sender:   发出信号的对象
  - signal:   属于sender对象, 信号是一个函数, 这个参数的类型是函数
              指针, 信号函数地址
  - receiver: 信号接收者
  - method:   属于receiver对象, 当检测到sender发出了signal信号, 
              receiver对象调用method方法,信号发出之后的处理动作
 */

//  参数 signal 和 method 都是函数地址, 因此简化之后的 connect() 如下:
connect(const QObject *sender, &QObject::signal, 
        const QObject *receiver, &QObject::method);

注意事项:

  • connect函数相对于做了信号处理动作的注册
  • 槽函数本质是一个回调函数,。当信号产生之后, 由Qt框架来执行对槽函数的调用
  • connect中的sender和recever两个指针必须被实例化了, 否则conenct不会成功
  • connect()操作一般写在窗口的构造函数中, 相当于事件产生之前在qt框架中先进行注册

使用方式

  • 一个信号可以连接多个槽函数, 发送一个信号有多个处理动作
    • 需要写多个connect()连接
    • 槽函数的执行顺序是随机的, 和connect函数的调用顺序没有关系
    • 信号的接收者可以是一个对象, 也可以是多个对象
  • 一个槽函数可以连接多个信号, 多个不同的信号, 处理动作是相同的
    • 需要写多个connect()连接

信号可以连接信号

  • 信号接收者可以不处理接收的信号, 而是继续发射新的信号,这相当于传递了数据, 并没有对数据进行处理
//信号之间可以连接(信号级联)
QObject::connect(A1, SIGNAL(sigfun(int)), A2, SIGNAL(sigfun(int)) );

信号槽是可以断开的

disconnect(const QObject *sender, &QObject::signal,
        const QObject *receiver, &QObject::method);
  • 注意:
    • connect连接的信号函数和槽函数的参数需要匹配
    • 槽函数的参数数量可以少于信号函数的参数数量

2.2 信号槽的连接方式

使用SIGNALSLOT

SIGNALSLOT宏无法检查函数名拼写是否正确,信号和槽的参数类型是否匹配,编译时无法发现错误,增加了调试难度

connect(sender, SIGNAL(signalSignature()), receiver, SLOT(slotSignature()));

// 单击按钮关闭窗口
connect(ui->testWindow, SIGNAL(clicked(void)), this, SLOT(close(void)) );

信号槽新语法

Qt 5开始支持新的语法,使用函数指针来作为参数。

推荐使用新的信号槽语法,它更加类型安全,并且支持Lambda表达式

connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slot);

// 单击按钮关闭窗口
connect(ui->closewindow, &QPushButton::clicked, this, &MainWindow::close);

// Lambda表达式
connect(ui->closewindow, &QPushButton::clicked, [=](){ this->close(); });

2.2.1 信号槽函数存在重载

// 信号和槽函数存在重载
class myclass : public QDialog{
...
signals:
    void sig(QString const& str);
    void sig(); 
public slots:
    void slo(QString const& str);
    void slo();
...};
  • 当槽函数存在重载时,使用函数指针作为参数,不能直接对函数名取地址

方式一、定义函数指针

// 举例:
void (myclass::*sigString)(const QString &) = &myclass::sig;	//信号函数指针
void (myclass::*sloString)(const QString &) = &myclass::slo;	//槽函数指针

// 使用定义的函数指针完成信号槽的连接
connect(this, sigString, this, sloString);

方式二、static_cast函数指针类型转换

通过显示类型转换可以让编译器找到对应的重载函数

connect(this,static_cast<void (myclass::*)(const QString &)>(&myclass::sig),
		this,static_cast<void (myclass::*)(const QString &)>(&myclass::slo));

3. 自定义信号槽

Qt 的元对象系统是实现信号和槽机制的基础。每个使用信号和槽的 Qt 类都需要从 QObject 类继承,并且使用 Q_OBJECT 宏。这个宏在编译时会生成一些额外的代码,包括元对象信息(meta-object information),这些信息在运行时用于信号和槽的连接。

自定义信号槽, 需要满足一些条件, 并且有些事项也需要注意:

  • 要编写新的类并且让其继承Qt的某些标准类
  • 这个新的子类必须从QObject类或者是QObject子类进行派生
  • 在定义类的头文件中加入Q_OBJECT
// 在头文件派生类的时候,首先像下面那样引入Q_OBJECT宏:
class MyMainWindow : public QWidget
{
    Q_OBJECT
    ......
}

3.1 自定义信号

信号函数只有声明, 无需定义

class X:public QObject{
signals:
    void signal_func(...);	//	信号函数
};

自定义信号的要求和注意事项:

  1. 信号是类的成员函数,支持重载,返回值必须是 void 类型

  2. 信号需要使用signals关键字进行声明

    1. 早期版本中(如Qt 4),信号被默认设置为受保护的(protected)
    2. 从Qt 5.0开始,信号被更改为公有(public)成员
  3. 信号函数只需要声明, 不需要定义(没有函数体实现)

  4. 发送信号的本质就是调用信号函数

    • 关键字emit显示声明信号被发射
// 举例: 信号重载
// Qt中的类想要使用信号槽机制必须要从QObject类派生(直接或间接派生都可以)
class Test : public QObject
{
    Q_OBJECT
signals:
    void signal_func();
	// 参数的作用是数据传递, 谁调用信号函数谁就指定实参
	// 实参最终会被传递给槽函数
    void signal_func(int a);
};

4.2 自定义槽

自定义槽函数和自定义的普通函数写法是一样的。

从Qt 5开始,对于槽函数的声明,可以省略slots关键字。普通成员函数也可以连接到信号,不再需要声明为槽。

// 槽函数书写格式举例
// 类中的这三个函数都可以作为槽函数来使用
class Test : public QObject
{
    public:
    void testSlot();
    static void testFunc();

    public slots:
    void testSlot(int id);
};

自定义槽的要求和注意事项:

  1. 槽函数支持重载,返回值必须是void类型

  2. 槽函数的参数是用来接收信号函数的参数

  3. 槽函数的类型可以是类的成员函数全局函数静态函数Lambda表达式(匿名函数)

5. Lambda表达式

Lambda表达式是 C++ 11 最重要也是最常用的特性之一,是现代编程语言的一个特点,简洁,提高了代码的效率并且可以使程序更加灵活,Qt是完全支持c++语法的, 因此在Qt中也可以使用Lambda表达式。

5.1 语法格式

Lambda表达式就是一个匿名函数, 语法格式如下:

[capture](params) opt -> ret {body;};
    - capture: 捕获列表
    - params: 参数列表
    - opt: 函数选项
    - ret: 返回值类型
    - body: 函数体

关于Lambda表达式的细节介绍:

  1. 捕获列表: 捕获一定范围内的变量
    • [] - 不捕捉任何变量
    • [&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
    • [=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获)
      • 拷贝的副本在匿名函数体内部是只读的
    • [=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量foo
    • [bar] - 按值捕获bar变量, 同时不捕获其他变量
    • [&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
    • [this]- 捕获当前类中的this指针
      • lambda表达式拥有和当前类成员函数同样的访问权限
      • 如果已经使用了 & 或者 =, 默认添加此选项
  2. 参数列表: 和普通函数的参数列表一样
  3. opt 选项 –> 可以省略
    • mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
    • exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();
  4. 返回值类型:
    • 标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略
  5. 函数体:
    • 函数的实现,这部分不能省略,但函数体可以为空。

5.2 定义和调用

因为Lambda表达式是一个匿名函数, 因此是没有函数声明的, 直接在程序中进行代码的定义即可, 但是如果只定义匿名函数在程序执行过程中是不会被调用的。

// 匿名函数的定义, 程序执行这个匿名函数是不会被调用的
[](){
    qDebug() << "hello, 我是一个lambda表达式...";
};

// 匿名函数的定义+调用:
int ret = [](int a) -> int
{
    return a+1;
}(100);  // 100是传递给匿名函数的参数

Lambda表达式的捕获列表中也就是 []内部添加不同的关键字, 就可以在函数体中使用外部变量了。

// 在匿名函数外部定义变量
int a=100, b=200, c=300;
// 调用匿名函数
[](){
    // 打印外部变量的值
    qDebug() << "a:" << a << ", b: " << b << ", c:" << c;  // error, 不能使用任何外部变量
}

[&](){
    qDebug() << "hello, 我是一个lambda表达式...";
    qDebug() << "使用引用的方式传递数据: ";
    qDebug() << "a+1:" << a++ << ", b+c= " << b+c;
}();

// 值拷贝的方式使用外部数据
[=](int m, int n)mutable{
    qDebug() << "hello, 我是一个lambda表达式...";
    qDebug() << "使用拷贝的方式传递数据: ";
    // 拷贝的外部数据在函数体内部是只读的, 如果不添加 mutable 关键字是不能修改这些只读数据的值的
    // 添加 mutable 允许修改的数据是拷贝到函数内部的副本, 对外部数据没有影响
    qDebug() << "a+1:" << a++ << ", b+c= " << b+c;
    qDebug() << "m+1: " << ++m << ", n: " << n;
}(1, 2);

posted @ 2024-12-21 16:16  -O-n-e-  阅读(304)  评论(0)    收藏  举报