【QT入门1】

目录

1.创建工程时基类的选择

2.第一个QT程序

3.创建一个按钮

4.对象树简单理解

5.信号和槽

5.1自定义信号槽

5.2信号连接信号

5.3信号函数和槽函数的注意事项

5.4配合lambda表达式

1.创建工程时基类的选择

在创建工程时会被要求选择一个基类:

【QT入门1】_第1张图片

这里有三个基类可供选择,分别是"QMainWindow"、"QWidget"、"QDialog"。它们的关系是:

【QT入门1】_第2张图片 这里简要介绍一下这三个基类分别是什么:

  • QWidget:一个空白窗口
  • QMainWindow:继承自QWidget类,它是一个包含菜单栏、工具栏、状态栏等等的窗口
  • QDialog:继承自QWidget类,它是一个对话框窗口

2.第一个QT程序

选择"QWidget"作为基类,并且不勾选"Generate form"选项,得到的工程文件如下:

【QT入门1】_第3张图片

.pro文件是以qmake构建的工程文件,它描述了当前工程的一些信息。该文件内的内容和解释如下图:

【QT入门1】_第4张图片

这些都是默认生成的。如果要使用网络通信模块,那么还应该加载"network"模块。

main.cpp是该项目的入口,它的内容和解释如下图:

【QT入门1】_第5张图片

如果开发过服务器,那么这几句代码很容易理解。"a.exec()"就是一个事件监听循环

widget.h是项目自动生成的派生类,它继承自"QWidget"。它的内容和解释如下图:

【QT入门1】_第6张图片

widget.cpp是上述类的成员函数实现,它的内容如下图:

【QT入门1】_第7张图片

运行该程序,得到如下结果:

【QT入门1】_第8张图片可见,输出结果就是一个空白窗口,并且不是一闪而过,这是因为main.cpp中做了事件监听循环处理。

3.创建一个按钮

在QT当中,按钮也封装成了单独的类,它的相关说明如下图:

【QT入门1】_第9张图片

 可以看到,QPushButton类继承自QAbstractButton类,而QAbstractButton又继承自QWidget类,如下图所示:

【QT入门1】_第10张图片

这说明按钮可以在单独的窗口当中打开,代码如下:

#include "widget.h"

#include 
#include 

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    QPushButton *btn = new QPushButton;// 创建一个按钮对象
    btn->show();// 单独打开一个窗口显示
    w.show();
    return a.exec();
}

最终的运行结果是这样的:

【QT入门1】_第11张图片

那么想要让按钮依附于已经存在的窗口,只需要指定"父亲"即可,代码如下:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    QPushButton *btn = new QPushButton("我的按钮",&w);// 构造函数当中可以指定按钮的文字和"父亲"
    //btn->setParent(&w);// 另一种方法指定"父亲"
    //btn->setText("我的按钮");// 另一种方法指定按钮的文字
    w.show();
    return a.exec();
}

最终效果如下:

【QT入门1】_第12张图片

 还可以更改按钮的大小和位置,代码如下:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    QPushButton *btn = new QPushButton("我的按钮",&w);
    btn->resize(200,300);// 设置按钮的大小
    btn->move(200,300);// 移动到(200,300)位置
    w.show();
    return a.exec();
}

运行效果如下:

【QT入门1】_第13张图片

 关于QPushButton类还有很多用法,篇幅有限就不一一列举了,可以在平时的练习和项目当中发现更多有意思的东西。

4.对象树简单理解

观察上面的代码可以发现,它们不是一份合格的C++代码,因为它们看起来有内存泄漏:

【QT入门1】_第14张图片

那么给它加上delete会发生意想不到的事:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget *wid = new Widget;
    QPushButton *btn = new QPushButton("我的按钮",wid);
    wid->show();
    delete wid;
    delete btn;
    return 0;
}

这份代码看似完美,实则会发生崩溃:

这是因为对象树的存在。 

在上面的代码当中,出现了指定"父亲"的情况,这种操作实际上就是加入对象树。

【QT入门1】_第15张图片

【QT入门1】_第16张图片

【QT入门1】_第17张图片

那么对象树和内存泄漏有什么关系?

在QT当中的对象树有一个特性,对象树当中的任意一个对象要析构的时候,清理自身资源之前要先清理所有的"儿子"

这就是为什么大多数QT类当中需要传入一个parent指针的原因,就是要让该类对象加入对象树,然后根据对象树当中的任意对象析构时的特性,可以做到避免内存泄漏。

举一个例子来说明以上结论是正确的,这里自定一个MyPushButton类:

#include "widget.h"

#include 
#include 
#include 
#include 
class MyPushButton : QPushButton// 继承自QPushButton类
{
public:
    MyPushButton(const QString &text, QWidget *parent = nullptr)
        :QPushButton(text,parent)
    {}

    ~MyPushButton()
    {
        qDebug() << "~MyPushButton()";
    }
private:
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    MyPushButton *btn = new MyPushButton("自定义按钮",&w);
    w.show();
    return a.exec();
}
Widget::~Widget()
{
    delete ui;
    qDebug() << "Widget::~Widget()";
}

其运行结果如下:

那么在这个例子当中就有了一颗简单的对象树:

【QT入门1】_第18张图片

【QT入门1】_第19张图片

5.信号和槽

"信号槽"实际上是两个东西,一个是信号,一个是槽。那么我自己更愿意称它们为"信号和信号处理"

既然谈到信号和信号处理,那么就必定涉及四个部分:

  • 信号的发送者
  • 发送了什么信号
  • 信号的接收者
  • 收到信号后要做什么动作

其中"收到信号后要做什么动作"在QT当中称为槽。

流程如下图所示:

 有一函数一一对应上面的过程:

connect函数是QObject当中的成员函数,而QObject是最顶层的基类,意思是说例如QWidget或者QMainWindow这样的类它们的祖宗类都是QObject,所以它们都可以直接使用类内的connect函数。

 下面以代码演示一下"按钮点击之后窗口关闭"的效果:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    QPushButton *btn = new QPushButton("我的按钮",&w);
    Widget::connect(btn,&QPushButton::clicked,&w,&Widget::close);// 注册信号和槽
    w.show();
    return a.exec();
}

connect方法实际上类似于Linux的系统调用signal,它们都是在注册一堆信号和信号处理方法,信号产生并且接收到时就会执行预设的动作。那么在QT当中有一个取消注册信号槽的方法:disconnect

5.1自定义信号槽

在QT当中允许自定义信号和槽。

有一需求:设计出员工类和老板类,员工负责发送"月底到了"的信号,老板负责响应员工发送的信号,老板的动作就是发工资。 

下面给出实现的代码:

// boss.hpp
#ifndef BOSS_H
#define BOSS_H

#include 
#include 
class Boss : public QObject
{
    Q_OBJECT
public:
    explicit Boss(QObject *parent = nullptr)
        :QObject(parent)
    {}

public slots:
    void GetPaid()//
    {
        qDebug() << "老板发工资了!";
    }

};

#endif // BOSS_H
// staff.hpp
#ifndef STAFF_H
#define STAFF_H

#include 

class Staff : public QObject// 员工类
{
    Q_OBJECT
public:
    explicit Staff(QObject *parent = nullptr)
        :QObject(parent)
    {}

signals:
    void IsEndOfMonth();// 到月底了
};

#endif // STAFF_H
// main.cpp
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Staff *st = new Staff;
    Boss *bs = new Boss;
    QObject::connect(st,&Staff::IsEndOfMonth,bs,&Boss::GetPaid);// 注册一个信号和槽
    emit st->IsEndOfMonth();// 员工发送"月底到了"的信号
    return a.exec();
}

程序运行后的结果为:

上面代码设计到4个新鲜的东西,这里一一介绍一下:

  • Q_OBJECT:这是有关于信号和槽的关键字,必加
  • signals:这是声明信号的关键字,在定义信号函数时,只声明不实现
  • public slots:声明槽函数的关键字,在QT的较新版本当中,槽函数也可以直接写在public下
  • emit:发送信号的关键字

5.2信号连接信号

connect方法不仅可以指定信号连接槽,还可以指定信号连接信号

接下来演示一个实例代码:点击按钮触发一个信号,该信号连接到Staff类的"月底到了"的信号,然后Staff类的信号连接到Boss类的"发工资"信号:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    Staff *st = new Staff(&w);
    Boss *bs = new Boss(&w);
    QPushButton *btn = new QPushButton("我的按钮",&w);
    QObject::connect(btn,&QPushButton::clicked,st,&Staff::IsEndOfMonth);// 信号连接信号
    QObject::connect(st,&Staff::IsEndOfMonth,bs,&Boss::GetPaid);
    w.show();
    return a.exec();
}

【QT入门1】_第20张图片

5.3信号函数和槽函数的注意事项

 上面的案例当中信号函数和槽函数都是没有参数的,事实上在QT当中,信号函数和槽函数允许重载。需要注意的是,槽函数的参数必须与信号函数一一对应,但是可以少于信号函数的参数。例如下面这样:

// 信号函数
void Signal1();
void Signal2(int x);
void Signal3(int x,int y);

// 槽函数
void Slot1();
void Slot2(int x);

在如上代码代码当红,Signal1触发会调用Slot1槽函数,Signal2、Signal3触发会调用Slot2槽函数。

还需要注意,在上面的所有与信号和槽相关的代码时都是"不准确"的,因为在实际的项目当中一个类的信号或槽都有多个重载,那么在使用connect()函数时,如何指定信号和槽?答案是使用函数指针。例如下面的用法:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    Staff *st = new Staff(&w);
    Boss *bs = new Boss(&w);
    QPushButton *btn = new QPushButton("我的按钮",&w);
    /*指向无参的信号和槽*/
//    void (Staff:: *staffSignal)() = &Staff::IsEndOfMonth;
//    void (Boss:: *bossSlot)() = &Boss::GetPaid;

    /*指向int类型参数的信号和槽*/
    void (Staff:: *staffSignal)(int) = &Staff::IsEndOfMonth;
    void (Boss:: *bossSlot)(int) = &Boss::GetPaid;

    QObject::connect(btn,&QPushButton::clicked,st,staffSignal);// 信号连接信号
    QObject::connect(st,staffSignal,bs,bossSlot);
    w.show();
    return a.exec();
}

那么输出结果或许猜想到了,是不正确的,因为我们并没有做传参的处理:

【QT入门1】_第21张图片

5.4配合lambda表达式

为了解决上面的问题,可以使用lambda表达式来解决它。

注意,槽函数的本质只是一个函数,而lambda表达式本质是一个匿名函数对象,所以可以直接搭配使用。

最终代码如下:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    Staff *st = new Staff(&w);
    Boss *bs = new Boss(&w);
    QPushButton *btn = new QPushButton("我的按钮",&w);
    /*指向int类型参数的信号和槽*/
    void (Staff:: *staffSignal)(int) = &Staff::IsEndOfMonth;
    void (Boss:: *bossSlot)(int) = &Boss::GetPaid;
    QObject::connect(st,staffSignal,bs,bossSlot);
    auto func = [=]()
    {
        emit st->IsEndOfMonth(100);
    };
    QObject::connect(btn,&QPushButton::clicked,func);// 使用lambda表达式时可以不指定接收信号对象

    w.show();
    return a.exec();
}

【QT入门1】_第22张图片

你可能感兴趣的:(qt,c++)