qt之信号与槽的原理

只要问到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)信号与槽的多种用法

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值