Qt信号与槽

Qt信号与槽_第1张图片

"流云开一朵~" 


        如果你系统地学习过Linux,你一定对“信号”一词不会感到陌生。当我们打开命令行终端,在键盘上敲击“Ctrl +C”、“Ctrl+Z”等组合键,当前命令行执行的程序就会收到系统发出的信号。信号的三要素:信号源、信号类型、信号处理方式,其在Qt中也有信号的概念,它与操作系统层面上的信号有何区别呢?

——前言 


 信号和槽概述

        在 Qt 中,⽤⼾和控件的每次交互过程称为⼀个事件。“用户关闭窗口”是一个实践,“用户”点击按钮是一个事件。每个事件都会发出⼀个信号,例如⽤⼾点击按钮会发出 "按钮被点击" 的号,用户关闭窗口时会发出“窗口被关闭”的信号。

        信号发出只是一个过程,或者是只是一种前提。当我们看到红绿灯时,灯光发出红光是一种信号,但我们更要关心的是,这种信号对应要做出的行为是,“停在白线以内,等待绿灯亮起”。显然,这种行为是约定俗成的规定。

        Qt中的所有控件都具有接收信号的能力,⼀个控件还可以接收多个不同的信号。对于接收到的每一个信号,都会做出相应的响应动作。而在 Qt 中,对信号做出的响应动作就称之为槽

         因此,Qt中的信号槽机制,就是给用户发起的事件,添加处理函数。        

Qt信号与槽_第2张图片

信号与槽关联函数

        当你为一个信号,量身定做了一个函数。可是,你却不让这个信号知道,自己有一个DIY。就像你不告诉小孩子,“在绿灯的条件下,路过斑马线”,而顽皮的孩童只知道“过斑马线”。

Connect函数        

QMetaObject::Connection QObject::connect(const QObject *sender, 
    const char *signal, 
    const QObject *receiver, 
    const char *method, Qt::ConnectionType type = Qt::AutoConnection)

# Sender: 信号发出者
# signal: 信号类型
# recviver: 信号接收者
# method: 槽函数(信号处理方法) 

        注:在Linux中也有一个connect函数,只不过它是在网络编程TCP之中。两者不是同一类


槽的本质

        槽(Slot)就是对信号响应的函数。槽就是⼀个函数,与⼀般的 C++ 函数是⼀样的,可以定义在类的任何位置。可以具有任何参数,可以被重载,也可以被直接调⽤。

        槽函数与⼀般的函数不同的是:

        槽函数可以与⼀个信号关联,当信号发射时,关联的槽函数会⾃动执⾏

槽函数与信号函数

 信号和槽机制底层是通过函数间的相互调⽤实现的:

        每个信号都可以⽤函数来表⽰,称为信号函数;每个槽也可以⽤函数表⽰,称为槽函数。

 信号函数和槽函数通常位于某个类中:

        信号函数⽤ signals 关键字修饰,槽函数⽤public slots、protected slots或者private slots修饰。

信号函数只需要声明,不需要定义(实现),⽽槽函数需要定义(实现)。


信号与槽的使用

        建立信号与槽的联系,我们可以使用两种方式:

使用代码建立

connect(myButton,&QPushButton::clicked,this,&Widget::handler_button);

slots:
void Widget::handler_button()
{
    if(myButton->text() == "hello qt"){
        myButton->setText("hello world");
    }
    else{
        myButton->setText("hello qt");
    }
}

Qt信号与槽_第3张图片

         当我们运行程序后,点击该按钮时,就会发出信号。

Qt信号与槽_第4张图片

        接收信号,执行我们connect好的槽函数,完成整个信号处理:

Qt信号与槽_第5张图片

使用ui建立        Qt信号与槽_第6张图片

        选择”转到槽“: 

Qt信号与槽_第7张图片

              Qt信号与槽_第8张图片  

        选择“clicked()”函数,这是QPushButto组件里自带的信号函数。

Qt信号与槽_第9张图片

        点击确定后,会在Widget文件下,创建一个新的函数——“槽函数”。

        Qt信号与槽_第10张图片

Qt信号与槽_第11张图片

         点击按钮后,文本框里的内容发生改变:
   Qt信号与槽_第12张图片

        我们在编写Qt代码时,使用、控制控件的方式有两种,一种是代码的方式,另一种则是使用ui。ui方式不过是使用xml格式,最终还是会转为C++文件,等待编译。

        至于选择哪一种,就看谁方便,就用哪一种。 


内置信号与槽         

        我们通过代码能够直观地使用信号与槽了,可是,我作为一个初学者,咋知道要选择Clicked()作为信号函数呢?这个问题,我们得打开Qt 为我们使用者提供的官方文档:
Qt信号与槽_第13张图片

                Qt信号与槽_第14张图片

        去Contents中寻找“signals”,如果每找到,就去它继承类查找:
Qt信号与槽_第15张图片

        进入QAbstractButton后,我们就能够在Content中找到Signals了,其中包含了如下内置的信号函数、槽函数。             

Qt信号与槽_第16张图片        在Qt中如果想要让某一个类能够使用信号槽,要在类最开始的地方写下:“Q_OBJECT”                  Qt信号与槽_第17张图片  

自动生成槽函数:

        在通过ui方式建立信号与槽函数的连接时,我们发现“转到槽”生效后,会自动生成:
 函数。

⾃动⽣成槽函数的名称有⼀定的规则。槽函数的命名规则为:on_XXX_SSS,其中:

 以 " on " 开头,中间使⽤下划线连接起来;

 " XXX " 表⽰的是对象名(控件的 objectName 属性);

 " SSS " 表⽰的是对应的信号。

     正如:" on_pushButton_clicked() " ,pushButton 代表的是对象名,clicked 是对应的信号。

        按照这种命名⻛格定义的槽函数, 就会被 Qt ⾃动的和对应的信号进⾏连接。但一般情况下,除非是IDE自动生成。我们手动进行connect更好,⼀⽅⾯显式 connect 可以更清晰直观的描述信号和槽的连接关系;另⼀⽅⾯也防⽌信号或者槽的名字拼写错误导致连接失效。

再谈connect函数:

        在前面,我们谈及了connect函数是什么,以及它的用法:

        我们也知道函数传参是这个过程:
Qt信号与槽_第18张图片

        但char* 是一个指针类型,函数指针又是另外一种指针类型,两者压根不是同类型的,怎么可能进行赋值呢?        

        我们使用Qt 帮助手册中查到,要使用这个connect函数,进行函数传递的时候,需要搭配SINGAL、SLOT宏,这些宏才能够让我们传递的函数指针转变为char*。但,在之前我们压根没有使用SLOT、SIGNAL宏完成这一步必要的转换啊!

        从Qt 5开始,上述写法做出了简化,不必再使用SLOT、SINGAL宏了。connect提供了重载版本,在这个重载版本中,第二个和第四个参数成为了泛型参数,允许传入任意指针类型了:

        这里泛型参数使用了封装的类型萃取器,能够在编译时得到传入参数的类型,当然这不是本篇要细说的。 

        此时的connect函数还具备了一定的 “参数检查功能”,一旦传入的第一个第二个、第三个第四个形参类型不匹配,就会编译报错。


自定义信号与槽

        在 Qt 中,允许⾃定义信号的发送⽅以及接收⽅,即可以⾃定义信号函数和槽函数。但是对于⾃定义的信号函数和槽函数有⼀定的书写规范。

⾃定义信号函数书写规范:

⾃定义信号函数必须写到 "signals" 下.
返回值必须为void,只需要声明,不需要实现.
可以有参数,也可以发⽣重载.
       
        作为信号是一种非常特殊的函数,程序员只需要写出函数声明,告诉Qt这是一个信号即可。这个函数的定义,会在Qt的编译过程中自动生成。

⾃定义槽函数书写规范:

 早期的 Qt 版本要求槽函数必须写到 "public slots" 下但是现在⾼级版本的 Qt 允许写到类“public”作用域或者全局下.

 返回值为 void,需要声明,也需要实现.

  可以有参数,可以发⽣重载.
         使⽤ "emit" 关键字发送信号 "emit" 是⼀个空的宏。"emit" 其实是可选的,没有什么含义,只 是为了提醒开发⼈员。同样,直接替代使用函数名也是可以的,但尽量选择前者。

建立自定义信号与槽:

        在 widget.h 中声明⾃定义的信号和槽,如图所⽰:

Qt信号与槽_第19张图片

        在 widget.cpp 中实现槽函数,并且关联信号和槽:

Qt信号与槽_第20张图片

                

带参数的信号与槽:

        Qt 的信号和槽也⽀持带有参数, 同时也可以⽀持重载。此处我们要求:

        信号函数的参数列表要和对应连接的槽函数参数列表⼀致,通过这样的机制, 就可以让信号给槽传递数据。

        重载信号槽和信号函数:

Qt信号与槽_第21张图片


多对多

       信号函数与槽函数的连接方式有哪几种呢?

Qt信号与槽_第22张图片

Qt信号与槽_第23张图片

Qt信号与槽_第24张图片 

        所以,一个结论:一个槽函数可以被多个信号绑定,一个信号可以绑定多个槽函数。 

        在实际写代码的过程中,我们也会发现信号函数与槽函数参数是有特点的: 

Qt信号与槽_第25张图片

        这是因为,一个槽函数可以被多个信号绑定。如果严格要求,信号函数的参数与槽函数参数一致,在这样的规则下,绑定会变得不够灵活。

        当与信号参数不一致时,至少能够拿到前N个参数,保证槽函数中的每个参数都是有值的。

        所以,一个结论:信号函数的参数个数多于或等于槽函数的参数个数

        

如何理解多对多? 

        把槽函数这个词放到系统编程中,通常被称为:“回调函数”。当事件机制被触发,就会执行回调函数。这种一对一的模式下,一个处理函数只能对应到一个事件上。

        Qt中一个信号可以选择多个槽函数,一个槽函数可以与多个信号关联,只要你不手动断开双方的连接,一旦信号发射,就会执行槽函数。此时,这个connect就如同一个关联表一样:记录着信号与槽函数关联的细节信息。

        Qt引入信号槽机制,本心是为了让“信号与槽”按照多对多的关系进行关联,从而能够应付复杂的现实问题。但, 在GUI图形化界面的开发过程中,压根不需要这种“复杂的需求”,“一对一”模式是仅够了的。


信号槽补充        

(1) 断开信号与槽       

        使⽤ disconnect 即可完成断开. disconnect 的⽤法和 connect 基本一致。
Qt信号与槽_第26张图片

(2) Lambda表达式

        Qt5 在 Qt4 的基础上提⾼了信号与槽的灵活性,允许使⽤任意函数作为槽函数。 但如果想⽅便的编写槽函数,⽐如在编写函数时连函数名都不想定义,则可以通过 Lambda表达式来达到这个⽬的。

        注:Lambda表达式不是本篇需要细讲的

Qt信号与槽_第27张图片

        当我们不断发射信号,就会执行connect中绑定的Lambda表达式。不过,Lambda表达式是C++11引入的,所以在负责编译的配置文件中,要加上:(默认是加了的)


信号与槽的优缺点

优点: 松散耦合

信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了⾃ ⼰,Qt的信号槽机制保证了信号与槽函数的调⽤。

⽀持信号槽机制的类或者⽗类必须继承于 QObject 类。 

缺点: 效率较低

与回调函数相⽐,信号和槽慢⼀些,因为它们提供更⾼的灵活性。但在实际应用中差别 不⼤。


总结:

① 什么是信号?什么是槽?

② Qt内置的信号与槽函数

③ 自定义信号与槽函数:不带参的、带参的

④ disconnect、Lambda用法简化槽函数定义


本篇到此结束,感谢你的阅读。

祝你好运,向阳而生~

Qt信号与槽_第28张图片

你可能感兴趣的:(QT,qt,开发语言)