可以看到,Qt Creator 帮助我们在 HelloWorld 项目文件夹下生成了四个文件:main.cpp,widget.cpp,widget.h 和 HelloWorld.pro。pro 文件就是 Qt 工程文件(project file),由 qmake 处理,生成 make 程序所需要的 makefile;其他两个文件就是先前我们曾经指定的文件名的文件。
//HelloWorld.pro
#-------------------------------------------------
#
# Project created by QtCreator 2022-08-05T08:08:15
#
#-------------------------------------------------
# 默认参数
QT += core gui
# QT版本大于4
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = HelloWorld //生成exe文件的名称
# 项目生成(lib-建立一个库的makefile;vcapp-建立一个应用程序的VS项目文件;vclib-建立一个库的VS项目文件;subdirs-这是一个特殊的模板,它可以创建一个能够进入特定目录并且为一个项目文件生成马克file并且为它调用make的makefile)
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
# 配置信息
CONFIG += c++11
# 源文件
SOURCES += \
main.cpp \
widget.cpp
# 头文件
HEADERS += \
widget.h
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
其他两个文件就是先前我们曾经指定的文件名的文件。
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include
class Widget : public QWidget //所有控件的基类,就是窗口
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
}
Widget::~Widget()
{
}
main.cpp 里面就是一个main函数,作为应用程序的入口函数;
#include "widget.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);//QT框架的初始化
Widget w;
w.show();
return a.exec();//作用是让程序循环不死,检测事件的发生
}
- Qt系统提供的标准类名声明头文件没有.h后缀
- Qt一个类对应一个头文件,类名就是头文件名
- QApplication应用程序类
管理图形用户界面应用程序的控制流和主要设置。
是Qt的整个后台管理的命脉它包含主事件循环,在其中来自窗口系统和其它资源的所有事件处理和调度。它也处理应用程序的初始化和结束,并且提供对话管理。
对于任何一个使用Qt的图形用户界面应用程序,都正好存在一个QApplication对象,而不论这个应用程序在同一时间内是不是有0、1、2或更多个窗口。- a.exec()
程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给QL,Qu完成事件外理工作,当应用程序退出的时候exee0的值就会返回。在 exec()中,Qi接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
点击 Qt Creater 左侧下面的绿色三角按钮即可运行(这里一共有三个按钮,从上到下分别是“运行”、“调试”和“构建”)。如果没有错误的话,就会看到运行结果。
通过右键点击,找到项目所在文件夹
此时显示项目文件夹中文件的类型有
打开编译文件夹
新建一个空项目
会发现这个空项目只有一个pro文件,此时就可以手动新建编写文件
新建main.cpp
添加主框架代码,会发现有报错如图1,这时选中QApplication
按F1,打开帮助文档如图2,会发现在qmake中需要添加如图语句,而qmake的文件正是pro文件,所以在pro文件中添加即可,如图3。
添加窗口QWidget,并显示(对于控件函数的使用可以选中类使用F1来查找)。控制台打印输出信息QDebug。
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);//QT框架的初始化
QWidget widget;
widget.resize(400,300);//设置窗口大小
widget.setWindowTitle("Hello World!");//设置窗口标题
widget.show();
qDebug()<<"Hello World!";
return a.exec();//作用是让程序循环不死,检测事件的发生
}
Tip:在栈和堆上创建对象的区别
#include
#include int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("Hello, world"); label.show(); return app.exec(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
前两行是 C++ 的 include 语句,这里我们引入的是QApplication以及QLabel这两个类。main()函数中第一句是创建一个QApplication类的实例。对于 Qt 程序来说,main()函数一般以创建 application 对象(GUI 程序是QApplication,非 GUI 程序是QCoreApplication。QApplication实际上是QCoreApplication的子类。)开始,后面才是实际业务的代码。这个对象用于管理 Qt 程序的生命周期,开启事件循环,这一切都是必不可少的。在我们创建了QApplication对象之后,直接创建一个QLabel对象,构造函数赋值“Hello, world”,当然就是能够在QLabel上面显示这行文本。最后调用QLabel的show()函数将其显示出来。main()函数最后,调用app.exec(),开启事件循环。我们现在可以简单地将事件循环理解成一段无限循环。正因为如此,我们在栈上构建了QLabel对象,却能够一直显示在那里(试想,如果不是无限循环,main()函数立刻会退出,QLabel对象当然也就直接析构了)。
如果在堆上创建对象呢#include
#include int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel *label = new QLabel("Hello, world"); label->show(); return app.exec(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
答案是,不建议这样做!
首先,按照标准 C++ 来看这段程序。这里存在着内存泄露。当exec()退出时(也就是事件循环结束的时候。窗口关闭,事件循环就会结束),label 是没办法 delete 的。这就造成了内存泄露。当然,由于程序结束,操作系统会负责回收内存,所以这个问题不会很严重。即便你这样修改了代码再运行,也不会有任何错误。
早期版本的 Qt 可能会有问题(详见本文最后带有删除线的部分,不过豆子也没有测试,只是看到有文章这样介绍),不过在新版本的 Qt 基本不存在问题。在新版本的 Qt 中,app.exec()的实现机制确定,当最后一个可视组件关闭之后,主事件循环(也就是app.exec())才会退出,main()函数结束(此时会销毁app)。这意味着,所有可视元素已经都关闭了,也就不存在后文提到的,QPaintDevice没有QApplication实例这种情况。另外,如果你是显式关闭了QApplication实例,例如调用了qApp->quit()之类的函数,QApplication的最后一个动作将会是关闭所有窗口。所以,即便在这种情况下,也不会出现类这种问题。由于是在main()函数中,当main()函数结束时,操作系统会回收进程所占用的资源,相当于没有内存泄露。不过,这里有一个潜在的问题:操作系统只会粗暴地释放掉所占内存,并不会调用对象的析构函数(这与调用delete运算符是不同的),所以,很有可能有些资源占用不会被“正确”释放。事实上,在最新版的 Sailfish OS 上面就有这样的代码:#include
int main(int argc, char *argv[]) { QScopedPointer<QApplication> app(new QApplication(argc, argv)); QScopedPointer<QQuickView> view(new QQuickView); view->setSource("/path/to/main.qml"); ... return app->exec(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
这段代码不仅在堆上创建组件实例,更是把QApplication本身创建在了堆上。不过,注意,它使用了智能指针,因此我们不需要考虑操作系统直接释放内存导致的资源占用的问题。
当然,允许使用并不一定意味着我们建议这样使用。毕竟,这是种不好的用法(就像我们不推荐利用异常控制业务逻辑一样),因为存在内存泄露。而且对程序维护者也是不好的。所以,我们还是推荐在栈上创建组件。因为要靠人工管理new和delete的出错概率要远大于在栈上的自动控制。除此之外,在堆上和在栈上创建已经没有任何区别。
如果你必须在堆上创建对象,不妨增加一句:label->setAttribute(Qt::WA_DeleteOnClose);//在窗口接受了关闭事件后,Qt会释放这个窗口所占用的资源,这样在关闭这个窗口时Qt能够自动回收该窗口所占用的资源,这样能够及时回收无效的资源,有用利于节约内存空间。
- 1
这点提示足够告诉程序维护者,你已经考虑到内存问题了。