信号和槽的机制是Qt编程的基本方法。它使程序员能把两个对象绑定在一起,而对象之间并不用知道彼此的存在。我们已经把一些信号和槽链接在一起了,并声明了我们自己的信号和槽,实现了我们自己的槽,发送了我们自己的信号。让我们花些时间更深的研究这个机制。
槽跟传统的C++成员函数基本一致。槽可以是虚函数;可以被重载;可以是公有的,保护的,或者私有的,可以向其它C++成员函数一样直接被调用;它们的形参可以是任何类型。不同之处在于一个槽可以到一个信号,当信号被发送时槽就自动被调用。
connect()语句像下面这样:
发送者(sender)和接收者(receiver)都是指向QObject对象的指针。信号和槽是没有形参名的函数签名。SIGNAL()和SLOT宏其实把它们的实参转化为一个字符串。
在我们看过的例子中,我们总是把不同的信号和不同的槽连接在一起。但还有如下的各种不同的形式的连接。
一个信号可以被连接到许多不同的槽:
当这个信号被发送时,槽就以不确定的顺序相继被调用。
多个信号可以被连接到同一个槽:
当任一个信号被发送时,槽就被调用。
一个信号可以与另一个信号连接:
当第一个信号被发送时,第二个信号也被发送。此外,信号之间的连接(signalsignal connections)与信号和槽的连接(signalslot connections)是难以区分的。
连接可以被去除
这很少用到,因为当一个对象被delete时Qt会把此对象相关的所有的连接都去掉。
为了成功的把一个信号和一个槽连接起来(或者两个信号之间的连接),它们必须都有相同的形参表,即同样的形参类型和顺序:
有一个例外是如果一个信号的形参个数比它连接的槽的形参个数多,则多余的形参被简单的省略:
如果形参类型不匹配,或者如果这个信号和槽不存在,如果程序在debug模式下建立的Qt就会发出一个运行时警告。同样的,如果形参名出现在信号或槽的签名中Qt也将发出警告。
到目前为止,我们仅仅对widgets使用了信号和槽。但是这种机制不仅局限在GUI编程,也在QObject有所实现。这种机制可以被任何QObject的子类使用:
注意到setSalary()是如何实现的。我们仅仅在newSalary != mySalary时发出了salary-Changed()信号。这确保了这个循环的连接不会导致无限循环。
Qt的原元对象系统(Meta-Object System)
Qt的一个主要成就是实现了创建独立的软件组件的机制,这种机制在组件之间互相不知道彼此存在的情况下绑定组件,这大大扩展了基本的C++。
这种机制成为元对象系统,它提供了两个关键的服务:信号和槽,以及反射。信号和槽的实现离不开反射的功能,并能使程序员在运行时得到继承自QObject的类的元信息,包括了这个类支持的信号和槽的列表和类本身的名字。这种机制也支持Qt Designer的属性(properties)和国际化(for internationalization)的文本翻译。而且还为Qt Script的应用(QSA)奠定了基础。
标准C++并不提供Qt元对象系统需要的动态元信息的支持。Qt提供了一个分离的工具解决了这个问题,就是moc,它能解析在类定义中的Q_OBJECT宏并通过C++函数的形式使这些信息可用。因为moc用纯C++实现了所有的的功能,所以Qt的元对象系统能使用任何C++编译器工作。
这种机制如下面的描述工作:
Q_OBJECT宏声明了一些每个QObject的子类必须实现的反射函数:metaObject(),TR(),qt_metacall(),等等。
Qt的moc工具实现了在Q_OBJECT声明的一些函数和所有的信号。
QObject类的成员函数诸如connect()和disconnect()使用反射函数来完成它们的工作。
上述这些都自动被qmake,moc,QObject所掌控,所以你很少会思考这方面的问题。但是如果你很好奇,你可以查看QMetaObject类的文档,且也可以通过查看moc生成的C++源码来看看这些实现是如何工作的。