只要问到qt的亮点是什么,不得不说就是信号与槽机制。信号与槽类似观察者模式。
在面向对象的编程中,都会创建很多实例,而每个实例都是独立的,要想每个实例能够协同合作,那么就会需要一种对象间传递消息的机制,在很多框架中都采用回调函数来进行对象间信息传递。
回调函数就是一个函数指针,如果想要一个处理函数通知一些事件,你需要将这个指针传递给处理函数。处理函数在适当时间调用回调函数。MFC就是使用的回调函数,但回调可能是不直观的,不易于理解的,并且也不能保证是类型安全的。
(1)信号与槽 与 回调函数的区别
回调函数的本质是基于“你想让别人的代码执行你的代码,而别人的代码你又不能动”这种产生的。回调函数是函数指针的一种用法,如果多个类都关注某个类的状态变化,此时需要维护一个列表,以存放多个回调函数的地址。对于每一个被关注的类,都需要做类似的工作,因此这种做法效率低,不灵活。
回调函数的例子:
void printWelcome(int len)
{
printf("欢迎欢迎 -- %d/n", len);
}
void printGoodbye(int len)
{
printf("送客送客 -- %d/n", len);
}
void callback(int times, void(*print)(int))
{
int i;
for (i = 0; i < times; ++i)
{
print(i);
}
printf("/n我不知道你是迎客还是送客!/n/n");
}
void main(void)
{
callback(2, printWelcome);
callback(2, printGoodbye);
}
Qt为了消除回调函数等的弊端,从而开发了一种新的消息传递机制,即信号和槽。
顺带提一句,Qt提供了一种机制,能够自动、有效的组织和管理继承自QObject的Qt对象,这种机制就是对象树。这种机制在界面变成上是有好处的,能够帮助程序员缓解内存泄露的问题,比如说当应用程序创建了一个具有父窗口部件的对象时,该对象将被加入父窗口部件的孩子列表。当应用程序销毁父窗口部件时,其下的孩子列表中的对象将被一一删除。这让我们在编程时,能够将主要精力放在系统的业务上,提高编程效率,同时也保证了系统的稳健性。所以new了一个父窗口后,只要delete父窗口后,那它的子窗口都会被自动释放,释放顺序(即析构顺序)与这些子对象的构造顺序相反。
例如,当我们要求鼠标点击某个按钮时,对应的窗口就需要关闭,那么这个按钮就会发出一个点击信号,而窗口接收到这个信号后执行关闭窗口。那么,这个信号就是按钮被点击,而槽就是窗口执行关闭函数。
可以将信号和槽理解成“命令-执行”,即信号就是命令,槽就是执行命令。
(2)信号
当一个对象的内部状态发生改变时,如果其它对象对它的状态改变需要作出反应,这时就应该让这个类发出状态改变的信号。
声明信号使用signals关键字。
发送信号使用emit关键字。
信号的注意点:
1.所有的信号声明都是公有的,所以Qt规定不能在signals前面加public,private, protected。
2.所有的信号都没有返回值,所以返回值都用void。
3.所有的信号都不需要定义。
4.必须直接或间接继承自QOBject类,并且开头私有声明包含Q_OBJECT。
5.在同一个线程中,当一个信号被emit发出时,会立即执行其槽函数,等槽函数执行完毕后,才会执行emit后面的代码,如果一个信号链接了多个槽,那么会等所有的槽函数执行完毕后才执行后面的代码,槽函数的执行顺序是按照它们链接时的顺序执行的。不同线程中(即跨线程时),槽函数的执行顺序是随机的。
6.在链接信号和槽时,可以设置链接方式为:在发出信号后,不需要等待槽函数执行完,而是直接执行后面的代码,是通过connect的第5个参数。
7.信号与槽机制要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,信号的参数可以比槽函数的参数多,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。
(3)槽
槽其实就是普通的C++函数,它可以是虚函数,static函数,也可以被重载,可以是公有的、保护的、私有的、也可以被其他C++成员函数调用,它唯一特点就是能和信号链接。当和它链接的信号被发出时,这个槽就会被调用,而且槽所在的类也需要直接或间接继承QObject,然后添加Q_OBJECT,槽函数因为是普通的C++函数,所以需要实现,不然接了信号什么都不做总不太好吧,槽是可以受访问控制的,而信号不行,不然一个private的信号,在类外有不能连接,没有意义。
在qt4中,声明槽可以使用:public/protected/private slots:
在Qt5中你也不需要使用这些声明,每个函数都可以被当作是槽函数,而且还可以使用Lambda表达式来作为槽。不过为了程序的可读性,我还是推荐槽函数要声明一下。
(4)信号与槽的连接
使用connect函数,有两个原型。
原型1:
static QMetaObject::Connection connect(
const QObject *sender, //信号发送对象指针
const char *signal, //信号函数字符串,使用SIGNAL()
const QObject *receiver, //槽函数对象指针
const char *member, //槽函数字符串,使用SLOT()
Qt::ConnectionType = Qt::AutoConnection);
如:connect(pushButton, SIGNAL(clicked()), dialog, SLOT(close()));
Qt4和Qt5都可以使用这种连接方式。
原型2:
static QMetaObject::Connection connect(
const QObject *sender, //信号发送对象指针
const QMetaMethod &signal, //信号函数地址
const QObject *receiver, //槽函数对象指针
const QMetaMethod &method, //槽函数地址
Qt::ConnectionType type = Qt::AutoConnection);
如:connect(pushButton, &QPushButton::clicked, dialog, &QDialog::close);
这是Qt5新增的连接方式,这使得在编译期间就可以进行拼写检查,参数检查,类型检查,并且支持相容参数的兼容性转换。
(5)信号与槽的多种用法