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种形式:
信号->槽函数,原型:connect(sender, signal, receiver, slot)
connect(btn, SIGNAL(clicked(bool)), this, SLOT(onBtnClicked()));
当对象btn发送clicked信号后,this对象的onBtnClicked()函数被执行。信号->信号,原型:connect(sender, signal, receiver, signal)
connect(edit, SIGNAL(textChanged(QString)), this, SIGNAL(textChanged(QString)));
当edit对象发送textChanged信号后,this对象的textChanged()函数被执行。多个信号->同一个槽函数
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()函数。一个信号->多个槽函数
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()
!!
其结果不言而喻,指针的效率是最高的,值传递时拷贝构造函数竟然被调用了3次,分别在emit发送信号时,发送到线程循环等待时,进入槽函数作形式参数时。而引用只在进入线程循环等待时拷贝了一次,这也就说明了为什么使用引用时对象在emit之后被释放了,但槽函数却还是能得到正确的结果,因为线程循环等待时即使是引用传递也会进行对象拷贝的。
- Qt编程入门(1) : 信号和槽
- Qt编程-信号和槽
- Qt入门(3)——信号和槽
- Qt编程之信号和槽机制
- Qt编程之信号和槽
- 【Qt编程】- 信号槽
- QT编程基础篇QT入门之Hello qt及信号与槽
- Qt信号槽机制入门
- Qt信号和槽
- QT信号和槽
- QT信号和槽
- QT 信号和槽
- Qt ---- 信号和槽
- QT信号和槽
- QT--信号和槽
- QT 信号和槽
- QT 信号和槽
- QT 信号和槽 .
- ttyUSB串口设备节点生成过程
- Tiny210v2( S5PV210 )平台下创建基本根文件系统
- hashCode与equal
- Linux-cpu分析-top
- DFS Deep First Search Tips
- Qt编程入门(1) : 信号和槽
- 泛型方法
- 机器学习之支持向量机原理
- 中缀表达式转后缀表达式并计算结果
- 【数据结构&算法】数据结构之单向链表(练习)
- thinking in java——0318学习笔记
- hexo搭建博客之command not found
- Linux下:Live555+S5pV210的mfc模块硬解方案实现
- 关于AngularJS的ui-router