Qt的学习之路:
参考网址1:
https://doc.qt.io/qt-5/qtquick-codesamples.html
参考网址2:
https://www.devbean.net/2012/08/qt-study-road-2-catelog/
需要的头文件:
#Include
使用:
//想测试结果的小伙伴在qt新建项目的main.cpp输入以下代码就可以啦
//后边没有特别说明放在哪里的代码,基本上都只要放到main.cpp就可以了
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);//必须的头
QPushButton button("Quit");
QObject::connect(&button,&QPushButton::clicked,&QApplication::quit);//这里对应的button属于object,而clicked是一个信号,quit是一个槽。
button.show();//show是一个显示该对象的类方法
return a.exec();//必须的尾巴
}
2.在Qt5中,QObject::connect()函数有五个重载。
QMetaObject::Connection connect(const QObject *, const char *,
const QObject *, const char *,
Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, const QMetaMethod &,
const QObject *, const QMetaMethod &,
Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, const char *,
const char *,
Qt::ConnectionType) const;
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
const QObject *, PointerToMemberFunction,
Qt::ConnectionType)
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
Functor);
这五个重载的返回值都是QMetaObject::Connection
对于Qt5最常用的一种connect形式是
connect(sender,signal,receiver, slot);
第一个是发出信号的对象,对二个是对象发出的信号,第三个是接收信号的对象,第四个是接收对象在接收到信号之后所需要调用的函数。
也就是说,当 sender 发出了 signal 信号之后,会自动调用 receiver 的 slot 函数。
这是最常用的形式。
我们可以套用这个形式去分析上面给出的五个重载。
第一个,sender 类型是const QObject *,signal 的类型是const char *,receiver 类型是const QObject *,slot 类型是const char *。这个函数将 signal 和 slot 作为字符串处理。
第二个,sender 和 receiver 同样是const QObject *,但是 signal 和 slot 都是const QMetaMethod &。我们可以将每个函数看做是QMetaMethod的子类。因此,这种写法可以使用QMetaMethod进行类型比对。
第三个,sender 同样是const QObject *,signal 和 slot 同样是const char *,但是却缺少了 receiver。这个函数其实是将 this 指针作为 receiver。
第四个,sender 和 receiver 也都存在,都是const QObject *,但是 signal 和 slot 类型则是PointerToMemberFunction。看这个名字就应该知道,这是指向成员函数的指针。
第五个,前面两个参数没有什么不同,最后一个参数是Functor类型。这个类型可以接受 static 函数、全局函数以及 Lambda 表达式。
由此我们可以看出,connect()函数,sender 和 receiver 没有什么区别,都是QObject指针;主要是 signal 和 slot 形式的区别。具体到我们的示例,我们的connect()函数显然是使用的第五个重载,最后一个参数是QApplication的 static 函数quit()。也就是说,当我们的 button 发出了clicked()信号时,会调用QApplication的quit()函数,使程序退出。
信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。
如果信号槽不符合,或者根本找不到这个信号或者槽函数的话,比如我们改成:
QObject::connect(&button, &QPushButton::clicked,
&Qapplication::quit2);
由于 QApplication 没有 quit2 这样的函数的,因此在编译时,会有编译错误:
’
quit2’ is not a member of QApplication
这样,使用成员函数指针,我们就不会担心在编写信号槽的时候会出现函数错误。
借助 Qt 5 的信号槽语法,我们可以将一个对象的信号连接到 Lambda 表达式,例如:
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton button("Quit");
QObject::connect(&button, &QPushButton::clicked, [](bool) {
qDebug() << "You clicked me!";});//qDebug()就相当与是C++的cout
button.show();
return app.exec();
}
注意这里的 Lambda 表达式接收一个 bool 参数,这是因为QPushButton的clicked()信号实际上是有一个参数的。Lambda 表达式中的qDebug()类似于cout,将后面的字符串打印到标准输出。
以上是针对Qt5的,对于Qt4的信号槽有以下3种不同的函数重载:
bool connect(const QObject *, const char *,
const QObject *, const char *,
Qt::ConnectionType);
bool connect(const QObject *, const QMetaMethod &,
const QObject *, const QMetaMethod &,
Qt::ConnectionType);
bool connect(const QObject *, const char *,
const char *,
Qt::ConnectionType) const
除了返回值,Qt 4 的connect()函数与 Qt 5 最大的区别在于,Qt 4 的 signal 和 slot 只有const char *这么一种形式。如果我们将上面的代码修改为 Qt 4 的,则应该是这样的:
#include
#include
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton button("Quit");
QObject::connect(&button, SIGNAL(clicked()),
&app, SLOT(quit()));
button.show();
return app.exec();
}
我们使用了SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。注意,即使quit()是QApplication的 static 函数,也必须传入一个对象指针。这也是 Qt 4 的信号槽语法的局限之处。另外,注意到connect()函数的 signal 和 slot 都是接受字符串,因此,不能将全局函数或者 Lambda 表达式传入connect()。一旦出现连接不成功的情况,Qt 4 是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。
信号槽机制是 Qt 的最大特性之一。这次我们只是初略了解了信号槽,知道了如何使用connect()函数进行信号槽的连接。在后面的内容中,我们将进一步介绍信号槽,了解如何设计自己的信号槽等等。
Qt中我们可以自己定义一个Newspaper(报纸)类和Subscriber(订阅者)类。用Subscriber订阅Newspaper类,从而实现自己的信号槽的编写。下面我们来看看如何实现这种订阅操作。
新建一个项目:(Qt5)
在newspaper.h文件写入:
#include
class Newspaper:public QObject
{
Q_OBJECT
private:
QString m_name;
public:
Newspaper(const QString &name):
m_name(name)
{}
void send()
{
//emit是Qt对C++的扩展,是一个关键字(其实也是一个宏),emit的含义
//是发出,也就是发出newPaper()信号。感兴趣的接收者会关注这个信号,
//可能还需要知道是哪份报纸发出的信号?所以,我们将实际的报纸名字m_name
//当做参数传给这个信号。当接收者连接这个信号时,就可以通过槽函数
//获得实际值。这样就完成了数据从发出者到接收者的一个转移。
emit newPaper(m_name);
}
signals: //signal就是一个个的函数名
void newPaper(const QString &name);
};
在reader.h文件写入
#include
#include
class Reader : public QObject
{
Q_OBJECT
public:
Reader(){};
void receiveNewspaper(const QString &name)
{
qDebug() <<"Receives Newspaper: " <
在main.cpp写入
#include
#include "newspaper.h"
#include "reader.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Newspaper newspaper("Newspaper A");
Reader reader;
//连接信号槽的接口。
QObject::connect(&newspaper,&Newspaper::newPaper,
&reader, &Reader::receiveNewspaper);
//使用send的时候,因为emit发出的信号Qstring被槽接收,reader输出
//Receives Newspaper: "Newspaper A"
newspaper.send();
return a.exec();
}
写完后运行代码:可以看到
Receives Newspaper: “Newspaper A”
下面我们开始代码的解析:
首先看Newspaper这个类。这个类继承了QObject类。只有继承了QObject类的类,才具有信号槽的能力。所以,为了使用信号槽,必须继承QObject。凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT。不管是不是使用信号槽,都应该添加这个宏。
这个宏的展开将为我们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI 的反射能力。因此,如果你觉得你的类不需要使用信号槽,就不添加这个宏,就是错误的。其它很多操作都会依赖于这个宏。
注意:这个宏将由 moc(我们会在后面章节中介绍 moc。这里你可以将其理解为一种预处理器,是比 C++ 预处理器更早执行的预处理器。) 做特殊处理,不仅仅是宏展开这么简单。moc 会读取标记了 Q_OBJECT 的头文件,生成以 moc_ 为前缀的文件,比如 newspaper.h 将生成 moc_newspaper.cpp。你可以到构建目录查看这个文件,看看到底增加了什么内容。注意,由于 moc 只处理头文件中的标记了Q_OBJECT的类声明,不会处理 cpp 文件中的类似声明。因此,如果我们的Newspaper和Reader类位于 main.cpp 中,是无法得到 moc 的处理的。解决方法是,我们手动调用 moc 工具处理 main.cpp,并且将 main.cpp 中的#include "newspaper.h"改为#include "moc_newspaper.h"就可以了。不过,这是相当繁琐的步骤,为了避免这样修改,我们还是将其放在头文件中。许多初学者会遇到莫名其妙的错误,一加上Q_OBJECT就出错,很大一部分是因为没有注意到这个宏应该放在头文件中。
Newspaper类的 public 和 private 代码块都比较简单,只不过它新加了一个 signals。signals 块所列出的,就是该类的信号。信号就是一个个的函数名,返回值是 void(因为无法获得信号的返回值,所以也就无需返回任何值),参数是该类需要让外界知道的数据。信号作为函数名,不需要在 cpp 函数中添加任何实现(我们曾经说过,Qt 程序能够使用普通的 make 进行编译。没有实现的函数名怎么会通过编译?原因还是在 moc,moc 会帮我们实现信号函数所需要的函数体,所以说,moc 并不是单纯的将 Q_OBJECT 展开,而是做了很多额外的操作)。
Newspaper类的send()函数比较简单,只有一个语句emit newPaper(m_name);。emit 是 Qt 对 C++ 的扩展,是一个关键字(其实也是一个宏)。emit 的含义是发出,也就是发出newPaper()信号。感兴趣的接收者会关注这个信号,可能还需要知道是哪份报纸发出的信号?所以,我们将实际的报纸名字m_name当做参数传给这个信号。当接收者连接这个信号时,就可以通过槽函数获得实际值。这样就完成了数据从发出者到接收者的一个转移。
Reader类更简单。因为这个类需要接受信号,所以我们将其继承了QObject,并且添加了Q_OBJECT宏。后面则是默认构造函数和一个普通的成员函数。Qt 5 中,任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。与信号函数不同,槽函数必须自己完成实现代码。槽函数就是普通的成员函数,因此作为成员函数,也会受到 public、private 等访问控制符的影响。(我们没有说信号也会受此影响,事实上,如果信号是 private 的,这个信号就不能在类的外面连接,也就没有任何意义。)
main()函数中,我们首先创建了Newspaper和Reader两个对象,然后使用QObject::connect()函数。这个函数我们上一节已经详细介绍过,这里应该能够看出这个连接的含义。然后我们调用Newspaper的send()函数。这个函数只有一个语句:发出信号。由于我们的连接,当这个信号发出时,自动调用 reader 的槽函数,打印出语句。
下面总结一下自定义信号槽需要注意的事项:
发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外;
使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
使用 emit 在恰当的位置发送信号;
使用QObject::connect()函数连接信号和槽。
对于Qt4,与Qt5有一点点差别:
Reader类,receiveNewspaper()函数放在了 public slots 块中。在 Qt 4 中,槽函数必须放在由 slots 修饰的代码块中,并且要使用访问控制符进行访问控制。其原则同其它函数一样:默认是 private 的,如果要在外部访问,就应该是 public slots;如果只需要在子类访问,就应该是 protected slots。
main()函数中,QObject::connect()函数,第二、第四个参数需要使用SIGNAL和SLOT这两个宏转换成字符串(具体事宜我们在上一节介绍过)。注意SIGNAL和SLOT的宏参数并不是取函数指针,而是除去返回值的函数声明,并且 const 这种参数修饰符是忽略不计的。
下面说明另外一点,我们提到了“槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响”,public、private 这些修饰符是供编译器在编译期检查的,因此其影响在于编译期。对于 Qt4 的信号槽连接语法,其连接是在运行时完成的,因此即便是 private 的槽函数也是可以作为槽进行连接的。但是,如果你使用了 Qt5 的新语法,新语法提供了编译期检查(取函数指针),因此取 private 函数的指针是不能通过编译的。