目录
2. class Widget : public QWidget
根据上面一篇文章 已经成功创建出了 qt 文件的 uu,可以直接跳转到第四部分进行 hello world 的测试,前三部分主要是对创建页面,再进行了一个详细的介绍,之前有 uu 问道过一些问题~
专栏会持续更新~
1. Qt Creator 概览
启动 Qt Creator 后,用户将面对一个集成了多种开发工具的界面,主要包括:
- 菜单栏:提供8个主要功能菜单。
菜单选项 | 功能描述 |
文件 | 新建、打开、关闭项目和文件、打印和退出等基本操作。 |
编辑 | 撤销、剪切、复制、查找和选择编码等功能,支持代码编辑的基本需求。 |
构建 | 提供构建和运行项目的工具,帮助开发者编译并执行其代码。 |
调试 | 包含调试运行项目的功能,允许设置断点、单步执行和查看变量等。 |
Analyze | 集成了 QML 分析器、Valgrind 内存分析器等功能,用于性能优化。 |
工具 | 提供了环境、文本编辑器、帮助、构建和运行、调试器及版本控制的设置选项。 |
控件 | 控制窗口布局,如全屏显示、边栏的显示与隐藏等界面管理功能。 |
帮助 | 包含 Qt 和 Qt Creator 的帮助文档、版本信息、bug 报告和插件管理等。 |
示意图:
- 模式选择:切换不同工作模式。
模式名称 | 功能描述 |
欢迎模式 | 提供快捷入口,如教程、示例程序、打开项目、新建项目、快速访问之前的项目和会话等。 |
编辑模式 | 主要用于查看和编辑代码,具备关键字高亮、自动补全、函数原型提示等功能,并可对编辑器进行个性化配置。 |
设计模式 | 整合了 Qt Designer 功能,用于图形界面设计,包括部件属性设置、信号槽连接和布局调整等。 |
调试模式 | 支持详细的调试功能,如断点设置、单步执行、远程调试,以及查看局部变量和监视器等。 |
项目模式 | 提供针对特定项目的构建、运行、编辑器、代码风格和依赖关系设置,有助于项目管理和优化。 |
帮助模式 | 整合了 Qt 助手,提供目录、索引、搜索和书签导航,便于查阅 Qt 和 Qt Creator 的相关信息。 |
- 构建套件选择器:包括目标、运行、调试和构建按钮。
组件名称 | 功能描述 |
目标选择器 | 选择要构建的项目和使用的 Qt 库版本,支持 debug 和 release 版本的选择。 |
运行按钮 | 实现项目的构建和运行,简化从编写到测试的过程。 |
调试按钮 | 启动项目的调试过程,方便开发者排查错误。 |
构建按钮 | 完成项目的构建步骤,确保代码可以正确编译。 |
- 欢迎模式窗口:展示初次使用或无打开项目时的引导信息。
- 定位器:快速查找项目元素及帮助文档。
- 输出窗格:显示问题、搜索结果、应用程序输出等7类信息。
- 会话记录:管理工程历史。
- 新建/打开项目:创建或加载已有项目。
2. 使用 Qt Creator 新建项目
创建新项目的过程涉及多个步骤,包括但不限于:
- 选择项目模板:如 Qt Widgets Application、Qt Console Application 等。
新建项⽬对话框⾥有五类项⽬模板:
💡 选择不同的项⽬模板,Qt Creator 就会在后续项⽬创建好了之后⽣成不同的基础代码.
- 设置项目路径:指定项目保存位置。
- 选择构建系统:默认使用
qmake
,也支持CMake
和Qbs
。
qmake
- 描述:
qmake
是一个用于自动生成 Makefile 文件的构建工具,支持跨平台构建。 - 文件格式:它编辑的是后缀名为
.pro
的项目配置文件。 - 特点:
qmake
与 Qt 紧密集成,是 Qt 用户中最广泛使用的构建工具。
CMake
- 描述:
CMake
是一个强大的跨平台构建系统生成器,不是编译器,而是生成让编译器理解的构建流程文件(如 Makefile 或 Visual Studio 项目文件)。 - 特点:作为一个第三方工具,
CMake
并非 Qt 专属,因其灵活性和强大功能被众多开源项目采用。它拥有独立的文档和支持社区。
Qbs
- 描述:
Qbs
(Qt Build Suite),被称为新一代构建工具,以其更快的编译速度著称。 - 特点:
Qbs
不绑定特定的 Qt 版本,从高级项目描述中生成依赖表。不同于qmake
和CMake
,它不依赖于 Make 工具执行实际命令。 - 现状:尽管有其优势,但因市场接受度较低,Qt 官方已宣布弃用
Qbs
。
构建过程概述:
- 在使用这些工具时,Qt 框架会在编译过程中自动调用它们来基于开发者的代码生成额外的 C++ 代码,最终编译出的应用程序也是基于这些生成的代码。
使用推荐:
对于大多数 Qt 用户来说,
qmake
仍然是最常用的构建工具,其次是CMake
。由于Qbs
的使用率较低,Qt 官方已经决定不再推荐或支持Qbs
。
填写类信息:定义自动生成的类及其父类。
上篇文章中,有详细讲过
选择语言和翻译文件:设定项目使用的语言。
配置 Qt 套件:选择编译器和库版本。
版本控制系统:可选集成 Git 等工具。
通过上述 8 个步骤,完成了项目的创建。项目创建完成之后,Qt Creator 会直接进入代码编辑模式,可以看到类似下图界面:
3. 认识 Qt Creator 界面
Qt Creator 的界面设计直观且功能丰富,包含以下几个关键区域:
- 左边栏:分为项目文件管理和打开文件列表窗口,可通过快捷键
Alt + 0
控制显示/隐藏。
在编辑模式下,左边竖排的两个窗口叫做 “边栏” 。
① 项目文件管理窗口,② 打开文件列表窗口。
在 QtCreator 菜单 “控件” ——> "Show Left Sidebar",或者使用快捷键:"Alt + 0" 可以控制边栏的显示和隐藏。
- 边栏里的窗口数码可以增加,边栏子窗口标题栏有一排小按钮,最右边的是关闭按钮,倒数第二个是增加分栏按钮,可以添加多个边栏子窗口。
- 边栏子窗口标题栏第一个控件是组合框,可以选择该子窗口的功能视图类型,目前可以选择 8 个视图类型:
视图类型 | 说明 |
项目 | 项目文件管理视图,可以选择项目里的文件进行编辑,包括 |
打开文档 | 显示当前已经打开的文件列表;文件名右边如果有 |
书签 | 在代码编辑器行号位置右击可看到“切换书签”选项,允许给代码行添加书签,便于快速跳转到特定位置。 |
文件系统 | 类似于系统的文件资源管理器,可以查看项目文件夹在磁盘中的实际文件列表。 |
类视图 | 查看项目中包含的类及其成员函数和成员变量。 |
Git Branches | 显示当前 Git 分支信息。 |
大纲 | 当前编辑文件的大纲列表,如命名空间、类名、成员函数、成员变量等,方便导航代码结构。 |
Tests | 测试相关视图,用于管理和运行测试用例。 |
类型层次 | 显示当前项目中类及其基类和派生类的层次关系。 |
Include Hierarchy | 包含视图,显示项目中 |
- 代码编辑区:提供导航、文件处理、符号跳转等功能,并支持多标签页浏览。
- UI 设计界面:允许通过拖拽控件到 UI 设计窗口来可视化地构建图形界面。
- 构建区:提供构建、运行、调试等操作按钮。
4. Qt Hello World 程序
1. 使用 “按钮” 实现
- 纯代码方式实现
-
- 按钮对象是我们自己
new
的,为了保证其他函数中能够访问到这个变量,就需要把按钮对象设定为Widget
类的成员变量。
- 按钮对象是我们自己
- 可视化操作实现
-
- A. 打开 UI 文件:双击
"widget.ui"
文件,Qt Creator 会调用 Qt Designer 打开 ui 文件。 - B. 拖拽控件:通过图形化的界面编辑器拖拽控件至 UI 界面窗口并修改内容。这里的按钮对象不需要我们自己
new
,new 对象这个操作已经是被 Qt 自动生成了,而且这个按钮对象已经作为ui
对象里的一个成员变量,也无需作为Widget
的成员。
- A. 打开 UI 文件:双击
-
- C. 构建并运行:完成上述步骤后,构建并运行项目,点击按钮查看效果。
2. 使用 “标签” 实现
纯代码方式实现
- 创建标签有两种方式:
-
QLabel* label = new QLabel(this);
// 更推荐在堆上创建的方式QLabel label;
// 在栈上创建
-
- 上述代码在 Qt 中在堆上创建,不会产生内存泄漏,
label
对象会在合适的时候被析构释放(虽然没有手动写delete
),是因为把这个对象挂到了对象树上。 - 实现效果:显示指定文本的标签。
- 上述代码在 Qt 中在堆上创建,不会产生内存泄漏,
可视化操作实现
- A. 打开 UI 文件:双击
"widget.ui"
文件。 - B. 修改标签内容:拖拽“标签”至 UI 设计界面中,并双击修改标签内容。此时的
ui
文件的 XML 中就会多出这一段代码,qmake 就会在编译项目时基于这个内容生成 C++ 代码构建界面。
- C. 实现效果
3. 使用 “编辑框” 实现
- 单行编辑框:
QLineEdit
- 多行编辑框:
QTextEdit
- 纯代码方式实现
-
- 完成编辑框的添加,并设置其属性和行为。
- 实现效果:用户可以输入文本的编辑框出现在界面上。
- 可视化操作实现
-
- A. 打开 UI 文件:双击
"widget.ui"
文件。 - B. 修改编辑框内容:拖拽“标签”至 UI 设计界面中,并双击修改标签内容。
- C. 构建并运行:完成上述步骤后,构建并运行项目,检查编辑框功能。
- A. 打开 UI 文件:双击
5. 项目文件解析
1. .pro 文件解析
- 工程新建好之后,在工程目录列表中有一个后缀为
.pro
的文件,.pro
文件是 qmake 自动生成的用于生产 Makefile 的配置文件。
.pro
文件的写法如下:
- 注释从
#
开始,到这一行结束。 QT += core gui
// 指定使用的 Qt 模块。
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
// 如果 Qt 版本大于 4,则需要增加 widgets 模块。TARGET = QtDemo
// 指定生成的应用程序名。TEMPLATE = app
// 指定模板类型。
🔊
- 包含源文件、头文件、资源文件、UI 设计文件等。
CONFIG += c++11
// 配置信息,如使用 C++11 的特性。
2. widget.h 文件解析(Widget 类的声明)
- Qt 的设定:使用 Qt 内置的类,包含的头文件的名字和类名是一致的。
- 引入
Q_OBJECT
宏以使用信号与槽机制。 QWidget *parent = nullptr
:创建的对象可以挂到对象树上。Ui::Widget *ui
:访问 UI 设计界面中的任意控件。
详解:类、对象、命名空间及类间关系
以下是对给定代码片段的详细解释,重点在于理解如何实现类和对象,各种类之间的关系引用逻辑,以及命名空间(namespace
)的作用。
1. 命名空间声明与作用
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
- 命名空间 (
namespace
) 是 C++ 中用于避免全局符号冲突的一种机制。通过将类、函数、变量等封装在命名空间中,可以防止不同库或模块中的同名实体发生冲突。 namespace Ui
声明了一个名为Ui
的命名空间,并在其中前向声明了Widget
类。这里的Widget
类是自动生成的 UI 类,它由 Qt Designer 根据.ui
文件生成,包含了所有界面元素的定义。namespace Ui { class Widget; }
提前声明了Ui::Widget
类,确保在实际定义之前可以使用这个类的指针。这使得头文件不会依赖于完整的Ui::Widget
类定义,从而减少了编译时间并提高了代码的可维护性。- 这样定义好 和后面的 我们定义的 Widget 类要进行区分
2. class Widget : public QWidget
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
};
class Widget : public QWidget
定义了一个继承自QWidget
的新类Widget
。这意味着Widget
类不仅拥有QWidget
的所有属性和方法,还可以添加自己的成员和功能。Q_OBJECT
宏是 Qt 的核心特性之一,用于启用信号与槽机制、运行时类型信息等功能。这个宏会在编译时展开为一些必要的元对象代码,使类能够使用 Qt 的高级特性。- 构造函数与析构函数:
-
Widget(QWidget *parent = nullptr);
是构造函数,允许指定一个父窗口部件(parent
)。如果未提供,则默认为nullptr
,即没有父窗口部件。~Widget();
是析构函数,负责清理资源,如删除动态分配的对象。
Ui::Widget *ui;
是一个指向Ui::Widget
类的指针,Ui::Widget
是由 Qt Designer 自动生成的类,Ui::Widget
包含了用户界面的所有控件和布局信息。通过这种方式,开发者可以在 C++ 代码中直接操作界面元素,而不需要手动编写大量的界面代码。
代码:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
// 开始 Qt 命名空间
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
// 结束 Qt 命名空间
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
// 构造函数,允许指定父窗口部件,默认为 nullptr
Widget(QWidget *parent = nullptr);
// 析构函数
~Widget();
private:
// 指向由 Qt Designer 自动生成的 Ui::Widget 类的指针
Ui::Widget *ui;
};
#endif // WIDGET_H
3. main.cpp 文件解析
Qt 中的
exec()
方法与 Linux 的exec
函数
- 在 Linux 学习中,我们了解过六个函数(本质上是一个),统称为
exec
(进程程序替换)。这些函数用于将可执行文件中的代码和数据替换到当前进程中。 - 然而,需要注意的是,Linux 中的
exec
和 Qt 中的exec()
方法没有任何关系。
Qt 系统初始化与事件循环解析
头文件与类名约定
- 头文件命名:Qt 系统提供的标准类名声明头文件没有
.h
后缀。 - 类与文件对应:每个 Qt 类对应一个头文件,类名即为头文件名。
QApplication 类的作用
- 应用程序类:
QApplication
是 Qt 应用程序的核心类,表示应用程序对象,通常命名为a
,并且有且仅有一个实例。 - 管理控制流与设置:
QApplication
负责管理图形用户界面应用程序的控制流和主要设置,是整个后台管理的核心组件。 - 事件处理:它包含主事件循环,负责来自窗口系统和其他资源的所有事件的处理和调度。此外,
QApplication
还处理应用程序的初始化和结束,并提供对话管理功能。 - 单例模式:对于任何一个使用 Qt 的图形用户界面应用程序,都恰好存在一个
QApplication
对象,不论该应用程序在同一时间是否拥有 0、1、2 或更多个窗口。
💡进入消息循环
Widget w; // 实例化窗口对象
a.exec(); // 进入消息循环
- 实例化窗口对象:
Widget w;
创建了一个窗口对象。 - 进入消息循环:
a.exec();
方法使得程序进入消息循环,等待对用户输入进行响应。此时,main()
函数把控制权转交给 Qt,由 Qt 完成事件处理工作。当应用程序退出时,exec()
的返回值会被传递回来。在exec()
中,Qt 接受并处理用户和系统的事件,并将它们传递给适当的窗口部件。
4. widget.cpp 文件解析
widget.cpp
文件是类Widget
的实现代码,所有窗体上的功能都在此文件中实现。
5. widget.ui 文件解析
widget.ui
是窗体界面定义文件,是一个 XML 文件,定义了窗口组件的属性设置、布局及信号与槽函数的关联等。- 设计界面由 Qt 自动解析并保存为 XML 格式,进一步由 qmake 调用工具生成 C++ 代码构造界面。
6. Qt 编程注意事项
1. Qt 中的命名规范
- 类名:首字母大写,单词和单词之间首字母大写。
- 函数名及变量名:首字母小写,单词和单词之间首字母大写。
- 命名偏好:Qt 偏好驼峰命名法。
在编程届,驼峰命名法的规则,使用程度比蛇形命名更广泛。
- 主要就是 C/C++/Python 偏好蛇形命名。eg. student_count
- Java/JS/Go...... 偏好驼峰命名。
2. Qt Creator 中的快捷键
- 注释:
Ctrl + /
- 运行:
Ctrl + R
- 编译:
Ctrl + B
- 字体缩放:
Ctrl + 鼠标滑轮
- 查找:
Ctrl + F
- 整行移动:
Ctrl + Shift + ⬆/⬇
- 帮助文档:
F1(+Fn)
- 自动对齐:
Ctrl + i
- 同名之间的 .h 和 .cpp 的切换:
F4(+Fn)
- 生成函数声明的对应定义:
Alt + Enter
3. 使用帮助文档
打开帮助文档有三种方式,实际编程中使用哪种都可以:
- 光标放到要查询的类名 / 方法名上,直接按 F1
- Qt Creator 左侧边栏中直接用鼠标单击 “帮助” 按钮
- 找到 Qt Creator 的安装路径,在 "bin" 文件夹下找到 assistant.exe,双击打开
新建项目时,在新建的项目中使用 Qt 中的 QpushButton
控件。可以通过在帮助手册的“索引”里面输入 QpushButton
来获取相关帮助信息。
7. 认识对象模型(对象树)
在 Qt 中创建很多对象的时候会提供一个 Parent 对象指针,下面来解释这个 parent 到底是干什么的。
- QObject 是以对象树的形式组织起来的
-
- 当创建一个 QObject 对象时,其构造函数接收一个 QObject 指针作为参数,即 parent(父对象指针)。我们创建的这个 QObject 对象会自动添加到其父对象的 children() 列表。当父对象析构时,所有子对象也会被一并析构。
- 这种机制在 GUI 程序设计中非常有用,例如,当删除按钮时,与之关联的快捷键对象也应该被删除。
QWidget 是能够在屏幕上显示的一切组件的父类
- QWidget 继承自 QObject,因此也继承了这种对象树关系。子组件会显示在父组件的坐标系统中,并受父组件边界剪裁的影响。
- 删除子对象时,它们会自动从父对象列表中移除,主窗口会相应调整显示。
解决了内存问题
在 Qt 中,对象树机制通过父子关系来组织和管理 QObject
及其子类的对象,这不仅简化了 GUI 应用程序的结构,还帮助开发者更有效地管理内存。以下是详细的解释:
1. 堆上创建的对象树
- 自动加入对象树:当一个
QObject
对象在堆上创建时(使用new
),Qt 会自动将其加入到对象树中。这个对象可以有一个父对象,它会自动添加到父对象的children()
列表。 - 未定义的销毁顺序:对象树中对象的顺序是没有定义的,这意味着销毁这些对象的顺序也是未定义的。然而,Qt 确保每个对象只会被删除一次,即使它们是其他多个对象的孩子。
- 安全的析构:如果一个
QObject
被delete
,且该对象有父对象,则它会被从父对象的children()
列表中移除;如果有孩子,则所有孩子也会被自动删除。这种机制避免了内存泄漏,并确保不会发生重复调用析构函数的情况。
2. 栈上创建的对象
- 相似的行为:即使对象是在栈上创建的,Qt 仍然保持相同的行为。局部对象的析构顺序遵循 C++ 标准,即按照创建顺序的相反过程进行。因此,在作用域结束时,子对象会先于父对象被析构。
- 正确的行为示例:
QWidget window;
QPushButton quit(&window);
在这段代码中,quit
的析构函数会在 window
析构之前被调用,从而避免了重复调用析构函数的问题。
3. ⭕错误的析构顺序示例
- 潜在问题:如果我们将父对象和子对象的声明顺序颠倒,可能会导致析构顺序的问题。
QPushButton quit;
QWidget window;
quit.setParent(&window); // 或者等价地,window 成为 quit 的 parent
在这段代码中,window
是最后一个创建的对象,所以它是第一个被析构的对象。在其析构过程中,它会尝试删除所有的子对象,包括 quit
。然而,由于 quit
也是一个局部变量,在超出作用域后,它的析构函数会被再次调用,导致双重析构,进而引发程序崩溃。
- 总的来说:为了防止上述问题的发生,最好在构造对象时就指定 parent 对象,并尽量在堆上创建对象。这样可以让 Qt 的对象树机制统一管理对象的生命周期,减少内存管理和析构顺序带来的麻烦。
内存泄漏问题
在 C++ 中,内存分配主要发生在两个区域:栈(stack)和堆(heap)。这两者有显著的区别,在选择使用哪一种时需要考虑性能、生命周期管理等因素。下面是栈与堆上开辟空间的主要区别:
栈(Stack)
- 分配速度:栈上的内存分配和释放非常快,因为它们是按照后进先出(LIFO, Last In First Out)的原则进行的,只需简单地移动栈指针。
- 生命周期:栈上的对象在其作用域结束时自动销毁。例如,当一个函数调用完成时,该函数中所有局部变量所占用的栈空间都会被释放。
- 大小限制:栈的空间通常是有限的,并且由操作系统预先设定。如果超出这个限制(例如递归过深或局部变量过多),可能会导致栈溢出错误。
- 分配方式:栈上的内存分配是静态的或自动的,即编译时确定或运行时根据作用域动态分配。
- 访问速度:由于栈的结构简单,CPU 缓存友好,因此栈上的数据访问速度较快。
- 代码示例:
void function() {
int a; // 变量 'a' 在栈上分配
// ...
}
堆(Heap)
- 分配速度:堆上的内存分配和释放相对较慢,因为需要更复杂的内存管理机制来跟踪哪些部分已经被使用,哪些是空闲的。
- 生命周期:堆上的对象不会自动销毁,必须显式地通过
delete
或free()
等操作手动释放,或者使用智能指针等工具进行管理。如果忘记释放,可能导致内存泄漏。 - 大小限制:理论上,堆的大小仅受物理内存和虚拟地址空间的限制,可以比栈大得多。
- 分配方式:堆上的内存分配是动态的,即在程序运行期间根据需求分配。
- 访问速度:由于堆的结构较为复杂,且可能分散在不同的内存区域,访问速度通常不如栈快。
- 代码示例:
int* p = new int(10); // 在堆上分配一个整数
delete p; // 手动释放堆上的内存
对比
- 如果需要快速分配和释放内存,并且知道对象的生命周期不会超过当前作用域,则应该优先考虑使用栈。
- 如果需要创建具有较长生命周期的对象,或者对象的大小只有在运行时才能确定,那么堆可能是更好的选择。不过要注意避免内存泄漏,并尽量利用现代C++特性如智能指针 (
std::unique_ptr
,std::shared_ptr
) 来简化内存管理。 - 对于较大的数据结构,比如大型数组或复杂的数据容器,通常也推荐使用堆,以避免栈溢出的风险。
示例
程序上线与内存泄露
1. 内存泄露问题
- 关注点:
-
- 内存泄露是需要融入到 DNA 中的事情。
- 内存泄露是一个非常严重的问题,不仅仅是内核泄露,包括文件描述符泄露等网络类问题,都是非常严重的。
2. 程序上线过程
- 问题发现:
-
- 这种问题不容易第一时间发现。
- 上线步骤:
-
- 在项目的时候,要上线一个程序。
-
-
- 把程序部署到生产环境上。
- 此时这个程序就可以被外面的用户访问到。
- 如果生产环境挂了,用户就访问不了了,这是非常严重的事故!
-
- 操作流程:
- 上线是一个挺麻烦的事情:
-
- 让测试进行测试,测试通过,预约运维同学排期。
- 还需要提电子流,让领导层层审批。
- 具体上线操作:
-
- 具体的上线操作也是非常繁琐的。当时我们有几十台服务器,就是要把程序部署到这几十台服务器上。
- 一般来说都是“灰度上线”,先上线一台机器,观察一下,验证一下,看看这个机器更新版本之后,有没有问题。
- 如果没问题,再上线后续的机器。如果有问题,那就赶紧调查问题,后续的机器上线就暂停。
-
- 可以先只上线一台机器,哪怕出现严重 bug,影响面积不大,造成的后果/损失比较有限。
3. 实际案例
- 上午:
-
- 我上线了一台机器——中午吃饭了,午休——下午的时候,已经观察几个小时了。
- 我检查了一下这个上线的机器,发现没啥问题,各个功能、各个指标都正常。
- 给运维同学说:
-
- 给运维同学说,可以上线后续机器了。
- 突发情况:
-
- 突然,第一台机器,狂报警!!!> 出事了!!
- 赶紧上到机器上,发现这个机器上的程序,出现了“文件资源泄露”问题——(打开了文件,没有 close)。
- 每个进程 => pcb => 文件描述符表。
- 每次打开一个文件,都需要在文件描述符表中申请一个条项。
- 文件描述符表长度是有上限的!!
- 要花多长时间才能达到上限?不知道,看代码泄露速度快不快了。
- 找到这个同学一起排查,他发现自己的代码中,打开了文件,没关闭。
- 如果泄露速度,再慢点——一直到凌晨,夜深人静,我们都在睡觉觉的时候。
- 突然搞出这么一手——所有的机器都可能会瘫痪,整个业务线就崩了!!
总结
-
- 内存泄露和文件描述符泄露是非常严重的问题,需要高度重视。
- 上线过程复杂且容易出现问题,需要谨慎处理。
- 需要定期检查和监控,避免因小失大。
注意:
- 对象树机制确保了统一管理对象生命周期。通过 new 创建的对象由对象树管理其生命周期,避免提前销毁导致界面控件缺失的问题。
- 栈上创建的对象可能在构造函数结束后就被销毁,导致程序无法正常显示控件。
代码示例
- 创建新工程、添加新文件、修改头文件和源文件,最终实现一个简单的窗口应用程序。
选择左上角 “文件” ——> “新建文件或项目”:
弹出如下界面:
此时手动创建类的头文件以及源文件会自动添加到目标工程中:
修改头文件:
编写源文件:
编译并运行:
当关闭弹出的对话框时,就会自动调用标签的析构函数:
可以看到,这里的结果出现了乱码(编码方式不匹配)。
关于汉字字符集与编码方式
在计算机中,一个汉字占几个字节的问题没有固定的答案,这取决于当前使用的中文编码(字符集)。不同的字符集有不同的编码规则,因此同一个汉字在不同字符集中可能占用不同数量的字节。
字符编码基础
- 二进制存储:计算机内部存储的是二进制数字。
- ASCII 码表:用于表示英文字母和其他常见符号,每个字符用一个字节表示。由于英文字母数量有限,一个字节足够表示所有 ASCII 字符。
- 汉字字符集:日常常用的汉字大约有 4000 多个,包括生僻字在内的总数约为 60,000 左右。因为字符集种类繁多,同一个汉字在不同字符集中可能有不同的编码。
主要的汉字字符集
目前,表示汉字的主要字符集有两种:
- GBK (简体中文):
-
- 使用 2 个字节 表示一个汉字。
- 是中国大陆广泛使用的字符集,也是 Windows 简体中文版默认的字符集。
- UTF-8:
-
- 是一种 变长编码,表示一个符号可以使用 2 到 4 个字节,但通常一个汉字是 3 个字节。
- 是 Linux 和许多现代操作系统及网络协议中默认的字符集。
Qt Creator 中的字符串处理
- 终端显示问题:Qt Creator 内置的终端不是以 UTF-8 方式来显示字符串,并且似乎不能设置字符编码。
- QString 类:Qt 提供了
QString
类,能够自动处理多种编码方式,确保正确显示和处理中文字符。 - qDebug() 工具:Qt 还提供了专门用来打印日志的工具
qDebug()
,它也能很好地自动处理编码问题。qDebug()
宏封装了QDebug
对象,可以直接当作cout
来使用。
Qt 对象树与内存管理
- 对象树机制:Qt 的对象树确保先释放子节点的内存,后释放父节点的内存。
- 析构函数调用顺序:需要注意的是,析构函数的调用顺序不一定遵守上述要求,可能会看到子节点的析构执行顺序反而在父节点之后。
- 提示:调用析构函数和实际释放内存并不是同一件事情,开发者应关注这两者的区别以避免潜在问题。
8.Qt 窗口坐标体系
1. 坐标系
- 数学上的坐标系:
-
- 右手坐标系
- 原点 (0, 0) 在左上角
- X轴向右,Y轴向上
- 计算机中的坐标系:
-
- 左手坐标系
- 原点 (0, 0) 在左上角
- X轴向右,Y轴向下
2. Qt 中的控件布局
- 父元素与子元素:
-
QPushButton
的父元素/父控件/父窗口是QWidget
- 如果
QWidget
没有父元素(NULL),则相当于父元素就是整个显示器桌面。
- 默认位置:
-
- 默认情况下,按钮的位置在 (0, 0),即左上角。
- 可以使用
move
函数来设置按钮的位置。
示例代码
// 移动按钮到指定位置
button->move(200, 300);
图示:
- 显示了
QWidget
和QPushButton
的关系。 - 系统自动生成的范围不在
QWidget
范围之内。
3. 坐标单位
像素
- 坐标背后的单位是像素。
- 显示器本质上是由一大堆可以发光的小亮点/小灯泡构成的。
- 如果使用手机拍屏幕,把对焦放大到最大,有可能看到像素。
4. 显示器分辨率
- 水平方向:
-
- 有 1920 个像素(亮点)
- 分辨率为 1080P
- 垂直方向:
-
- 有 1080 个像素(亮点)
- 2K 显示器:
-
- 分辨率通常为 2560 x 1440 像素。
我们可以在设置中,查看我们的电脑
- 显示器亮点数量越多,画面就越亮。
- 对应的显示器也就越贵。
小结
1. 认识 QLabel 类
- 功能:能够在界面上显示字符串。
- 设置文本:通过
setText
方法来设置,参数类型为QString
(Qt 中对 C++ 容器类进行了重新封装,历史原因)。
2. 内存泄露 / 文件资源泄露
- 问题:内存泄露和文件资源泄露是严重的问题。
3. 对象树
- 概念:Qt 中通过对象树来统一释放界面的控件对象。
- 推荐做法:
-
- 推荐使用
new
的方式在堆上创建对象,并通过对象树统一释放对象。 - 在构造函数中指定父对象,此时对象会挂到对象树上。
- 如果对象没有挂到对象树上,必须手动释放。
- 推荐使用
4. 继承自 Qt 内置的类
- 扩展功能:通过继承自 Qt 内置的类,可以达到对现有控件进行功能扩展的效果。
- 示例:
-
- 创建类
MyLabel
继承自QLabel
。 - 重写析构函数,在析构函数中加上日志,直观地观察对象释放的过程。
- 可以重写控件中的任何功能,不仅仅是析构函数,达到功能扩展的目的。
- 创建类
5. 乱码问题和字符集
- 涉及范围:MySQL(很多地方都涉及到)。
6. 如何在 Qt 中打印日志
- 调试信息:
-
- 使用
cout
虽然可以,但并不是上策(字符编码处理不好,不方便统一关闭)。 - Qt 中推荐使用
qDebug()
完成日志的打印。
- 使用
- 调试工具局限性:
-
- 调试器很多时候是有局限性的,无法使用。
- 假设当前 bug 是一个概率性的 bug,出现的概率是 1% 甚至更小。
- 使用日志可以很好地解决这种问题。
注意:
- 面向对象“继承”:本质上是对现有代码进行的“扩展”。
- 调试工具局限性:无论是哪种方式,本质上都是观察程序执行的中间过程和中间结果。