Qt学习笔记2——信号槽

学习自:https://www.devbean.net/2012/08/qt-study-road-2-catelog/
记录学习笔记仅供自己学习,如有侵权,请联系作者删除。

1.关于QT

Qt Designer
Qt Designer被称为Qt设计师,用于设计和构建图形用户界面(Qt Widgets)。可以组合和自定义窗口或对话框,并使用不同的风格和分辨率进行测试。用Qt Designer创建的窗口部件和表格无缝集成编程代码,采用Qt信号和槽机制,这样就可以轻松地分配图形元素的行为。在Qt设计师中设置的所有属性可以动态地在代码中进行更改。
Qt Linguist
Qt Linguist被称为Qt语言家。主要任务是读取翻译文件、为翻译人员提供友好的翻译界面,是用于界面国际化的重要工具。它能帮助你很容易读懂C++语言。
Qt Assistant
Qt Assistant被称为Qt助手,是Qt自带的一款可定制、可重新发行的帮助文件浏览器。它支持HTML文件,用户可以利用其定制自己的功能强大的帮助文档浏览器。

2.GUI

图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)
是指采用图形方式显示的计算机操作用户界面。

3.Hello World

在 Qt 中,所有的QPaintDevice必须要在有QApplication实例的情况下创建和使用。也就是app对象的生命周期应长于label对象。
指针对象创建在堆上,需要程序员手动申请和释放内存;
普通对象创建在栈上,无需手动释放内存;
推荐在栈上创建组件,无需手动 new 和 delete 管理内存。
1.hello world

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QLabel label("Hello, world");
    label.show();

    return app.exec();
}

include引入:QApplication、QLabel两个类;
main()函数中第一句是创建一个QApplication类的实例。
对于 Qt 程序来说,main()函数一般以创建 application 对象(GUI 程序是QApplication,非 GUI 程序是QCoreApplication。QApplication实际上是QCoreApplication的子类。)
application 对象用于管理 Qt 程序的生命周期,开启事件循环(main()函数最后,调用app.exec(),开启事件循环。)。

2.内存泄露问题

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QLabel *label = new QLabel("Hello, world");
    label->show();

    return app.exec();
}

**C++标准:**当exec()退出时(也就是事件循环结束的时候。窗口关闭,事件循环就会结束),label 是没办法 delete 的。
新版本Qt: app.exec()的实现机制确定,当最后一个可视组件关闭之后,主事件循环(也就是app.exec())才会退出,main()函数结束(此时会销毁app)。由于是在main()函数中,当main()函数结束时,操作系统会回收进程所占用的资源,相当于没有内存泄露。不过,这里有一个潜在的问题:操作系统只会粗暴地释放掉所占内存,并不会调用对象的析构函数(这与调用delete运算符是不同的),所以,很有可能有些资源占用不会被“正确”释放。

4.信号槽连接connect

Qt框架使用信号槽机制:当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,用自己的一个函数(成为槽(slot))来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。类似于观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
连接函数 QObject::connect() 的五个重载:

QMetaObject::Connection connect(const QObject *, const char *,
                                const QObject *, const char *,
                                Qt::ConnectionType);
//将signal和slot作为字符串处理。
QMetaObject::Connection connect(const QObject *, const QMetaMethod &,
                                const QObject *, const QMetaMethod &,
                                Qt::ConnectionType);
//使用QMetaMethod进行类型比对。
QMetaObject::Connection connect(const QObject *, const char *,
                                const char *,
                                Qt::ConnectionType) const;
//缺少receiver,将this指针作为receiver。
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
                                const QObject *, PointerToMemberFunction,
                                Qt::ConnectionType)
//signal和slot是指向成员函数的指针。
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
                                Functor);
//最后一个参数是Functor类型,这个类型可以接受 static 函数、全局函数以及 Lambda 表达式。

connect()函数的常用形式:

connect(sender,   signal,  receiver, slot);

//发出信号的对象;发送对象发出的信号;接收信号的对象;接收对象在接收到信号之后所需要调用的函数。即,当 sender 发出了 signal 信号之后,会自动调用 receiver 的 slot 函数。

信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。
Lambda表达式:是一个匿名函数,即没有函数名的函数。
C++中的基本语法:

[capture list] (parameter list) -> return type { function body }

其中除了“[ ]”(其中捕获列表可以为空)和“复合语句”(相当于具名函数定义的函数体),其它都是可选的。它的类型是单一的具有成员operator()的非联合的类类型,称为闭包类型(closure type)。
C++中,一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。它与普通函数不同的是,lambda必须使用尾置返回来指定返回类型。
如,调用中的std::sort,来实现降序排序:

sort(a, a+n, [](int a,int b){return a>b;});//降序排序

5.自定义信号槽

观察者模式举例:报纸和订阅者的例子。有一个报纸类Newspaper,有一个订阅者类Subscriber。Subscriber可以订阅Newspaper。这样,当Newspaper有了新的内容的时候,Subscriber可以立即得到通知。在这个例子中,观察者是Subscriber被观察者是Newspaper。在经典的实现代码中,观察者会将自身注册到被观察者的一个容器中(比如subscriber.registerTo(newspaper))。被观察者发生了任何变化的时候,会主动遍历这个容器,依次通知各个观察者(newspaper.notifyAllSubscribers())。
注意:
1.发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
2.使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
3.槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
4.使用 emit 在恰当的位置发送信号;
5.使用QObject::connect()函数连接信号和槽。

// newspaper.h
#include <QObject>
class Newspaper : public QObject
{
    Q_OBJECT
public:
    Newspaper(const QString & name) :
        m_name(name)  {}

    void send()
    {
        emit newPaper(m_name);
    }

signals:
    void newPaper(const QString &name);

private:
    QString m_name;
};

// reader.h
#include <QObject>
#include <QDebug>

class Reader : public QObject
{
    Q_OBJECT
public:
    Reader() {}

    void receiveNewspaper(const QString & name)
    {
        qDebug() << "Receives Newspaper: " << name;
    }
};

// main.cpp
#include <QCoreApplication>

#include "newspaper.h"
#include "reader.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    Newspaper newspaper("Newspaper A");
    Reader reader;
    QObject::connect(&newspaper, &Newspaper::newPaper,
                     &reader,    &Reader::receiveNewspaper);
    newspaper.send();

    return app.exec();
}

这段代码放在了三个文件,分别是 newspaper.h,reader.h 和 main.cpp。为了减少文件数量,可以把 newspaper.h 和 reader.h 都放在 main.cpp 的main()函数之前吗?答案是,可以,但是需要有额外的操作(因为 宏Q_OBJECT 是由moc(一种预处理器)做特殊处理)。

Newspaper类: 只有继承了QObject类的类,才具有信号槽的能力。凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT。不管是不是使用信号槽,都应该添加这个宏。moc将宏展开会做一些特殊的处理,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"就可以了。不过,这是相当繁琐的步骤,为了避免这样修改,我们还是将其放在头文件中。】

Newspaper类: 新加了一个 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 的槽函数,打印出语句。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值