Qt编程入门(1) : 信号和槽

来源:互联网 发布:微博软件加人软件 编辑:程序博客网 时间:2024/04/28 03:09

信号槽是Qt的核心通信机制,类似于一个发送天线,向四面八方发送信号,任何人都可能接收到;槽函数类似于一个收音机,只有该收音机将广播调制到特定的频率上才能接收到对应的广播,频率就是信号槽的连接。当某个特定的对象发送信号时,与之关联的某个槽函数就会被执行。

信号槽原理

Qt的信号signals是一个特殊的宏,而槽函数则和普通的函数没有什么区别。qmake会在编译器之前先行执行,将signals宏展开为C++函数,然后将信号和槽的连接生成为一个带有moc_前缀的cpp文件,里面产生C++代码被包含进行项目中提供给编译器编译。

textChanged信号在moc文件中被变形为:

// SIGNAL 0int Widget::textChanged(const QString & _t1){    int _t0 = int();    void *_a[] = { const_cast<void*>(reinterpret_cast<const void*>(&_t0)), const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };    QMetaObject::activate(this, &staticMetaObject, 0, _a);    return _t0;}

其实signals就是一函数,不过在调用QMetaObject::activate(this, &staticMetaObject, 0, _a);之后转到对应的槽函数中去执行罢了。

connect函数在moc文件中被变形为:

void Widget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a){    if (_c == QMetaObject::InvokeMetaMethod) {        Widget *_t = static_cast<Widget *>(_o);        Q_UNUSED(_t)        switch (_id) {        case 0: { int _r = _t->textChanged((*reinterpret_cast< const QString(*)>(_a[1])));            if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; }  break;        case 1: { int _r = _t->textChanged((*reinterpret_cast< const myclass(*)>(_a[1])));            if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; }  break;        case 2: _t->on_pushButton_clicked(); break;        case 3: _t->onBtnClicked(); break;        case 4: _t->onTextChanged((*reinterpret_cast< const QString(*)>(_a[1]))); break;        case 5: _t->onTextChanged((*reinterpret_cast< const myclass(*)>(_a[1]))); break;        default: ;        }    } else if (_c == QMetaObject::IndexOfMethod) {        int *result = reinterpret_cast<int *>(_a[0]);        void **func = reinterpret_cast<void **>(_a[1]);        {            typedef int (Widget::*_t)(const QString & );            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Widget::textChanged)) {                *result = 0;                return;            }        }        {            typedef int (Widget::*_t)(const myclass & );            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Widget::textChanged)) {                *result = 1;                return;            }        }    }}

这里已经明确的告诉编译器那个信号要调用什么样的槽函数了。

信号槽的连接形式

信号和槽的连接有多种方式,这里只列举最常见的4种形式:

  1. 信号->槽函数,原型:connect(sender, signal, receiver, slot)
    connect(btn, SIGNAL(clicked(bool)), this, SLOT(onBtnClicked()));
    当对象btn发送clicked信号后,this对象的onBtnClicked()函数被执行。

  2. 信号->信号,原型:connect(sender, signal, receiver, signal)
    connect(edit, SIGNAL(textChanged(QString)), this, SIGNAL(textChanged(QString)));
    当edit对象发送textChanged信号后,this对象的textChanged()函数被执行。

  3. 多个信号->同一个槽函数
    connect(btn, SIGNAL(clicked(bool)), this, SLOT(onBtnClicked()));
    connect(edit, SIGNAL(editingFinished()), this, SLOT(onBtnClicked()));
    connect(this, SIGNAL(windowTitleChanged(QString)), this, SLOT(onBtnClicked()));
    无论是btn, edit还是this的信号发送,都会调用this的onBtnClicked()函数。

  4. 一个信号->多个槽函数
    connect(edit, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString)));
    connect(edit, SIGNAL(textChanged(QString)), this, SLOT(raise()));
    一个信号还可以出发多个槽函数被调用,不过他们的执行先后顺序就不确定了。

信号槽的调用方式

查阅connect()函数的帮助文档,发现在最后还有一个参数Qt::ConnectionType,那么这个参数起到什么作用呢?它有很重要的作用,它决定了信号和槽函数在调用时将采取什么样的方式。
它定义了4中连接形式:
1. AutoConnection
自动模式,也是默认的选项。根据Qt的描述,如果信号发送对象和接收对象在同一个线程,那么实际使用的是DirectConnection(直连)模式,如果它们在不同的线程,使用的就是QueuedConnection(队列)模式。这两个模式的意义请继续看下面的讲解。
2. DirectConnection
直连模式,言下之意就是直接调用。当信号发出后,将立即执行槽函数,并且等待槽函数执行完毕后才返回。当发送和接收对象都处于同一个线程内部时使用这种方式。
3. QueuedConnection
队列模式主要用在线程间信号槽传递。当发送和接收处于同一线程时,他们处于一个事件循环中,发送和接收函数的执行都是按照事件循环的顺序执行;而当他们处于不同线程时,发送对象并不知晓接收方所在的线程处于事件循环的什么状态,因此信号被传递进对方线程的信号队列,等待对方线程处理,同时发送信号立即结束返回。他们的执行是异步进行的。
4. BlockingQueuedConnection
有时候虽然他们不在同一个线程,但是发送方也希望等槽函数执行完毕后才返回,因此这个参数就是为了这个目的。值得注意的时使用这个参数必须是发送和接收对象不在一个线程,否则可能会引起死锁。
5. UniqueConnection
这个是一个组合规则,一般很少使用。

当我们使用QueuedConnection模式进行信号槽连接时,必须要确保信号槽的传递参数是 Meta-Object 系统所能识别的类型,如果要传递我们自己定义的类型呢?

信号槽的参数传递

我们知道槽其实就是一个函数,它可以像普通函数一样有返回值,有各种各样的参数。在Qt中,使用 Meta-Object 来表示connect传递的参数。Meta-Object 是什么东西?它是一种能被Qt所识别的类型,比如说int,float,bool等,还有QString,QSize,QRect,QFont等Qt所定义的类型。我们自己创造的class类型是不被Meta-Object系统识别的,那如果要传递我们自己定义的类型时该如何办呢?不用担心,Qt为我们提供了一种注册机制,可以将我们自己的类型注册为Meta-Object类型。

下面是我的自定义类:

class myclass{public:    ~myclass(){}    myclass(){}    myclass(const myclass& other){        if (&other != this) {            x = other.x;            y = other.y;        }    }    int x;    int y;};

要让Qt识别自己的类型,class必须要提供拷贝构造函数,因为在线程间拷贝的时候会用到拷贝构造函数,如果你不能保证使用系统提供的默认拷贝构造函数能得到正确结果,就必须实现自己的拷贝构造函数。

现在有这样一个信号定义:textChanged(const myclass& data);
还有这样一个槽函数定义:void onTextChanged(const myclass& data);
他们都使用自定义的class类型作为信号槽的传递参数,在使用connect函数之前,我们使用Qt提供的qRegisterMetaType(“myclass”);函数注册该class为Meta-Object类型。然后使用connect连接信号槽,这样我们就可以得到正确的调用。

或许你会发现一个问题,即使不使用qRegisterMetaType函数注册,一样能得到正确结果。那是因为在前面已经说过的问题,在同一个线程中,使用信号槽的默认调用方式是DirectConnection模式,在这种情况下无须注册都可以使用,但当你使用QueuedConnection模式连接时,你就会得到一个错误提示:

QObject::connect: Cannot queue arguments of type 'myclass'(Make sure 'myclass' is registered using qRegisterMetaType().)

编译器告诉你在使用队列模式时必须要注册Meta-System类型。

参数传递的效率

我们知道一般参数传递分值传递和引用传递,当然也可以说还有指针传递。那么在信号槽中使用不同的参数传递效率会如何呢?
我现在将myclass类修改一下,使其可以在构造和析构的时候打印些字符,这样我就能知道该对象呗创建和拷贝了多少次。

修改后的myclass大致是这样:

class myclass{public:    ~myclass(){ qDebug() << "~myclass()"; }    myclass(){ qDebug() << "myclass()"; }    myclass(const myclass& other){        if (&other != this) {            x = other.x;            y = other.y;        }        qDebug() << "myclass(const myclass& other)";    }    int x;    int y;};

分别调用不同的参数传递方式打印出来的结果如下:

// 值传递myclass()myclass(const myclass& other)myclass(const myclass& other)~myclass()~myclass()myclass(const myclass& other)x= 10 , y= 5~myclass()~myclass()
// 引用传递myclass()myclass(const myclass& other)~myclass()x= 10 , y= 5~myclass()
// 指针传递myclass()x= 10 , y= 5~myclass()
参数传递方式 默认构造函数调用次数 拷贝构造函数调用次数 值传递 1次 3次 引用传递 1次 1次 指针传递 1次 无

!!
其结果不言而喻,指针的效率是最高的,值传递时拷贝构造函数竟然被调用了3次,分别在emit发送信号时,发送到线程循环等待时,进入槽函数作形式参数时。而引用只在进入线程循环等待时拷贝了一次,这也就说明了为什么使用引用时对象在emit之后被释放了,但槽函数却还是能得到正确的结果,因为线程循环等待时即使是引用传递也会进行对象拷贝的。

0 0
原创粉丝点击