目录
1. Qt Creator 概览
2. 使用 Qt Creator 新建项目
3. 认识 Qt Creator 界面
4. Qt Hello World 程序
1. 使用 “按钮” 实现
2. 使用 “标签” 实现
3. 使用 “编辑框” 实现
5. 项目文件解析
1. 命名空间声明与作用
2. class Widget : public QWidget
6. Qt 编程注意事项
1. Qt 中的命名规范
2. Qt Creator 中的快捷键
3. 使用帮助文档
7. 认识对象模型(对象树)
内存泄漏问题
栈(Stack)
堆(Heap)
1. 内存泄露问题
2. 程序上线过程
3. 实际案例
代码示例
关于汉字字符集与编码方式
8.Qt 窗口坐标体系
1. 坐标系
2. Qt 中的控件布局
3. 坐标单位
4. 显示器分辨率
小结
1. 认识 QLabel 类
2. 内存泄露 / 文件资源泄露
3. 对象树
4. 继承自 Qt 内置的类
5. 乱码问题和字符集
6. 如何在 Qt 中打印日志
[Qt] Qt介绍 | 搭建SDK
根据上面一篇文章 已经成功创建出了 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% 甚至更小。
- 使用日志可以很好地解决这种问题。
注意:
- 面向对象“继承”:本质上是对现有代码进行的“扩展”。
- 调试工具局限性:无论是哪种方式,本质上都是观察程序执行的中间过程和中间结果。