(2)选择项目模板
弹出如下对话框:
新建项目对话框里有五类项目模板:
常用的只有第⼀类 Application,选择它之后,在右侧会看到 Qt 应用程序的五个子模板:
Qt Widgets Application:普通窗体模板,传统基于部件的窗体界面程序。(如果使用 Qt 写一个 GUI 程序,就选择这个)Qt Console Application:Qt 控制台应用程序。因为 Qt 主要用于图形界面设计,这个控制台项目模板基本不用。 Qt for Python:在 Python 下用 LGPL 的许可来开发闭源 Qt 软件。(不仅支持 C++,还支持 Python)Qt Quick Application:Qt 提供的⼀种高级用户界面技术,使用它可以方便快速的为移动以及嵌入式设备开发流畅美观的用户界面。Qt Quick 模块是开发 QML 应用的标准库,提供了使用 QML 创建用户界面所需的⼀切,包括可视化、交互、动画、模型、视图、粒⼦效果以及着色效果等。
选择不同的项目模板,Qt Creator 就会在后续项目创建好了之后生成不同的基础代码。
(3)选择项目路径
给 Qt 项目命名及选择保存项目的路径:
(4)选择构建系统
选择 Qt 项目的构建系统,通过 Qt 写的程序,涉及到一系列的 “元编程” 技术,通过代码来生成代码。
使用默认的 "qmake" 即可:
Qt 中的构建⼯具有三种可供选择,分别是:qmake、CMake、Qbs。
qmake: qmake 是⼀个构建工具(build tool),用于自动生成 makefile 文件。qmake 支持跨平台构建。qmake 编辑的是⼀个后缀名为 .pro 的文件。 CMake:CMake 是⼀个跨平台的构建工具。CMake 本身不是⼀个编译器,其实就是生成⼀个让编译器能读懂编译流程的⽂件工具。让 CMake 自动生成构建系统,例如 Makefile 和 Visual Studio 项目文件。CMake 是⼀个第三方工具,有自己的文档。(实际上 CMake 并非 Qt 专属,很多开源项目都会使用 CMake)Qbs:Qbs(Qt Build Suite:Qt构建套件)同 qmake、CMake ⼀样都是构建工具。Qbs 号称是新一代的构建⼯具,比 qmake 编译速度更快。Qbs 没有绑定 Qt 版本,它从项目文件的高级项目描述中生成⼀个正确的依赖表。而传统的 MakeFile 生成工具如 qmake 和 CMake ,其在生成 MakeFile 文件后将实际的命令交给 Make 工具去执行。
Qt 框架会在编译时自动先调用一系列的生成工具,基于我们自己写的代码生成一系列其它的 C++ 代码,最终编译的代码也是最后生成的这些代码。
Qt 官方声明:因市场原因,弃用 Qbs(新一代的 Qt 构造工具,但实际上使用的人很少)。
对于 Qt 用户来说,qmake 是当前使用最广泛的构建工具,CMake 其次。
(5)填写类信息设置界面
使用 Qt Creator 创建项目会自动生成一些代码出来,生成的代码就包含一个类,此处就是要选择这个自动生成的类的父类是什么。
对于基类的选择,目前有三种基类:
这里生成的文件名是和类名关联的,这样的关联并非是强制的(但推荐这么做)。
这个 Form file 非常关键,Qt 中创建图形化界面的程序有两种方式:
直接通过 C++ 代码的方式创建界面。通过 Form file,以图形化的方式来生成界面。此时就可以使用 Qt Designer 或者直接使用 Qt Creator 来编辑这个 ui 文件,从而以图形化的方式快速方便的生成图像界面。
Qt 内置的类都是以 Q 为前缀开头的。
上述三个类之间的关系如下图:
(6)选择语言和翻译文件
点击 “下⼀步” 进入如下界面:
此处选择翻译文件(对应语言)是:“汉语”,“英语” 这样的语言,而不是 “编程语言”
由于我们暂时不考虑国际化问题,直接点击 “下一步” 即可。
(7)选择 Qt 套件
选择一下基于哪个编译器的 Qt SDK来构建后续代码。默认只有第一个 "Desktop Qt 5.14.2 MinGW 64-bit",如果安装配置了多个 Qt 套件,就可以都选择上。
Qt 套件是指:Qt 程序从编译链接到运行环境的全部工具和 Qt 类库的集合,对于 MinGW 版本 Qt 程序生成和调试,至少需要 MinGW 中的编译器 g++(自动调用链接器)、g++ 配套的基础库、调试器 gdb 还有使用 MinGW 环境编译而成的 Qt 类库自身。默认情况下,在上面 Kit Selection 里选中全部套件。
(8)选择版本控制系统
点击 “下⼀步” 进入项目管理界面。在项目管理界面可以设置作为子项目,以及加⼊版本控制系统管理。这两个功能暂时用不到,都用默认的
如果想把代码提交到码云或者 github,可以在此处选择 git 作为版本控制系统。
(9)最终效果
通过上述 8 个步骤,完成了项目的创建。项目创建完成之后,Qt Creator 会直接进入代码编辑模式,可以看到类似下图界面:
3、认识 Qt Creator 界面
(1)左边栏
在编辑模式下,左边竖排的两个窗口叫做 “边栏” 。
① 项目文件管理窗口,② 打开文件列表窗口。
在 QtCreator 菜单 “控件” ——> "Show Left Sidebar",或者使用快捷键:"Alt + 0" 可以控制边栏的显示和隐藏。
边栏里的窗口数码可以增加,边栏子窗口标题栏有一排小按钮,最右边的是关闭按钮,倒数第二个是增加分栏按钮,可以添加多个边栏子窗口。
边栏子窗口标题栏第一个控件是组合框,可以选择该子窗口的功能视图类型,目前可以选择 8 个视图类型:
(2)代码编辑区
① 和 ②:导航按钮 “返回” 和 “前进”,这与网页浏览器的前进和后退按钮类似,可以在之前浏览的多个代码文件或⼀个代码文件里多个位置之间快速切换。 ③:标识当前显示的文件是只读还是可写,⼀般都是可写的。 ④:文件类型图标,当前显示文件的类型,这个控件其实是⼀个菜单按钮,点击可以弹出丰富的文件处理功能菜单。 ⑤:打开的文件名,可以在多个打开的文件之间选择切换,与边栏的 “打开文档” 视图是对应的。 ⑥:关闭当前显示的文档。 ⑦:为当前显示的文件添加额外的 C++ 预处理指令(⼀般用不到)。⑧:选择符号,可以在当前显示的文件里多个函数、类、成员变量等之前快速切换,与边栏 “大纲” 视图是对应的。 ⑨:编辑区光标的行号和列号。 ⑩:代码编辑区分栏,可以增加多个编辑器窗口,显示多个打开的文档或显示较大源码文件的多个位置。
行首区:主要用来显示代码行号,以及调试断点标志和代码书签标志。右击行首区可以弹出右键菜单,菜单里可以切换书签、编辑书签以及设置或取消断点。
同⼀行是既可以打断点也可以设置书签的,二者不冲突(其实它们根本就没关系)。单击行号前面的浅灰色空白区可以直接打断点,再次单击可以取消断点,另外也可以用快捷键 F9 设置或取消断点。代码书签⼀般用右键菜单来设置,也可以⽤快捷键 Ctrl+M 设置或取消书签。
编辑区:写代码的区域。
(3)UI 设计界面
双击 widget.ui 文件,Qt Creator 会自动进入设计模式,可以对图形界面进行可视化编辑:
① 组件选择窗口(Qt 中内置的控件)。组件选择窗口分为多个组,如 Layouts、Buttons、Display Widgets 等,界面设计的常见组件都可以在组件选择窗口中找到。拖拽左侧的控件到程序窗口中就可以创建出具体的界面了。② UI 设计窗口。如果要将某个组件放置到该窗口上时,从组件选择窗口上拖放⼀个组件到窗体上即可。 ③ 动作编辑窗口。动作编辑窗口包括 Action Editor 以及 Signals 和 Slots 编辑器。 Action Editor 主要是用来新建 Action,并且通过拖拽的动作,将新建好的 Action 添加到菜单栏和工具栏上;Signals 和 Slots 编辑器用于可视化地进行信号与槽的关联。 ④ 对象浏览窗口。用树状视图显示窗体上各组件之间的布局包含关系,视图有两列,显示每个组件的对象名称(ObjectName)和类名称。 ⑤ 属性设置窗口。显示某个选中的组件或窗体的各种属性及其取值,可以在属性设置窗口里修改这些属性的值。(每个控件都有很多属性,这些属性影响着控件的具体行为)
(4)构建区
第一个按钮是选择构建项目使用的 Qt 套件和构建目标程序的类型(Debug 或 Release)。
对于第一个按钮,默认的是 Debug,构建的是 Debug 类型的目标程序。如果需要构建 Release 版目标程序,点开左下角第一个按钮:
这里有三种构建模式:
上图是针对项目只用户到单一 Qt 套件的,如果之前配置了多个 Qt 套件,看到的类似下图:
如果项目配置了多个可用的 Qt 套件,点开左下角第一个按钮后,会看到各个套件以及构建类型,如果要切换 Qt 套件或构建类型,直接选中相应条目,然后点击运行按钮就行了。如果构建和运行时没出错,就会显示出构建好的目标程序界面。
第⼆个是运行按钮,快捷键是 Ctrl+R,如果还没构建项目或刚修改了代码,直接点击运行的话,QtCreator 会自动构建生成新的目标程序并运行。 第三个是调试按钮,快捷键是 F5。调试程序之前,QtCreator 会自动构建⽣成最新的目标程序,并进入调试模式。 第四个是构建按钮,快捷键是 Ctrl+B,只构建最新的目标程序,但不运行。
四、Qt Hello World 程序
1、使用 “按钮” 实现
(1)纯代码方式实现
按钮对象是我们自己 new 的,为了保证其他函数中能够访问到这个变量,就需要把按钮对象设定为 Widget 类的成员变量。
(2)可视化操作实现
A. 双击:"widget.ui" 文件
此时 Qt Creator 会调用 Qt Designer,打开 ui 文件。
B. 拖拽控件至 ui 界面窗口并修改内容
图形化的界面编辑器:
通过图形化界面实现:
这里的按钮对象不需要我们自己 new,new 对象这个操作已经是被 Qt 自动生成了,而且这个按钮对象已经作为 ui 对象里的一个成员变量,也无需作为 Widget 的成员。
C. 构建并运行
点击之后:
再点击:
2、使用 “标签” 实现
(1)纯代码方式实现
创建标签有两种方式:
QLabel* label = new QLabel(this); //更推荐在堆上创建的方式QLabel label; //在栈上创建
上述代码在 Qt 中不会产生内存泄漏,label 对象会在合适的时候被析构释放(虽然没有手动写 delete),是因为把这个对象挂到了对象树上。
实现效果:
(2)可视化操作实现
A. 双击:"widget.ui" 文件
B. 拖拽 “标签” 至 UI 设计界面中,并双击修改标签内容
刚才往界面上拖拽了一个 QLabel 控件,此时的 ui 文件的 xml 中就会多出这一段代码:
进一步的 qmake 就会在编译项目时,基于这个内容生成一段 C++ 代码,通过这个 C++ 代码构建出界面内容。
C. 实现效果
3、使用 “编辑框” 实现
单行编辑框:QLineEdit多行编辑框:QTextEdit
(1)纯代码方式实现
实现效果:
(2)可视化操作实现
A. 双击:"widget.ui" 文件
B. 拖拽 “标签” 至 UI 设计界面中,并双击修改内容
C. 构建并运行
五、项目文件解析
1、.pro 文件解析
工程新建好之后,在工程目录列表中有⼀个后缀为 ".pro" 的文件, ".pro" 文件就是工程文件(project),它是 qmake 自动生成的用于生产 makefile 的配置文件。如图所示:
.pro 类似之前 Linux 中学过的 Makefile 文件,qmake 搭配 .pro 的作用与其类似。
".pro" 文件的写法如下:
注释:从 "#" 开始,到这一行结束。 QT += core gui // Qt 包含的模块 Qt5 包含的模块如下图所示:
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 这条语句的含义是:如果 QT_MAJOR_VERSION 大于 4 也就是当前使用的 Qt5 及更高版本) 需要增加 widgets 模块。如果项目仅需支持 Qt5 ,也可以直接添加 "QT += widgets" 一句。不过为了保持代码兼容,最好还是按照 QtCreator 生成的语句编写。 指定生成的应用程序名:TARGET = QtDemo TEMPLATE = app //模板。告诉 qmake 为这个应用程序生成哪种 makefile。下面是可供选择的模板:
app:建立⼀个应用程序的 makefile。这是默认值,所以如果模板没有被指定,这个将被使
用。
lib:建立⼀个库的 makefile。
vcapp:建立⼀个应用程序的 VisualStudio 项目文件。
vclib:建立⼀个库的 VisualStudio 项目文件。
subdirs:这是⼀个特殊的模板,它可以创建⼀个能够进⼊特定⽬录的 makefile 并且为它调用 make 的 makefile。
工程中包含的源文件:SOURCES += main.cpp/widget.cpp 工程中包含的头文件:HEADERS += widget.h 工程中包含的资源文件:RESOURCES += painter.qrc 工程中包含的 "ui" 设计文件:FORMS += widget.ui 配置信息:CONFIG += c++11(使用 c++11 的特性)CONFIG 用来告诉 qmake 关于应⽤程序的配置信息。
2、widget.h 文件解析(Widget 类的声明)
Qt 的设定:使用 Qt 内置的类,包含的头文件的名字和类名是一致的。当然,并不是用到的所有的 Qt 的类都需要显示的包含头文件,在 C++ 中,头文件可能是 “间接包含” 的。比如:引入了 a.h,a.h 里又包含了 b.h,此时就相当于把 a.h 和 b.h 都包含了。
在 Qt 中,如果要使用信号与槽(signal 和 slot)这个核心机制,就必须引入 Q_OBJECT 宏。Q_OBJECT 展开之后就会生成一大堆代码。
QWidget *parent = nullptr:创建的 Qt 的对象,就可以把对象给挂到对象树上,往树上挂的时候需要指定 “父节点”(此处的对象树是一个普通的 N 叉树(不是二叉))。
Ui::Widget *ui;:通过这个指针可以访问 UI 设计界面中的任意控件,它是用前面声明的 namespace Ui 里的 Widget 类定义的。
3、main.cpp 文件解析
使用 Qt Creator 新建任意工程之后,main.cpp 文件中都会自动生成如下代码:
之前在 Linux 学习中,也学过六个函数(本质上是一个),叫作 exec(进程程序替换),把可执行文件中的代码和数据,替换到当前进程中。(Linux 中的 exec 和 当前 Qt 中的 exec 没有任何关系)
解释:
Qt 系统提供的标准类名声明头文件没有 .h 后缀。Qt ⼀个类对应⼀个头文件,类名就是头文件名。QApplication 为应用程序类,QApplication a;(a 为应用程序对象,有且仅有⼀个)
QApplication 管理图形用户界面应用程序的控制流和主要设置。 QApplication 是 Qt 的整个后台管理的命脉。它包含主事件循环,在其中来自窗口系统和其它资源的所有事件处理和调度。它也处理应用程序的初始化和结束,并且提供对话管理。 对于任何⼀个使用 Qt 的图形用户界面应用程序,都正好存在一个 QApplication 对象,而不论这个应用程序在同一时间内是不是有 0、1、2 或更多个窗口。
Widget w; //实例化窗口对象 a.exec();:程序进入消息循环,等待对用户输入进行响应。这里 main()把控制权转交给 Qt,Qt 完成事件处理工作,当应用程序退出的时候 exec() 的值就会返回。在 exec() 中,Qt 接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
4、widget.cpp 文件解析
widget.cpp 文件是类 Widget 的实现代码,所有在窗体上要实现的功能添加在此文件中。
5、widget.ui 文件解析
此时再点击左侧边栏的 “编辑” 按钮,那么此时显示的内容就是 .ui 文件的本体:
widget.ui 是窗体界面定义文件,是一个 XML 文件(和 html 很相似,都是使用成对的标签来表示数据),定义了窗口上的所有组件的属性设置、布局,及其信号与槽函数的关联等。用 UI 设计器可视化设计的界面都由 Qt 自动解析,并以 XML 文件的形式保存下来。在设计界面时,只需在 UI 设计器里进行可视化设计即可,而不用管 widget.ui 文件是怎么生成的。 进一步的 qmake 会调用相关的工具,根据这个 xml 文件生成一些 C++ 代码,从而把完整的界面构造出来。
六、Qt 编程注意事项
1、Qt 中的命名规范
类名:首字母大写,单词和单词之间首字母大写。函数名及变量名:首字母小写,单词和单词之间首字母大写。
Qt 偏好驼峰命名法。
2、Qt Creator 中的快捷键
注释:Ctrl + / 运行:Ctrl + R 编译:Ctrl + B 字体缩放:Ctrl + 鼠标滑轮 查找:Ctrl + F整行移动:Ctrl + Shift + ⬆/⬇帮助文档:F1(+Fn)自动对齐:Ctrl + i同名之间的 .h 和 .cpp 的切换:F4(+Fn)生成函数声明的对应定义:Alt + Enter
3、使用帮助文档
打开帮助文档有三种方式,实际编程中使用哪种都可以。
(1)光标放到要查询的类名 / 方法名上,直接按 F1
(2)Qt Creator 左侧边栏中直接用鼠标单击 “帮助” 按钮。
(3)找到 Qt Creator 的安装路径,在 "bin" 文件夹下找到 assistant.exe,双击打开
新建项目,在新建的项目中使用 Qt 中的 "QpushButton" 控件。打开帮助手册,在 "索引" 里面输入 "QpushButton":
4、认识对象模型(对象树)
在 Qt 中创建很多对象的时候会提供一个 Parent 对象指针,下面来解释这个 parent 到底是干什么的。
(1)QObject 是以对象树的形式组织起来的
当创建⼀个 QObject 对象时,会看到 QObject 的构造函数接收一个 QObject 指针作为参数,这个参数就是 parent,也就是父对象指针。 这相当于在创建 QObject 对象时,可以提供⼀个其父对象,我们创建的这个 QObject 对象会⾃动添加到其父对象的 children() 列表。 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意:这里的父对象并不是继承意义上的父类)
这种机制在 GUI 程序设计中相当有用。例如:一个按钮有一个 QShortcut(快捷键)对象作为其子对象。当删除按钮的时候,这个快捷键理应被删除。这是合理的。
(2)QWidget 是能够在屏幕上显示的一切组件的父类。
QWidget 继承自 QObject ,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭⼀个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该⼀起被删除。事实就是如此,因为这些都是对话框的⼦组件。 当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
(3)解决了内存问题
当⼀个 QObject 对象在堆上创建的时候,Qt 会同时为其创建⼀个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。 任何对象树中的 QObject 对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的 children() 列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有 QObject 会被 delete 两次,这是由析构顺序决定的。
如果 QObject 在栈上创建,Qt 保持同样的行为。正常情况下,这也不会发生什么问题。来看下面的代码片段:
作为父组件的 window 和作为子组件的 quit 都是 QObject 的子类(事实上,它们都是 QWidget 的子类,而 QWidget 是 QObject 的子类)。这段代码是正确的,quit 的析构函数不会被调用两次,因为标准 C++ 要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。
但是,如果我们使用下面的代码:
情况又有所不同,析构顺序就有了问题。我们看到,在上面的代码中,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说,quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。
由此我们看到,Qt 的对象树机制虽然在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以最好从开始就养成良好习惯。
在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。
(4)Qt 对象树图示
这里的树上的这些对象,统一销毁时最好不过的。如果某个对象提前销毁,此时就会导致对应的控件在界面上不存在了。所以,我们选择通过 new 的方式来创建对象就是为了把这个对象的生命周期交给 Qt 的对象树来统一管理。
当把对象改在栈上创建,那么此时就可以看到运行起来的程序无法显示出 hello world。此时 label 对象随着构造函数的结束就销毁了。
(5)代码示例
创建一个新工程并编译运行,生成如下窗口:
选中工程程名,鼠标右键 ——> "add new..."(或 “添加新文件”):
或者选择左上角 “文件” ——> “新建文件或项目”:
弹出如下界面:
参考图:
此时手动创建类的头文件以及源文件会自动添加到目标工程中:
修改头文件:
编写源文件:
编译并运行:
当关闭弹出的对话框时,就会自动调用标签的析构函数:
可以看到,这里的结果出现了乱码(编码方式不匹配)。
在计算机中,一个汉字占几个字节?
针对这个问题,不能回答出一个具体的数字。需要有提前条件:当前中文编码使用的是哪种方式(字符集)。
计算机存的是二进制数字。英文字母是用 ASCII 码表表示的,规定了每个字符都有一个对应的数字来表示。
只是表示英文,一个字节就足够了,毕竟英文字母数目非常有限。
日常的常用汉字大概是 4k 多个,算上各自生僻字,总数差不多是 6w 左右。
因为表示汉字的字符集有很多种,不同的字符集表示同一个汉字,可能使用的数字并不相同。
目前,表示汉字字符集主要是以下两种方式
GBK(中国大陆),使用 2 个字节表示一个汉字。Windows 简体中文版默认的字符集就是 BGK。UTF-8 / utf8,变长编码,表示一个符号,使用的字节数有变化(2-4),但是一般一个汉字是 3 个字节。Linux 中默认的字符集是 utf8。
Qt Creator 内置的终端不是 utf8 的方式来显示字符串,且这个终端好像不能设置字符编码。Qt 中有一个 QString,可以帮助我们自动处理编码方式,且还提供了专门用来打印日志的工具 qDebug(),也能很好的自动处理编码方式。
qDebug 这个宏里封装了 QDebug 对象,可以直接使用qDebug(),可以被当作 cout 来使用。
对象树确保的是先释放子节点的内存,后释放父节点的内存。而析构函数的调用顺序则不一定遵守上述要求,因此看到子节点的析构执行顺序反而在父节点析构顺序之后。
注意:调用析构函数和释放内存并非是同一件事情。
5、Qt 窗口坐标体系
坐标体系:以左上角为原点(0,0),X 向右增加,Y 向下增加。
对于嵌套窗口,其坐标是相对于父窗口来说的。示例:使用 Qt 中的坐标系设置控件的位置:分别对 “按钮” 和 widget 本身移动位置:
运行结果如下图示: