信号与槽机制
信号是公共访问函数,可以从任何地方发出,建议只从定义信号的类及其子类发出信号。如果几个插槽连接到一个信号上,当信号发出时,这些插槽将按照它们连接的顺序依次执行。信号是由moc自动生成的,不能在.cpp文件中实现。它们永远不能有返回类型(例如,使用void)。
当一个连接到插槽的信号被发射时,它被调用。槽是普通的c++函数,可以正常调用;它们唯一的特点是,信号可以与它们相连。由于槽是普通的成员函数,所以它们在直接调用时遵循普通的c++规则。但是,作为插槽,任何组件都可以通过信号-插槽连接调用它们,而不管其访问级别如何。这意味着从任意类的实例发出的信号可能导致在不相关类的实例中调用私有槽。
与回调相比,信号和插槽的速度稍微慢一些,因为它们提供了更高的灵活性,尽管对于实际应用程序来说,这种差别并不显著。一般来说,发送一个连接到某些插槽的信号,比直接调用非虚函数要慢大约10倍。这是定位连接对象、安全地遍历所有连接(即检查后续接收方在发射过程中没有被销毁)以及按次序排列任何参数所需的开销。虽然10个非虚函数调用听起来很多,但是它比任何新操作或删除操作的开销要小得多。一旦您在后台执行一个需要新建或删除的字符串、向量或列表操作,信号和插槽开销只占整个函数调用开销的很小一部分。在槽中执行系统调用时也是如此。信号和插槽机制的简单性和灵活性是值得的,这些开销用户甚至不会注意到。
基于Qobject基类的类定义如下:
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
private:
int m_value;
};
这个类可以通过发送一个信号valueChanged()告诉外部世界它的状态已经改变,并且它有一个槽,其他对象可以将信号发送到这个槽。所有包含信号或槽的类都必须在声明的顶部包含宏Q_OBJECT。它们还必须(直接或间接)从QObject派生。
槽是由程序员实现的,上面的槽可实现如下:
void Counter::setValue(int value)
{
if (value != m_value) {
m_value = value;
emit valueChanged(value);
}
}
上例中emit行从对象中发出信号valueChanged(),并将新值作为参数。在下面的代码片段中,创建了两个计数器对象,并使用QObject::connect()将第一个对象的valueChanged()信号连接到第二个对象的setValue()槽
Counter a, b;
QObject::connect(&a, &Counter::valueChanged,\
&b, &Counter::setValue);
a.setValue(12); // a.value() == 12, b.value() == 12
b.setValue(48); // a.value() == 12, b.value() == 48
调用a.setValue(12)会使a发出一个valueChanged(12)信号,b将在其setValue()插槽中接收到这个信号,即调用b.setValue(12)。然后b发出相同的valueChanged()信号,但由于没有插槽连接到b的valueChanged()信号,因此该信号被忽略。注意,setValue()函数设置值并仅在value != m_value时发出信号。这可以防止在循环连接的情况下出现无限循环(例如,如果b.valueChanged()连接到a.setValue())。
一个实例:
#ifndef LCDNUMBER_H
#define LCDNUMBER_H
#include <QFrame>
class LcdNumber : public QFrame
{
Q_OBJECT
public:
LcdNumber(QWidget *parent = 0);
signals:
void overflow();
public slots:
void display(int num);
void display(double num);
void display(const QString &str);
void setHexMode();
void setDecMode();
void setOctMode();
void setBinMode();
void setSmallDecimalPoint(bool point);
};
#endif