signal/slot是Qt对象以及其派生类对象之间的一种高效通信接口,它是Qt的核心特性,也是区别与其他工具包的重要地方。它完全独立于标准的C/C++语言,因此用正确的处理好信号和槽,必须借助于一个成为MOC(Meta Object Compiler)的qt工具,该工具是一个C++预处理程序,能为高层次的事件处理自动生成所需要的附加代码。尽管它的机制很像回调函数,但是这里要注意它和与回调函数间的不同,回调函数传递的是函数指针,很容易造成程序崩溃,另一方面,回调方式紧紧的绑定了图形用户接口的功能元素,因此很难开发进行独立的分类。而signal/slot机制也能携带任意数量和任意参数,并且不会像回调函数那样产生core dumps。
信号signal和槽Slot是用来在对象间通讯的方法:当一个特定事件发生的时候,signal会被emit出来,slot调用是用来响应相应的signal的。QT对象已经包含了许多预定义的 signal,但我们总是可以在派生类中添加新的signal。QT对象中也已经包含了许多预定义的slog,但我们可以在派生类中添加新的slot来处理我们感兴趣的signal。
signal 和 slot 机制是类型安全的,signal 和 slot必须互相匹配(实际上,一个solt的参数可以比对应的signal的参数少,因为它可以忽略多余的参数)。signal 和 slot是松散的配对关系,发出signal的对象不关心是那个对象链接了 这个signal,也不关心是那个或者有多少slot链接到了这个 signal。QT的signal 和 slot机制保证了,如果一个signal和slot相链接,slot会在正确的时机被调用,并且是使用正确的参数。Signal和slot都可以携带任何数量和类型的参数,他们都是类型安全的。
所有从QObject直接或者间接继承出来的类都能包含信号和槽,当一个对象的状态发生变化的时候,信号就可以被emit出来,这可能是某个其它的 对象所 关心的。这个对象并不关心有那个对象或者多少个对象链接到这个信号了,这是真实的信息封装,它保证了这个对象可以作为一个软件组件来被使用。
槽(slot)是用来接收信号的,但同时他们也是一个普通的类成员函数,就象一个对象不关心有多少个槽链接到了它的某个信号,一个对象也不关心一个槽链接了多少个信号。这保证了用QT创建的对象是一个真实的独立的软件组件。
一个信号可以链接到多个槽,一个槽也可以链接多个信号。同时,一个信号也可以链接到另外一个信号。所有使用了信号和槽的类都必须包含 Q_OBJECT 宏,而且这个类必须从QObject类派生(直接或者间接派生)出来,
当一个signal被emit出来的时候,链接到这个signal的slot会立刻被调用,就好像是一个函数调用一样。当这件事情发生的时候,signal和slot机制与GUI的事件循环完全没有关系,当所有链接到这个signal的slot执行完成之后,在 emit 代码行之后的代码会立刻被执行。当有多个slot链接到一个signal的时候,这些slot会一个接着一个的、以随机的顺序被执行。
Signal 代码会由 moc自动生成,开发人员一定不能在自己的C++代码中实现它,并且它永远都不能有返回值。
Slot其实就是一个普通的类函数,并且可以被直接调用,唯一特殊的地方是它可以与signal相链接。
C++的预处理器更改或者删除 signal, slot, emit 关键字,所以,对于C++编译器来说,它处理的是标准的C++源文件。
此外,用户可以将N多个信号和单个槽相连接,或者将将N个槽和单个信号连接,甚至是一个信号和另外一个信号连接。这样,当信号发射时,所以与之相连的信号或者槽都会按一定的次序(没有预定的顺序,也就是说执行的先后顺序是随机的)执行,当所有与之相连的信号和槽返回后,emit才会返回。
下面是Qt中关于信号和槽的函数,Qt中信号的定义:
- siganls:
- void mySignal();
- void mySignal( int x );
- void mySignal( int x, int y );
其中signals是Qt的关键字,而不是C/C++的关键字。此外信号与一般函数的区别是,它的所有返回值都是void,并且它没有函数实现体,它的函数体是moc自动生成的。
Qt中槽的定义:
- public slots:
- void mySlot();
- void mySlot( int x );
- <span style="font-family:SimSun;font-size:18px;">不同类型的slot有不同的操作权限,具体看slot是public、protected还是private。</span>
Qt中信号与信号或者与槽的连接:
- QObeject::connect( obj1, SIGNAL( mySignal() ), obj2, SLOT( mySlot() ) );
- QObeject::connect( obj1, SIGNAL( mySignal() ), obj2, SIGNAL( mySignal2() ) );
Qt中信号与槽的断开:
- QObeject::disconnect( obj1, SIGNAL( mySignal() ), obj2, SLOT( mySlot() ) );
- QObeject::disconnect( obj1, SIGNAL( mySignal() ), obj2, SIGNAL( mySignal2() ) );
这种机制GUI控件的操作来说很是方便,当然也要用的恰当,用的规范和科学。
下面是一个简单的样例程序,程序中定义了三个信号和三个槽函数,然后将信号与槽进行了关联,每个槽函数都只是弹出一个窗口(widget),信号和槽函数的声明一般位于头文件中,同时在类声明的开始位置必须加上Q_OBJECT语句,这条语句是不可缺少的,它将告诉编译器在编译之前必须先应用 moc工具进行扩展。关键字signals指出随后开始信号的声明,这里signals用的是复数形式而非单数,siganls没有public、 private、protected等属性,这点不同于slots。另外,signals、slots关键字是QT自己定义的,不是C++中的关键字。信号的声明类似于函数的声明而非变量的声明,左边要有类型,右边要有括号,如果要向槽中传递参数的话,在括号中指定每个形式参数的类型,当然,形式参数的个数可以多于一个。
- //tsignal.h
- ...
- class TsignalApp:public QMainWindow
- {
- Q_OBJECT
- ...
- //信号声明区
- signals:
- //声明信号mySignal()
- void mySignal();
- //声明信号mySignal(int)
- void mySignal(int x);
- //声明信号mySignalParam(int,int)
- void mySignalParam(int x,int y);
- //槽声明区
- public slots:
- //声明槽函数mySlot()
- void mySlot();
- //声明槽函数mySlot(int)
- void mySlot(int x);
- //声明槽函数mySignalParam (int,int)
- void mySignalParam(int x,int y);
- }
- ...
- //tsignal.cpp
- ...
- TsignalApp::TsignalApp()
- {
- ...
- //将信号mySignal()与槽mySlot()相关联
- connect(this,SIGNAL(mySignal()),SLOT(mySlot()));
- //将信号mySignal(int)与槽mySlot(int)相关联
- connect(this,SIGNAL(mySignal(int)),SLOT(mySlot(int)));
- //将信号mySignalParam(int,int)与槽mySlotParam(int,int)相关联
- connect(this,SIGNAL(mySignalParam(int,int)),SLOT(mySlotParam(int,int)));
- }
- // 定义槽函数mySlot()
- void TsignalApp::mySlot()
- {
- QMessageBox::about(this,"Tsignal", "This is a signal/slot sample without
- parameter.");
- }
- // 定义槽函数mySlot(int)
- void TsignalApp::mySlot(int x)
- {
- QMessageBox::about(this,"Tsignal", "This is a signal/slot sample with one
- parameter.");
- }
- // 定义槽函数mySlotParam(int,int)
- void TsignalApp::mySlotParam(int x,int y)
- {
- char s[256];
- sprintf(s,"x:%d y:%d",x,y);
- QMessageBox::about(this,"Tsignal", s);
- }
- void TsignalApp::slotFileNew()
- {
- //发射信号mySignal()
- emit mySignal();
- //发射信号mySignal(int)
- emit mySignal(5);
- //发射信号mySignalParam(5,100)
- emit mySignalParam(5,100);
- }