Bootstrap

Qt -初识

博客主页:【夜泉_ly
本文专栏:【暂无
欢迎点赞👍收藏⭐关注❤️

在这里插入图片描述

📚 前言

本文主要内容:
在这里插入图片描述

嘿嘿,又开了一个新坑。

为什么学 Qt?

当 C++了解的差不多了,
我就想看看现在自己能做些什么实际的应用。
学习新技术也巩固了旧知识。

然后,我便发现了 Qt。

简单来讲,
Qt 能让我们从“面向黑框框编程”中解脱出来。

因为它提供了丰富的图形界面组件和工具,
使得我们可以更加直观地设计和开发具有良好用户体验的应用程序。

🛠️ 搭建环境

学Qt,那就必须有对应的Qt开发环境。
主要分三个部分:

  • C++ 编译器
  • Qt SDK(软件开发工具包)
  • IDE

看起来很麻烦,但实际上,
我们安装一个Qt SDK,其他两个也都有了~~

这里贴个官网下载地址:http://download.qt.io/archive/qt/

很明显,这是个外国的网站。
怎么下载,那就八仙过海,各显神通了~~

我这里下的是 5.14.2

下载完成,双击安装,一路next,就行了?
不对!!先断网,再双击安装。
为什么?因为这样不用注册Qt账号😋。

安装完了大概会看到五个东西:

在这里插入图片描述

  • Assistant ,这是官方文档
  • Designer,这个后面会搭配Qt Creater使用
  • L开头的,可以对国际化进行支持
  • 黑框框,不管
  • Qt Creater,Qt的集成开发工具,
    后面主要就用这个

📂 新建项目

打开Qt Creater,新建文件:
在这里插入图片描述

我们想用Qt写一个GUI,因此:
点Application,再Widgets,再choose:

在这里插入图片描述

一路next到Details这里,选择QWidget

在这里插入图片描述

再next,最后到这个界面:
在这里插入图片描述

可以看到,这里已经有代码了。
Ctrl R运行,甚至可以生成一个窗口,虽然啥也没有:
在这里插入图片描述
这里不得不再提一下Qt是什么了:
在这里插入图片描述
框架。。
这和我们以前从头开始造轮子就有点区别了。
在这里,我们看到一个完整的 Qt 项目框架已经被自动生成,甚至可运行。
比如刚刚我们选择了QWidget,这里就自动帮我们搭好了相关的架子。
而我们要做的,就是在这个框架上面补充内容。

不过,为了避免当一个“框架驾驶员”,
初学时,先来了解一下它提供了什么,加深理解。

📝 初始代码理解

main.cpp

接下来,先仔细看看 main.cpp 的东西:
在这里插入图片描述

#include "widget.h"

首先,包了一个"widget.h"
这个就是我们刚刚选了QWidgets后自动生成的头文件。

#include <QApplication>

然后是<QApplication>,这个是每个Qt应用程序的基础。
这里面有个类叫做 QApplication
作用是管理GUI应用程序的控制流和主要设置:
在这里插入图片描述

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

main,这个不必多说。
而形参是命令行参数,这个暂时也不用管。

    QApplication a(argc, argv);

创建了一个 QApplication 对象 a
用于管理我们的应用程序。
这个对象是编写 Qt 的图形化界面所必须要有的!
不过不需要我们写,毕竟是框架嘛😋。

    Widget w;
    w.show();

这个也是选择QWidgets后自动生成的。
Widget w; 创建一个 Widget 对象。
Widget 是我们自定义的窗口类。
w.show(); 显示窗口。
此调用将使 Widget 出现在屏幕上。
把这两句注释掉。。你的窗口就没了。

    return a.exec();
}

exec() 的作用有很多:
在这里插入图片描述
不过我只看得懂第一句话🤣:
用来启动事件循环,直到被明确终止(比如点击右上角的叉)才会返回,然后退出程序。

如果改成return 0; ,那程序闪一下就没了:
请添加图片描述

widget.h

接下来看widget.h,这里面就是 Widget 类的声明
在这里插入图片描述

#ifndef WIDGET_H
#define WIDGET_H

防止头文件被重复包含。
一般更推荐用#pragma once
不过它写都写了,那我也懒得改了😋~~

#include <QWidget>

#include <QWidget>引入 QWidget 类,
这是 Qt 所有用户界面对象的基类。

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

声明 Ui::Widget 类,这个是 Qt Designer 生成的类。
这个类的名字是可以自己改的,
不过要改的地方还挺多的,一般用他默认给的就行。

class Widget : public QWidget {

继承可以让人变得富有😁,类也一样。
QWidget 是创建项目时选择的父类。
不过需要注意,这里的 Widget
和上面的 Ui::Widget不是一个东西!!!
这里的 Widget 是等会我们写代码的地方,通常继承自 QWidget
Ui::Widget 是由 Qt Designer 根据 .ui 文件生成的类。
为什么要用同一个名字?这样不会混吗?
其实不会,通过使用相同的名字,可以更好的体现两者的关联关系
——它们都用来管理和显示我们最终看到的界面。

    Q_OBJECT

这是一个宏,后续使用信号和槽时必须要写这个宏

Ctrl 加 鼠标点击转到定义

#define Q_OBJECT \
public: \
    QT_WARNING_PUSH \
    Q_OBJECT_NO_OVERRIDE_WARNING \
    static const QMetaObject staticMetaObject; \
    virtual const QMetaObject *metaObject() const; \
    virtual void *qt_metacast(const char *); \
    virtual int qt_metacall(QMetaObject::Call, int, void **); \
    QT_TR_FUNCTIONS \
private: \
    Q_OBJECT_NO_ATTRIBUTES_WARNING \
    Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
    QT_WARNING_POP \
    struct QPrivateSignal {}; \
    QT_ANNOTATE_CLASS(qt_qobject, "")

。。。看不懂,不看了

public:
    Widget(QWidget *parent = nullptr);

这里的 parent,很明显代表父亲。
但为什么要在构造里面加个 parent 参数呢?
这就和Qt的对象树有关了,这个之后再讲~~

现在简单理解为:
这个参数将子控件挂到树上。

父控件被删除时,所有的子控件也会被自动删除。
这意味着指定父控件后,不必手动 delete 子控件。

    ~Widget();

析构,不必多说。

private:
    Ui::Widget *ui;
};

uiUser Interface 的缩写,代表用户界面。
Widget 类有了 Ui::widget 类的指针,
就可以通过这个指针来管理和展示 UI 元素。

widget.cpp

接下来看看 widget.cpp
在这里插入图片描述

#include "widget.h"
#include "ui_widget.h"

引了两个头文件,
"widget.h" 好说,毕竟刚刚已经看过了。
"ui_widget.h"
这个是 Qt Designer 自动生成的 UI 文件的头文件。
什么意思?
意思是不用我们管。。。
在项目文件管理窗口里都没有这个文件,
这样设计的目的就是避免我们直接修改它。
如果想修改这里面的内容,
正确的做法是打开 .ui 文件,通过图形化界面调整界面,
之后编译的时候会自动更新 ui_widget.h

Widget::Widget(QWidget *parent)
    : QWidget(parent)

Widget 类的构造函数
这里用 QWidget 的指针,
调用了当前对象的父类 QWidget 的构造函数,
并将 parent 参数传过去。
而父类又会调用它的父类,
最终层层向上,来到了 QObject 的构造:
在这里插入图片描述
此时新创建的这个对象将被挂到对象树上,
便于统一的释放,减少内存泄漏的可能。

而对于这块,我简单模拟实现了一下Qt的对象树,
正确性未知,毕竟我没找到 QObject()的实现,只找到了声明🤣:

在这里插入图片描述

下面来试试模拟实现:

#include <iostream>
#include <vector>
using namespace std;
class QObject 
{
public:
	QObject(QObject* parent = nullptr)
		:_parent(parent)
	{
		if(parent)
		{
			parent->addNode(this);
			cout << "Add a Node" << endl;
		}
	}

	void addNode(QObject* child) { _child.push_back(child); }
	virtual ~QObject()
	{
		cout << "Delete QObject and nodes" << endl;
		for (auto e : _child) 
		{ 
			if(e)
				delete e;
			e = nullptr;
			cout << "Delete a child" << endl;
		}
	}
private:
	QObject* _parent;
	vector<QObject*> _child;
};
class QWidget : public QObject
{
public:
	QWidget(QObject* parent = nullptr)
		:QObject(parent)
	{
		cout << "QWidget()" << endl;
	}
};
int main()
{
	QObject a;
	QWidget* b = new QWidget(&a);
	QWidget* c = new QWidget(b);
	return 0;
}

运行结果:

Add a Node
QWidget()
Add a Node
QWidget()
Delete QObject and nodes
Delete QObject and nodes
Delete QObject and nodes
Delete a child
Delete a child

上面的代码仅供参考。。。
如果对象是在栈上创建的,这段会直接报错(多次析构了)。
听说可以用智能指针解决,我这里就不写了。


    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

而之后,又 new 了一个 Ui::Widget ,用来设置 ui

在这里插入图片描述
为指定的小部件设置用户界面。

这里没有在Ui::Widget的构造中传入父对象的指针,为什么?
因为Ui::Widget就没有直接的父子管理能力!
对于Ui::Widget,可以理解为它主要是由图形化界面操控的,
所以和其他的QObject对象关系不大,因此没有父子关系。
再进一步讲,在设计原则中,有非常重要的六个字: “先描述,在组织”
在这儿就可以认为Ui::Widget负责描述,
而其他的QObject主要负责组织。
这样的设计模式可以方便我们将看到的界面和其背后应用程序的代码逻辑分离。

与之对应,其他的控件最好在构造时就传入父对象的指针,
不要等创建完了再 set ,不然有可能会导致析构被两次调用。

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPushButton pushButton;
    Widget w;
    pushButton.setParent(&w);
    w.show();
    return a.exec();
}

当退出时:
在这里插入图片描述

Widget::~Widget()
{
    delete ui;
}

这个刚刚提过,ui 并没有在 Qt 的对象树上,因此这里需要手动 delete

widget.ui

再来看看这个 widget.ui
在这里插入图片描述
双击会来到一个图形化的界面:
在这里插入图片描述
在这里我们可以拖动左边的控件到中间的窗口,
然后可以在右边那个黄色的框里面设置对应的属性。
当我们再次编译时,widget.ui会自动生成对应的代码。
而点击左边的编辑,会来到widget.ui文件:
在这里插入图片描述
可以看见,这个文件只能在 设计模式 下更改,也就是刚刚的图形化界面。
然后,我们还能看见这个文件是 xml 格式的,
这说明了什么?
说明了只学过C/C++的我看不懂。。

HelloWorld.pro

最后是 HelloWorld.pro
在这里插入图片描述

QT       += core gui

引入 Qt 的模块,以后可能会改这里。
现在?暂时不管~~

CONFIG += c++11

指定使用 C++11 标准进行编译。
如果没加这句,一些C++11的东西可能就用不了了。

SOURCES += \
    main.cpp \
    widget.cpp

HEADERS += \
    widget.h

FORMS += \
    widget.ui

SOURCES列出项目中所有的源文件。
HEADERS列出项目中所有的头文件。
FORMS列出项目中的 .ui 文件。
这个地方也是自动生成的,不用我们管。

🛠️ 中间文件

除了这些,Qt项目编译运行后,
还会多出一些中间文件。
一般会在与 项目目录 并列的地方,
多一个名字很像的目录(build-...):
在这里插入图片描述
打开:
在这里插入图片描述
看到了熟悉的东西:Makefile
不过这个也是自动生成的,不用我们管。
ui_widget.h ,这就是自动生成的 widget.ui 的头文件!
因此在刚刚的 widget.ui 中引用的就是这个东西。

ui_widget.h

在这里插入图片描述
前面几句是注释,
比较有意思的是它还提示我们在该文件的任何修改都是无效的:
WARNING! All changes made in this file will be lost when recompiling UI file!

class Ui_Widget {
public:

这个类就是在 widget.h 中的 Ui::Widget
因为在后面的命名空间 Ui 中,
用一个叫做Widget的空类继承了这个 Ui_Widget

void setupUi(QWidget *Widget)
{
    if (Widget->objectName().isEmpty())
        Widget->setObjectName(QString::fromUtf8("Widget"));
    Widget->resize(800, 600);

    retranslateUi(Widget);

    QMetaObject::connectSlotsByName(Widget);
} // setupUi

在刚刚 Widget 的构造中,
Widget 对象将它的 this 指针传给了这个函数。

而这个函数的作用,
就是通过图形化界面中的内容,
Widget 对象进行初始化:

  • if语句
    检查 Widget 的对象名是否为空,
    如果是,则设置对象名为 “Widget”。
  • Widget->resize(800, 600);
    调整窗口的大小到 800x600 像素。
  • retranslateUi(Widget);
    翻译?
  • QMetaObject::connectSlotsByName(Widget);
    从名字猜作用:
    connect连接 Slots槽 By通过 Name名字
   void retranslateUi(QWidget *Widget);

这个应该是用来翻译的。

namespace Ui {
    class Widget: public Ui_Widget {};
} // namespace Ui

这就是刚刚说的命名空间,
它用来将Ui相关的类都封在一个命名空间下,
可能便于理解?
比如我一看到 Ui::Widget ,噢,这是用来界面设计的。
再看到 Widget ,噢,这是用来写代码的。

.exe

最后再看个东西:
在这里插入图片描述
这个 exe ,就是最终生成的可执行程序,
和之前Qt Creater 中运行的效果一致。

在这里插入图片描述


希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;