QT入门看这一篇就够了,以项目的方式学QT开发——超详细讲解(12000多字详细讲解,涵盖qt大量知识)逐步更新!

以项目的方式学QT开发

P1 QT介绍

1.1QT简介

1.2QT安装

1.2.1Windows QT安装

1.2.2QT Creator 使用基本介绍

P2 C++基础

2.1命名空间

2.1.1命名空间作用

2.1.2自定义命名空间

2.2从C语言快速入门

2.2.1输入输出

2.2.2基本变量类型

2.2.3流程控制

2.2.4函数

2.2.5内联函数

2.2.6Lambda 表达式

2.2.7数组

2.2.8练习

2.2.9指针

2.2.9 字符串string类型

2.3类

2.3.1类的初探

2.3.2结构体引入类

2.3.2.1回忆结构体

2.3.2.2新建C++工程来使用结构体

2.3.2.3真正的成员函数

2.3.4 QT中经常出现的用法

2.4权限初识

2.4.1基本介绍

2.4.2目前能概况的结论

2.4.3提问和回答

2.5引用

2.6.1和指针的区别

2.6.2把引用作为参数

2.6.3把引用作为返回值

2.6重载

2.5.1函数重载

2.5.2运算符重载

2.7构造函数

2.7.1什么是构造函数

2.7.2带参数构造函数

2.7.3拷贝构造函数

2.7.3.1基本概念及发生条件

2.7.3.2浅拷贝

2.7.3.3深拷贝

2.7.3.4规则三则

2.7.3.5避免不必要的拷贝

2.7.3.6拷贝构造函数的隐式调用

2.7.3.7禁用拷贝构造函数

2.7.3.8拷贝构造函数总结

2.7.4使用初始化列表

2.7.5this关键字

2.7.6new关键字

2.8析构函数

2.8.1什么是析构函数

2.9静态成员

2.9.1静态成员的定义

2.9.2静态成员变量的作用

2.10继承

2.10.1继承基本概念

2.10.2权限对继承的影响

2.10.3基类构造函数

2.10.4虚函数

virtual 关键字

override 关键字

2.10.5多重继承

2.10.6虚继承

菱形继承问题示例

使用虚继承解决菱形继承问题

2.11多态

2.11.1如何实现多态

2.11.2抽象类

2.11.3纯虚函数-接口

2.12友元

2.12.1什么是友元

2.12.2友元函数

2.12.3友元类

2.12.4友元成员函数

2.13模板

2.13.1类模板

2.13.2函数模板

2.13.3模板特化

2.14标准模板库STL

2.14.1容器

2.14.2vector

2.14.3list

2.14.4set

2.14.5map

2.15异常

2.15.1异常基本

2.15.2自定义异常

P3 记事本项目

3.1项目概述

3.1.1功能介绍

3.1.2界面预览

3.2.3 工程概述

3.2UI设计师工具

3.2.1按键 QPushButton

3.2.2水平布局 QHBoxLayout

3.2.3文本编辑器 TextEdit

3.2.4垂直布局 QVBoxLayout

3.2.5主窗体元素设计

3.3按键响应-初识信号与槽

3.3.1信号与槽基本介绍

3.3.2按键QPushButton设置信号与槽

3.3.3自定义信号与槽

3.3文件操作类 QFile

3.3.3 QTextStream

3.4文件选择对话框 QFileDialog

3.4.1QFileDialog开发流程

3.4.2QFileDialog 打开开发案例

3.4.3QFileDialog 保存开发案例

3.6实现文件打开功能

3.6.1开发流程

3.6.2代码实现

3.6.3打开功能优化

3.6.4QComboBox

3.6.5记事本支持字符编码

3.6.6添加行列显示

3.6.7添加文件打开提示

3.6.8设置当前行高亮

3.6.8.1QList

3.8.2 ExtraSelection 简介

3.7文件保存功能优化

3.7.1开发流程

3.8关闭优化

3.8.1消息对话框 QMessageBox

3.7.3 代码实现

3.9实现快捷键功能

3.9.1快捷键开发基础

3.9.2上官记事本添加快捷键

3.10实现字体放大缩小功能

3.10.1滚动调节字体大小的流程

3.10.2本节笔记失误

3.10.3检测Ctrl键被按下

3.10.4记事本添加字体放大缩小

3.10.5事件

事件处理过程重写事件案例

事件方式实现字体放大缩小

事件过滤器

3.10.6鼠标滚轮和字体大小

3.12 记事本项目总结

P4 串口调试助手项目

4.1项目概述

4.2串口通信核心代码开发

P5 网络调试助手

5.1TCP网络调试助手

5.1.1项目概述

5.1.2开发流程

5.1.3QTtcp服务器的关键流程

5.1.4QTtcp客户端的关键流程

5.1.2 TCP协议

5.1.4 Socket

5.2UI设计

5.3网络通信核心代码

5.3.1创建TCP服务端的核心代码

5.3.2创建TCP客户端的核心代码

5.4TCP服务端项目开发

5.5TCP客户端项目开发

5.6项目总结

P6 自定义控件

6.1QPaintEvent绘图事件

6.2QPainter画家

6.2.1概述

6.2.2渐变色

6.2.2.1线性渐变

6.2.2.2径向渐变

6.2.2.3圆锥形渐变

6.3坐标转移

6.4画雷达案例

6.5仪表表盘

6.5.1初步完成

6.5.2稍微美化

6.5.3优化数字显示后代码整理

6.5.4画一个指针

6.5.5内环

6.5.6完结

6.6汽车表盘参考样式

P7 天气预报项目

7.1项目概述

7.2stylesheet样式

7.3窗体无状态栏-关闭

7.4窗口跟随移动

7.5天气预报数据接口

7.6软件开发网络通信架构

7.6.1BS架构/CS架构

7.6.2HTTP基本概念

7.7QT的HTTP编程

7.8JSON数据

7.8.1概述

7.8.2QT生成JSON数据

7.8.3QT解析JSON数据

P8 Ubuntu搭建QT开发环境

8.1安装Ubutnu22

8.1.1下载和安装Vmware

8.1.2下载和安装Ubuntu22

8.1.3常用功能配置

8.2安装Ubuntu环境下的QT

8.2.1下载安装UbuntuQT

8.2.2Ubuntu中文支持

P9 加餐课

P1 QT介绍

1.1 QT简介

Qt 是一个跨平台的应用程序和用户界面框架,用于开发图形用户界面(GUI)应用程序以及命令行工具。它最初由挪威的 Trolltech (奇趣科技)公司开发,现在由 Qt Company 维护,2020年12月8日发布QT6。Qt 使用 C++ 语言编写,支持多种编程语言通过绑定进行使用。

对于许多开发者和小型企业来说,Qt 的开源版提供了一个强大且灵活的开发框架,而对于需要额外支持和专有功能的大型企业或具有特定需求的项目,商业版则提供了所需的服务和资源。

 Qt 商业版

商业版提供专有许可,需要购买许可证来使用。这适用于希望在不共享源代码的情况下开发商业软件的公司和开发人员

 QT免费开源版

开源版根据 GNU Lesser General Public License (LGPL) 和 GNU General Public License (GPL) 发布。这意味着用户可以免费使用 Qt,但必须遵守特定的开源许可条款

 QT主要历史版本

版本 发布年份 关键特性

Qt 1.x 1996 初始发布,专注于 X11 平台

Qt 2.x 1999 引入了对 Microsoft Windows 的支持

Qt 3.x 2001 添加了许多新功能,包括网络和 XML 支持

Qt 4.x 2005 重大改进,增强了跨平台支持和图形视图框架

Qt 5.x 2012 专注于现代硬件的性能,引入了 QML 和 Qt Quick 用于开发流畅的动画和触摸界面

Qt 6.x 2020 进一步增强了性能和功能,针对未来的软件开发趋势进行了优化,包括对 3D 图形的支持

学习者学习QT5和QT6都是可以的,无论选择哪个版本,Qt的基本概念和理念在各个版本之间是相通的,因此你可以相对轻松地转换到其他版本。本次我们基于QT5学习

 成熟和稳定性

Qt 5已经存在了一段时间,经过了多个版本的迭代和改进。它在很多项目中被广泛使用,证明了其成熟性和稳定性。这对于在大型项目或生产环境中使用Qt的开发者来说是一个优势。

 丰富的文档和社区支持

Qt 5有大量的文档和社区支持。你可以轻松找到各种教程、示例和解决方案,这对于初学者来说是非常宝贵的

 广泛的应用领域

Qt 5有大量的文档和社区支持。你可以轻松找到各种教程、示例和解决方案,这对于初学者来说是非常宝贵的。

1.2 QT安装

1.2.1Windows QT安装

  下载windowsQT安装包

本教程使用的QT版本是:https://download.qt.io/archive/qt/5.12/5.12.9/ 本教程的安装包放在百度网盘供大家获取。

 QT安装

如果没有梯子,大家登录QT官网可能会失败,这里可以不需要QT账号,直接离线安装,所以要断开网络。

选择windows底下的编译工具,QT源代码,QT的绘图模块及QT的虚拟键盘

安装完成后打开

1.2.2QT Creator 使用基本介绍

 课程录屏展示-创建并运行第一个QT项目  

课程录屏展示-创建并运行第一个C++项目  课程录屏展示-QT Creator的界面介绍

  QT Creator常用的快捷键介绍

功能 快捷键 中文说明

撤销 CTRL + Z 撤销最近的操作

重做 CTRL + Y 重做最近的撤销操作

复制 CTRL + C 复制选中内容

粘贴 CTRL + V 粘贴内容

复制行向下 CTRL + ALT + DOWN 将当前行复制到下一行

复制行向上 CTRL + ALT + UP 将当前行复制到上一行

运行 CTRL + R 运行当前项目

返回编辑模式 ESCAPE 返回到编辑状态

切换当前文件 CTRL + TAB 在打开的文件间切换

切换声明和定义 F2 在代码的声明与定义间切换

切换头文件和源文件 F4 在头文件和源文件间切换

开始调试 F5 启动调试

停止调试 SHIFT + F5 停止当前的调试

构建当前项目 CTRL + B 构建当前打开的项目

功能 快捷键 中文说明

构建所有项目 CTRL + SHIFT + B 构建所有项目

新建文件或项目 CTRL + N 创建新文件或项目

打开文件或项目 CTRL + O 打开现有文件或项目

保存当前文件 CTRL + S 保存当前编辑的文件

保存所有文件 CTRL + SHIFT + S 保存所有打开的文件

关闭当前文件 CTRL + W 关闭当前文件

关闭所有文件 CTRL + SHIFT + W 关闭所有打开的文件

退出QT Creator CTRL + Q 退出QT Creator

位置后退 ALT+Left 光标位置回退

P2 C++基础

C和C++之间的关系是紧密且复杂的。C++最初是作为C语言的一个扩展开发的,目的是在不放弃C的强大功能和效率的同时,增加对象导向编程、泛型编程和其他一些特性。下面是C和C++之间主要的关系和区别:

1.兼容性:C++在很大程度上是与C兼容的。这意味着许多C程序可以在C++编译器中编译并运行,尽管可能需要一些小的修改。

2.面向对象编程(OOP):C++引入了面向对象编程。它允许使用类和对象,而C是一个过程性语言,不支持这些概念,或者说支持的不好,麻烦。

3.模板:C++支持模板,这是一种允许程序员编写与数据类型无关的代码的功能。C没有这个功能。

4.标准库:C++有一个更丰富的标准库,包括STL(标准模板库),这为数据结构和算法提供了广泛的支持。而C的标准库相对较小。

5.类型检查:C++比C提供更严格的类型检查。这意味着某些在C中可行但可能导致错误的代码在

C++中可能无法编译。

6.异常处理:C++支持异常处理,这是一种处理程序运行时错误的机制。C没有内置的异常处理机制。

7.命名空间:C++引入了命名空间,这有助于防止名称冲突。C没有这个概念。

2.1 命名空间

2.1.1命名空间作用

创建自己的命名空间是 C++ 中组织代码的一种好方法,特别是在开发大型项目或库时。命名空间可以帮助你避免名称冲突,并且清晰地组织代码。

是 C++ 标准库的命名空间。它是一个定义在 C++ 标准库中的所有类、函数和变量的命名空间。我们新建一个QTCreator的C++工程,默认生成的代码

在 C++ 中,如果你想使用标准库中的任何类、函数或对象,你通常有两种选择:

1.使用

素。

前缀:这是最常见的方式,它明确指定了你正在使用的是位于

命名空间中的元

2.使用 using namespace std; :这允许你在不显式指定所有元素。

的情况下使用

命名空间中的

std包含的内容

命名空间包含了许多类、函数和对象,例如:

 输入输出库(如 std::cout , std::cin , std::endl )  容器类(如 std::vector , std::map , std::set )

  字符串类( std::string )

 异常类( std::exception 和相关子类)  算法(如 std::sort , std::find )

 实用工具(如 std::pair , std::tuple )  其他许多功能

使用建议

  对于小型代码或示例代码,使用

通常是安全的。

对于大型项目或库,建议显式地使用和可维护性。

前缀,以避免潜在的名称冲突,并提高代码的可读性

命名空间是 C++ 编程的基础部分,理解和正确使用它对于编写健壮和高效的 C++ 代码至关重要。

2.1.2自定义命名空间

定义命名空间

假设我们要创建一个命名空间来包含与圆形相关的功能。我们可以命名这个命名空间为 Cir :

在这个头文件中,我们定义了一个名为圆周率常量 PI 。

使用命名空间

的命名空间,其中包含了计算圆的面积和周长的函数,以及

在另一个文件中,我们可以使用这个命名空间中定义的函数和常量:

中,我们首先包含了定义

命名空间的头文件。然后,我们可以使用

前缀来访

问该命名空间中的函数和常量。

通过使用自定义命名空间,你可以有效地组织你的代码,并减少不同库之间的名称冲突。这在大型项目和团队协作中尤其重要。

2.2 从C语言快速入门

2.2.1输入输出

C++ 中的输入和输出(I/O)主要是通过标准库中的输入输出流来实现的。最常用的是 iostream 库,它提供了用于输入和输出的基本流类,包括 cin 、 cout 、 cerr 和 clog 。

标准输出流 ( cout )

代表标准输出流,通常用于向屏幕输出数据。

使用操作符 << (插入操作符)向 发送数据。

 例如, std::cout << "Hello, world!" << std::endl; 会在屏幕上打印 "Hello, world!" 并换行。

标准输入流 ( cin )

代表标准输入流,用于从键盘接收数据。

使用操作符 >> (提取操作符)从 提取数据。

  例如, int x; std::cin >> x; 会从用户那里读取一个整数并存储在变量 中。

标准错误流 ( cerr ) 和标准日志流 ( clog )

用于输出错误消息。与 不同, cerr 不是缓冲的,这意味着它会立即输出。

类似于  cerr ,但它是缓冲的。它通常用于记录错误和日志信息。

示例代码

下面是一个展示如何使用这些基本流的简单示例:

2.2.2基本变量类型

C++ 基本数据类型整理成表格。以下是一个表格,展示了不同的基本数据类型及其一般用途和大小范围:

和C语言类似。

数据类型 描述 大小(通常情况下) 用途

int 整型 至少 16 位 存储整数

short int 短整型 至少 16 位 存储较小的整数

long int 长整型 至少 32 位 存储较大的整数

long long int 更长的整型 至少 64 位 存储非常大的整数

unsigned int 无符号整型 同 int 存储非负整数

float 单精度浮点类型 32 位 存储小数,精度约为 6-7 位小数

double 双精度浮点类型 64 位 存储小数,精度约为 15-16 位小数

long double 扩展精度浮点类型 80 位或更多 存储小数,提供比 double 更高的精度

char 字符型 8 位 存储单个字符或小整数

unsigned char 无符号字符型 8 位 存储较大的字符或作为字节使用

signed char 有符号字符型 8 位 明确作为带符号的字符或小整数使用

bool 布尔型 通常为 8 位 存储真值 true 或假值 false C语言 C99以上支持

wchar_t 宽字符类型 通过为16位或

32位 存储中文或者unicode

宽字符的用法

在 C++ 中, <climits> (或在 C 中是  <limits.h> )是一个标准头文件,提供了关于整型限制的信    息。这个头文件中定义了各种整型数据类型的属性,如最大值、最小值等。使用这些信息可以帮助你了解在特定编译器和平台上各种数据类型的大小和范围。

如何使用

要使用 中定义的常量,你首先需要包含这个头文件:

然后,你可以使用它提供的各种常量,例如:

INT_MAX : int 类型的最大值。

INT_MIN : int 类型的最小值。

UINT_MAX : unsigned int 类型的最大值。

LONG_MAX : long int 类型的最大值。

LONG_MIN : long int 类型的最小值。

LLONG_MAX : long long int 类型的最大值。

LLONG_MIN : long long int 类型的最小值。

示例代码

下面是一个简单的示例,展示了如何使用

中的值:

这个程序会输出 int 、 unsigned int 和注意事项

类型的最大值和最小值。

提供的是编译时确定的常量,这意味着这些值在编译时就已经固定,根据编译器和平台的不同而可能有所不同。

使用这些限制值可以帮助你编写更可移植和安全的代码,特别是在处理可能超出数据类型范围的操作时。

2.2.3流程控制

在 C++ 中,流程控制语句用于根据不同条件控制程序的执行流程。它们是编程中的基本构建块,允许程序根据条件执行不同的代码段,重复执行某些操作,或者根据特定情况跳过某些代码段。下面是 C++ 中最常见的流程控制语句:

条件语句

1.语句:基于条件的基本控制结构。如果条件为真,则执行代码块。

语句:与

语句配合使用,当

的条件为假时执行。

语句:用于测试多个条件。

语句:基于变量的值选择执行不同代码块的方法。

循环语句

for 循环:当知道循环应该执行的次数时使用。

循环:当条件为真时,重复执行代码块。

循环:至少执行一次循环体,然后再检查条件。

跳转语句

1.

语句:用于立即跳出最近的

或循环( for 、 while 、 do-while )。

2.语句:跳过循环的当前迭代,并继续下一次迭代。

3. 语句:直接跳转到程序中的另一个点。使用和维护。

通常不推荐,因为它可以使代码难以阅读

流程控制语句是编程中非常重要的部分,允许开发者编写可以根据不同情况改变行为的灵活且强大的程序。在使用这些语句时,应该确保逻辑清晰,以便代码易于理解和维护。

2.2.4函数

在 C++ 中,函数是一段执行特定任务的代码块,它可以带有参数,并且可能返回一个值。函数的使用使得代码更加模块化和可重用,有助于降低代码的复杂性,并提高可维护性。

函数的基本结构

C++ 函数的基本结构包括返回类型、函数名、参数列表和函数体:

示例

以下是一个 C++ 函数的简单示例:

在这个示例中, add 函数接收两个整数参数,并返回它们的和。函数的组成部分

1.返回类型:指定函数返回的数据类型。如果函数不返回任何值,则使用  void 。

2.函数名:函数的标识符,用于调用函数。

3.参数列表:括号内的变量列表,用于从函数的调用者那里接收值。如果函数不接收任何参数,则此列表为空。

4.函数体:大括号 内的一系列语句,定义了函数的执行操作。

2.2.5内联函数

内联函数(Inline Function)是C++中一种特殊的函数,其定义直接在每个调用点展开。这意味着编译器会尝试将函数调用替换为函数本身的代码,这样可以减少函数调用的开销,尤其是在小型函数中。

特点

1.减少函数调用开销:内联函数通常用于优化小型、频繁调用的函数,因为它避免了函数调用的常规开销(如参数传递、栈操作等)。

2.编译器决策:即使函数被声明为内联,编译器也可能决定不进行内联,特别是对于复杂或递归函数。

3.适用于小型函数:通常只有简单的、执行时间短的函数适合做内联。

4.定义在每个使用点:内联函数的定义(而非仅仅是声明)必须对每个使用它的文件都可见,通常意味着将内联函数定义在头文件中。

使用方法

通过在函数声明前添加关键字 来指示编译器该函数适合内联:

示例

在这个示例中,函数的代码。

被定义为内联函数。当它被调用时,编译器可能会将函数调用替换为函数体内

注意事项

  过度使用的风险:不应滥用内联函数,因为这可能会增加最终程序的大小(代码膨胀)。对于大型函数或递归函数,内联可能导致性能下降。

  编译器的决定:最终是否将函数内联是由编译器决定的,即使函数被标记为 inline 。

  适用场景:最适合内联的是小型函数和在性能要求高的代码中频繁调用的函数。

内联函数是一种用于优化程序性能的工具,但需要合理使用,以确保代码的可维护性和性能的平衡。

2.2.6Lambda 表达式

Lambda 表达式是 C++11 引入的一种匿名函数的方式,它允许你在需要函数的地方内联地定义函数,而无需单独命名函数

Lambda 表达式的基本语法如下:

Lambda 表达式由以下部分组成:

 捕获列表(Capture clause):用于捕获外部变量,在 Lambda 表达式中可以访问这些变量。捕获列表可以为空,也可以包含变量列表 [var1, var2, ...] 。

 参数列表(Parameters):与普通函数的参数列表类似,可以为空或包含参数列表

  返回类型(Return type):Lambda 表达式可以自动推断返回类型auto,也可以显式指定返回类型 -> return_type 。如果函数体只有一条返回语句,可以省略返回类型。

  函数体(Body):Lambda 表达式的函数体,包含需要执行的代码。

Lambda 表达式最简单的案例是在需要一个小型函数或临时函数时直接使用它。以下是一个非常简单的例子,其中使用 Lambda 表达式来定义一个加法操作,并立即使用它来计算两个数的和。

示例:使用 Lambda 表达式进行加法

在这个例子中:

 我们定义了一个名为

的 Lambda 表达式,它接受两个整数参数,并返回它们的和。

  然后,我们使用这个 Lambda 表达式来计算两个数字(10 和 20)的和,并将结果存储在变量中。

 最后,我们打印出这个和。

这个例子展示了 Lambda 表达式的基本用法:作为一种简洁而快速的方式来定义小型函数。

我们可以写一个例子,其中使用一个函数来找出两个数中的较大数,这个函数将接受一个 lambda 函数作为回调来比较这两个数。Lambda 函数将直接在函数调用时定义,完全是匿名的。

先回忆以下回调函数

示例:使用匿名 Lambda 函数来返回两个数中的较大数

在这个例子中:

函数接受两个整数 和 b ,以及一个比较函数 compare 。这个比较函数是一个指向函数

的指针,它接受两个整数并返回一个布尔值。

在 函数中,我们调用  getMax ,并直接在调用点定义了一个匿名的 lambda 函数。这个

lambda  函数接受两个整数并返回一个表示第一个整数是否大于第二个整数的布尔值。

这个 lambda 函数在返回较大的数。

中被用作比较两个数的逻辑。根据 lambda 函数的返回值, getMax

这个例子展示了如何直接在函数调用中使用匿名 lambda 函数,使代码更加简洁和直接。这种方法在需要临时函数逻辑的场合非常有用,尤其是在比较、条件检查或小型回调中。

在 Lambda 表达式中,参数捕获是指 Lambda 表达式从其定义的上下文中捕获变量的能力。这使得

Lambda  可以使用并操作在其外部定义的变量。捕获可以按值(拷贝)或按引用进行。

让我们通过一个简单的示例来展示带参数捕获的 Lambda 表达式。

示例:使用带参数捕获的 Lambda 表达式

在这个例子中:

  第一个 Lambda 表达式

按值捕获了

和 y (即它们的副本)。这意味着

内的 和

是在 Lambda 定义时的值的拷贝。

第二个 Lambda 表达式 使用

第三个 Lambda 表达式 使用

捕获列表,这表示它按值捕获所有外部变量。

捕获列表,这表示它按引用捕获所有外部变量。

因此,它可以修改

的原始值。

这个示例展示了如何使用不同类型的捕获列表(按值和按引用)来控制 Lambda 表达式对外部变量的访问和修改。按值捕获是安全的,但不允许修改原始变量,而按引用捕获允许修改原始变量,但需要注意引用的有效性和生命周期问题。

以下是一个表格,概述了 Lambda 函数和内联函数在 C++ 中的相似之处和区别:

特性 Lambda 函数 内联函数

定义 一种匿名函数,通常用于定义在需要它们的地方。 一种常规函数,通过 inline 关键字定义。

用途 提供一种快捷方式来定义临时的、小型的函数。 用于优化小型函数,减少函数调用的开销。

语法 使用 [capture](params) { body }

的形式定义。 使用常规函数定义语法,但在前面加上

inline 关键字。

生命周期 在定义它们的作用域内有效。 在整个程序执行期间有效。

捕获外部变量 可以捕获外部作用域中的变量(按值或按引用)。 不能直接捕获外部变量,只能通过参数传递。

调用方式 作为函数对象,可直接调用。 像普通函数一样调用。

特性 Lambda 函数 内联函数

优化 可以被编译器自动内联化,但这取决于编译器优化策略。 明确请求编译器尝试内联,但实际内联化也取决于编译器。

可见性 通常只在定义它们的局部作用域内可见。 可以在定义它的任何作用域内可见。

使用场景 适合于一次性使用的场景,如作为回调、在算法中使用等。 适合于频繁调用的小型函数。

请注意,虽然 Lambda 函数和内联函数在某些方面有相似之处,如它们都可以被编译器优化以减少调用开销,但它们在设计和用途上有明显的不同。Lambda 函数的核心优势在于它们的匿名性和对外部变量的捕获能力,而内联函数则主要关注于提高小型函数的性能。

2.2.7数组

在 C++ 中,数组是一种存储固定大小的相同类型元素的序列。数组的所有元素都存储在连续的内存位置上。这种数据结构非常适合于存储具有固定数量和相同数据类型的元素集合。

声明数组

声明数组的基本语法如下:

例如,声明一个类型为 的数组,包含 10 个元素:

初始化数组

在声明数组时,您可以同时初始化数组:

如果您在初始化数组时没有指定所有元素的值,未初始化的元素将被自动设置为该数据类型的默认值

(对于基本数据类型通常是 0):

访问数组元素

您可以通过指定索引来访问数组中的元素。数组索引是从 0 开始的,所以数组的第一个元素是

[0] ,第二个元素是 数组名[1] ,依此类推:

示例

以下是使用数组的简单示例:

注意事项

  数组的大小必须在编译时已知,且不能更改。

  数组索引越界是常见的错误,可能会导致未定义的行为。

  对于更复杂的用例,您可能需要使用 C++ 的标准模板库(STL)中的容器,如 std::vector ,它提供了可以动态改变大小的数组。

  数组的元素存储在连续的内存位置上,这使得访问数组元素非常快。

2.2.8练习

 计算器支持加减乘除

数组找最大值

2.2.9指针

C++完全兼容C语言指针,多出一个this指针,在面向对象中再讲解。

2.2.9 字符串string类型

C语言中对字符串的表示通常用指针,新手会面临内存泄漏或者段错误等众多问题。

在 C++ 中, string 类是标准库的一部分,用于表示和操作字符串。它是对传统的 C 风格字符串(以空

字符 '\0' 结尾的字符数组)的一个更安全、更方便的封装。 string 类是在 头文件中定义

的,并且位于 命名空间中。

类提供了许多有用的功能和特性,包括:

1.动态大小:与 C 风格的字符串不同, string 对象可以动态改变大小,这意味着你可以在运行时添加或移除字符,而不需要担心分配和释放内存。

2.安全性:由于 管理其自己的内存,因此减少了内存泄漏和缓冲区溢出的风险。

3.方便的成员函数: string 类提供了各种操作字符串的方法,如 append() (添加)、 insert()

(插入)、 erase() (删除)、 substr() (获取子字符串)等。

4.操作符重载: string 类重载了多个操作符,使得字符串比较、连接和赋值更加直观。例如,你可

以使用

操作符来连接两个字符串,或者使用

操作符来比较两个字符串是否相等。

5.迭代器支持:像其他标准库容器一样, string 类也支持迭代器,使得你可以使用迭代器来遍历字符串中的字符。

6.与 C 风格字符串的兼容性: string 类提供了与 C 风格字符串互操作的功能,例如,你可以使用方法来获取一个与 C 风格字符串兼容的、以 null 结尾的字符数组。

下面是一个简单的 类的使用示例:

在这个示例中,我们创建了一个 类的灵活性和强大功能。

下面是一个表格,展示了 C++ 中

对象 str ,然后使用不同的方法对其进行操作。这展示了类的一些常用成员函数及其功能和参数:

函数名 功能 参数 返回值类型

length() 或 size() 返回字符串的长度 无

size_t

empty() 检查字符串是否为空 无

bool

append(const string& str) 向字符串末尾添加另一个字符串 要追加的字符串

string&

substr(size_t pos = 0, size_t len = npos)

返回一个子字符串 pos :子字符串的起始位置

len :子字符串的长度

string

find(const string& str, size_t pos = 0)

查找子字符串出现的位置 str :要查找的字符串

pos :搜索起始位置

size_t

compare(const string& str) 比较两个字符串 要比较的字符串 int

erase(size_t pos = 0, size_t len = npos) 删除字符串中的一部分 pos :起始位置 len :要删除的长度

string&

insert(size_t pos, const string& str) 在指定位置插入字符串 pos :插入位置 str :要插入的字符串

string&

replace(size_t pos, size_t len, const string& str)

替换字符串中的一部分 pos :起始位置 len :要替换的长度

str :替换的字

符串

string&

c_str() 返回 C 风格字符串表示 无 const char*

operator[] (size_t pos) 访问指定位置的字符 pos :字符位置

char&

这些函数是 类中常用的一部分,提供了强大且灵活的字符串操作能力。使用这些函数可

以方便地处理和修改字符串数据。

2.3 类

2.3.1类的初探

C++  中的类(class)是一种编程结构,用于创建对象。这些对象可以拥有属性(即数据成员)和行为

(即成员函数或方法)。类的概念是面向对象编程的核心之一,其主要目的是将数据和与数据相关的操作封装在一起。例如,如果你有一个“汽车”类,它可能包含颜色、品牌、型号等属性(数据成员),以及启动、停止、加速等行为(成员函数)。每当你基于这个类创建一个对象时,你就有了一个具体的汽

车,具有这些属性和行为。

C++ 类的基本结构通常包含:

1.数据成员(Attributes):定义类的属性。这些是类内部的变量,用于存储对象的状态。

2.成员函数(Methods):定义类的行为。这些是可以操作对象的数据成员的函数。

3.构造函数和析构函数:特殊的成员函数。构造函数在创建对象时自动调用,用于初始化对象。析构函数在对象销毁时调用,用于执行清理操作。

4.访问修饰符:如 public , private , protected ,用于控制对类成员的访问权限。例如, public

成员可以在类的外部访问,而 成员只能在类内部访问。

5.继承:允许一个类继承另一个类的特性。这是代码重用和多态性的关键。

通过这些特性,C++ 类提供了一种强大的方式来组织和处理数据,使得代码更加模块化、易于理解和维护。

2.3.2结构体引入类

2.3.2.1回忆结构体

如果用C语言实现上面描述的汽车类,我们实现如下代码

2.3.2.2新建C++工程来使用结构体

 在C++中,字符串用string来表示,发现有个string赋值给char 的警告,所以修改所有char *为 string类型

修改后,发现printf的%s控制位,不能用于string的输出,所有有string构建了即将要输出的字符串

C++中,通过std::tostring()函数,将整型数转化成字符串在printInfo中使用cout输出汽车信息

发现在C++工程中,使用malloc在堆申请结构体空间有问题,所以直接在此引入类的概念,把struct改成class

引入新问题,class的成员数据和成员函数在不指定权限的情况下,默认private权限,类的对象无法进行直接访问

添加public属性

访问权限 类内部 同一个类的对象 派生类(子类) 类外部

public ✔ 可访问 ✔ 可访问 ✔ 可访问 ✔ 可访问

private ✔ 可访问 ❌ 不可访问 ❌ 不可访问 ❌ 不可访问

protected ✔ 可访问 ❌ 不可访问 ✔ 可访问 ❌ 不可访问

把main函数中的原本结构体变量改成了类的实例化,如果变量类型是指针,把原来的malloc改成

new一个对象

最后解决了所有问题

#include <stdio.h> #include <stdlib.h> #include <string> #include <iostream>

using namespace std;

class Car{ //汽车“类” public:

string color; //颜色 string brand; //品牌 string type; //车型 int year;  //年限

void (*printCarInfo)(string color,string brand,string type, int year); //函数指针,指向车介绍函数

void (*carRun)(string type); //函数指针,指向车运行的函数

void (*carStop)(string type); //函数指针,执行车停止的函数

};

void bwmThreePrintCarInfo(string color,string brand,string type, int year)

{

string str = "车的品牌是:" + brand

+ ",型号是: " + type

+ ",颜色是:" + color

+ ",上市年限是:" + std::to_string(year); cout << str << endl;

}

void A6PrintCarInfo(string color,string brand,string type, int year)

{

string str = "车的品牌是:" + brand

+ ",型号是: " + type

+ ",颜色是:" + color

+ ",上市年限是:" + std::to_string(year); cout << str << endl;

}

int main()

{

Car BWMthree;

BWMthree.color = "白色"; BWMthree.brand = "宝马";

但是!我们还没有真正体验到面向对象的封装特性,仅仅感受到权限上的区别

2.3.2.3真正的成员函数

  上一节的案例中, void (*printCarInfo)(string color,string brand,string type, int

到底是变量函数函数呢?

  是一个指针变量,是保存某个函数地址的变量,所以它不是成员函数,是成员数据

  真正的成员函数遵守封装特性,在函数体内部访问成员数据的时候,不需要参数传递

在 C++ 中,双冒号 称为 "作用域解析运算符"(Scope Resolution Operator)。它用于指定一

个成员(如函数或变量)属于特定的类或命名空间。例如,在类的外部定义成员函数时, :: 用于指明该函数属于哪个类。

void (*printCarInfo)(string color,string brand,string type, int year); //函数指针,指向车介绍函数

void (*carRun)(string type); //函数指针,指向车运行的函数

void (*carStop)(string type); //函数指针,执行车停止的函数 void realPrintCarInfo();//声明成员函数

};

void Car::realPrintCarInfo() //在类的外部进行成员函数的实现

{

string str = "车的品牌是:" + brand

+ ",型号是: " + type

+ ",颜色是:" + color

+ ",上市年限是:" + std::to_string(year); cout << str << endl;

}

void bwmThreePrintCarInfo(string color,string brand,string type, int year)

{

string str = "车的品牌是:" + brand

+ ",型号是: " + type

+ ",颜色是:" + color

+ ",上市年限是:" + std::to_string(year); cout << str << endl;

}

void A6PrintCarInfo(string color,string brand,string type, int year)

{

string str = "车的品牌是:" + brand

+ ",型号是: " + type

+ ",颜色是:" + color

+ ",上市年限是:" + std::to_string(year); cout << str << endl;

}

int main()

{

Car BWMthree; BWMthree.color = "白色"; BWMthree.brand = "宝马"; BWMthree.type = "3系"; BWMthree.year = 2023;

BWMthree.printCarInfo = bwmThreePrintCarInfo;

BWMthree.printCarInfo(BWMthree.color,BWMthree.brand,BWMthree.type,BWMthree.year)

;

BWMthree.realPrintCarInfo();

Car *AodiA6 = new Car();

// AodiA6 = (struct Car*)malloc(sizeof(struct Car));

AodiA6->color = "黑色"; AodiA6->brand = "奥迪"; AodiA6->type = "A6";

2.3.4 QT中经常出现的用法

在 C++中,一个类包含另一个类的对象称为组合(Composition)。这是一种常见的设计模式,用于表示一个类是由另一个类的对象组成的。这种关系通常表示一种"拥有"("has-a")的关系。

普通变量访问成员变量或者成员函数,使用 “ . ” 运算符

指针变量访问成员变量或者成员函数,使用“ -> ”运算符,像C语言的结构体用法

void (*printCarInfo)(string color,string brand,string type, int year); //函数指针,指向车介绍函数

void (*carRun)(string type); //函数指针,指向车运行的函数

void (*carStop)(string type); //函数指针,执行车停止的函数 void realPrintCarInfo();//声明成员函数

};

void Car::realPrintCarInfo() //在类的外部进行成员函数的实现

{

string str = "车的品牌是:" + brand

+ ",型号是: " + type

+ ",颜色是:" + color

+ ",上市年限是:" + std::to_string(year); cout << str << endl;

}

void bwmThreePrintCarInfo(string color,string brand,string type, int year)

{

string str = "车的品牌是:" + brand

+ ",型号是: " + type

+ ",颜色是:" + color

+ ",上市年限是:" + std::to_string(year); cout << str << endl;

}

void A6PrintCarInfo(string color,string brand,string type, int year)

{

string str = "车的品牌是:" + brand

+ ",型号是: " + type

+ ",颜色是:" + color

+ ",上市年限是:" + std::to_string(year); cout << str << endl;

}

int main()

{

Car BWMthree; BWMthree.color = "白色"; BWMthree.brand = "宝马"; BWMthree.type = "3系"; BWMthree.year = 2023;

BWMthree.pwl = new Wheel(); BWMthree.pwl->brand = "米其林"; BWMthree.pwl->year = 2023;

//BWMthree.wl.brand = "米其林";

//BWMthree.wl.year = 2023;

BWMthree.printCarInfo = bwmThreePrintCarInfo;

BWMthree.printCarInfo(BWMthree.color,BWMthree.brand,BWMthree.type,BWMthree.year)

;

BWMthree.realPrintCarInfo();

//BWMthree.wl.wheelPrintInfo();

2.4 权限初识

2.4.1基本介绍

C++中的访问权限主要分为三种: public 、 private 和protected 。这些权限决定了类成员(包括数据成员和成员函数)的可访问性。以下是一个总结表格,说明了在不同情况下这些权限如何应用:

访问权限 类内部 同一个类的对象 派生类(子类) 类外部

public ✔ 可访问 ✔ 可访问 ✔ 可访问 ✔ 可访问

private ✔ 可访问 ❌ 不可访问 ❌ 不可访问 ❌ 不可访问

protected ✔ 可访问 ❌ 不可访问 ✔ 可访问 ❌ 不可访问

使用权限(如 public 、 private 和 protected )在C++中是一种关键的封装手段,它们旨在控制对类成员的访问。下面是一个表格,总结了使用权限的主要好处和潜在缺点:

好处 / 缺点 描述

好处

封装性 通过隐藏类的内部实现(私有和受保护成员),提高了代码的安全性和健壮性。

接口与实现的分离 公开接口(公开成员)与私有实现分离,有助于用户仅关注于如何使用类而不是如何实现。

易于维护 修改类的内部实现不会影响使用该类的代码,从而降低了维护成本。

好处 / 缺点 描述

控制读写访问 通过设置访问权限,可以精确控制类成员的读写访问。

继承的灵活性 protected 成员在派生类中是可访问的,使得继承更加灵活。

缺点

增加复杂性 过度使用或不当使用权限可能导致代码结构复杂,难以理解。

测试难度 私有成员的测试比公共成员更困难,因为它们不能从类的外部访问。

灵活性降低 过于严格的封装可能限制了某些有效的用法,降低了灵活性。

可能导致紧耦合 过多依赖 friend 类或函数可能导致类之间的耦合过紧。

2.4.2目前能概况的结论

权限相当于我们学习C语言结构体一样,不考虑访问权限的存在,但是要注意,类中不写权限,默认是私有权限

留到继承讲解的时候再提

私有权限,通过一下案例向各位表达一下作用的意思,但需要未来实战中慢慢体会。

这个例子将阐述在类设计中使用private 成员的必要性。我们将创建一个简单的BankAccount 类,展示如何使用private 来保护账户的余额,确保它只能通过指定的方法进行修改。

所以,我们可以脑部一个场景:

银行的账户是一个模板,是一个类,有存款人信息和账户额度,而具体的存款人视为一个对象,

一个对象不能私自修改账户额度,需要通过一个操作流程,比如去ATM或者柜台进行操作才能修改到账户额度,

所以,存款人信息和账户额度设计成私有权限,通过公有的操作流程,也就是公有函数去操作私有变量。

基于这个场景,我们编程实现代码

int age;

double balance; public:

string bankAddr;

//比如去ATM或者柜台进行操作才能修改到账户额度

void registerMes(string newName, string newAddr,int newAge,double newBalance);

void withdraw(double amount); void deposit(double amount); double getBalance();

void printUserInfo();

};

void BankAccount::printUserInfo()

{

string mesTem = "账户名:" + name + ",地址:" + addr +

",年龄:"+ std::to_string(age) + ",存款:" + std::to_string(balance); cout << mesTem << endl;

}

void BankAccount::registerMes(string newName, string newAddr,int newAge,double newBalance)

{

name = newName; addr = newAddr; age = newAge;

balance = newBalance;

}

// 存款方法

void BankAccount::deposit(double amount) { if (amount > 0) {

balance += amount;

} else {

cerr << "Deposit amount must be positive." << endl;

}

}

// 取款方法

void BankAccount::withdraw(double amount) { if (amount > balance) {

cerr << "Insufficient funds." << endl;

} else if (amount <= 0) {

cerr << "Withdrawal amount must be positive." << endl;

} else {

balance -= amount;

}

}

// 获取当前余额的方法

double BankAccount::getBalance() { return balance;

}

int main()

{

BankAccount user1;

user1.registerMes("老陈","深圳光明区",35,100);

在这个示例中, balance 是一个private 成员变量,它不能被类的外部直接访问。这保证了账户余额只能通过类提供的方法(如 deposit , withdraw , 和 getBalance )来修改和查询,从而防止了不合适的修改,比如直接设置余额为负数或任意值。这样的设计保证了类的封装性和数据的完整性。

2.4.3提问和回答

问:为什么新手学习C++感受不到访问权限的必要性呢

答:新手学习C++时可能不会立即感受到访问权限(如 public 、 private 、 protected )的必要性,主要有以下几个原因:

1.简单的例子和练习:初学者通常从简单的例子和练习开始,这些例子可能不需要复杂的封装或继承结构。在这种情况下,访问权限的作用可能不太明显。

2.封装的概念需要时间去理解:封装是面向对象编程中的一个核心概念,但对于初学者来说,理解封装的价值需要一定的时间和实践。在初期,更多的关注点可能放在基本语法和程序结构上。

3.缺乏大型项目经验:在小型项目或单文件程序中,访问权限的重要性可能不如在大型、多人协作的项目中那么显著。在复杂的软件开发中,适当的访问控制对于代码的维护性和可读性至关重要。

4.直接操作感觉更简单:对于初学者来说,直接访问和修改类的所有成员可能看起来更简单直接。他们可能还没有遇到由于不恰当访问控制导致的维护和调试问题。

5.抽象和设计模式的理解:理解何时以及如何使用访问权限通常涉及到对软件设计模式和抽象的深入理解。这些通常是随着经验积累和更深入的学习而逐渐掌握的。

随着经验的增长,学习者开始处理更复杂的项目,他们将开始意识到恰当的访问控制的重要性,特别是在保持代码的可维护性、可读性以及在团队环境中的协作方面。因此,对于教育者和学习者来说,强调并实践这些概念是很重要的,以便在编程技能成熟时能够有效地运用它们。

2.5 引用

引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。

一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。思维发散:

在C语言中,一个数据对应一个内存,通过由一个变量名来访问这个内存空间的数据,叫做直接访问,相对直接访问,有个间接访问的说法,叫做指针。

而引用相当于又给这个内存中的数据提供了一个新的变量名,

这个变量名功能比传统变量名更特殊,是直达地址的,后续代码验证!

2.6.1和指针的区别

引用很容易与指针混淆,它们之间有三个主要的不同:    不存在空引用。引用必须连接到一块合法的内存。

  一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对

象。

  引用必须在创建时被初始化。指针可以在任何时间被初始化。

  官方没有明确说明,但是引用确实不是传统意义上的独立变量,它不能“变”嘛

试想变量名称是变量附属在内存位置中的标签,您可以把引用当成是变量附属在内存位置中的第二个标签。因此,您可以通过原始变量名称或引用来访问变量的内容。例如:

我们可以为 i 声明引用变量,如下所示:

在这些声明中,& 读作引用。因此,第一个声明可以读作 "r 是一个初始化为 i 的整型引用",第二个声明可以读作 "s 是一个初始化为 d 的 double 型引用"。下面的实例使用了 int 和 double 引用:

2.6.2把引用作为参数

我们已经讨论了如何使用指针来实现引用调用函数。下面的实例使用了引用来实现引用调用函数。实例

运行结果:

2.6.3把引用作为返回值

通过使用引用来替代指针,会使 C++ 程序更容易阅读和维护。C++ 函数可以返回一个引用,方式与返回一个指针类似。

当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。例如,请看下面这个简单的程序:

当上面的代码被编译和执行时,它会产生下列结果:

当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。

2.6 重载

2.5.1函数重载

在同一个作用域内,可以声明几个功能类似的同名函数,

这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。

下面的实例中,同名函数 print() 被用于输出不同的数据类型:

2.5.2运算符重载

在C++中,运算符重载是一个允许程序员自定义各种运算符(如 + , - , == ,

等)在自定义类型(类或

结构体)上的行为的特性。这意味着你可以定义类似于内置类型的运算符行为,使你的自定义类型更加直观和易于使用。

基本原则

1.不可以创建新的运算符:只能重载已经存在的运算符。

2.至少有一个操作数是用户定义的类型:不能重载两个基本类型的运算符。

3.不能更改运算符的优先级:重载的运算符保持其原有的优先级和结合性。

示例1:假设我们有一个Person 类,我们可以重载 运算符来实现两个Person是否相等的判断。

示例2:假设我们有一个简单的

类,我们可以重载

运算符来实现两个点的加法。

在这个例子中, operator+ 被重载为一个成员函数,接受一个回两个点相加的结果。

类型的常量引用作为参数,并返

这里的

表明这个

函数不会修改调用它的

对象。它只是读取对象的 和

成员,并返回一个新的 对象。这种做法在设计类的时候是很有用的,因为它可以确保某些函数不

会意外地改变对象的状态,同时也使得这个函数可以在常量对象上被调用。

注意事项

  一致性:重载的运算符应与其原始意图和常见用法保持一致。例如, + 运算符通常应该实现加法,而不是其他意外的操作。

  复杂性:过度使用运算符重载可能导致代码难以理解和维护。确保它们的使用直观且合理。

运算符重载是C++中提高代码可读性和表达力的强大工具,但需要谨慎使用,以保证代码的清晰性和维护性。

2.7 构造函数

2.7.1什么是构造函数

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。构造,那构造的是什么呢?

构造成员变量的初始化值,内存空间等

构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。

下面的实例有助于更好地理解构造函数的概念:

2.7.2带参数构造函数

默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值,如下面的例子所示:

2.7.3拷贝构造函数

2.7.3.1基本概念及发生条件

拷贝构造函数是 C++ 中的一种特殊的构造函数,用于创建一个新对象作为现有对象的副本。它在以下几种情况下被调用:

1.当一个新对象被创建为另一个同类型的现有对象的副本时:

  例如: MyClass obj1 = obj2; 或 MyClass obj1(obj2); ,其中

2.将对象作为参数传递给函数时(按值传递):

是现有的对象。

  当对象作为参数传递给函数,并且参数不是引用时,会使用拷贝构造函数创建函数内部的对象副本。

3.从函数返回对象时(按值返回):

  当函数返回对象,并且没有使用引用或指针时,拷贝构造函数用于从函数返回值创建副本。

4.初始化数组或容器中的元素时:

  例如,在创建一个包含对象的数组时,数组中的每个对象都是通过拷贝构造函数初始化的。拷贝构造函数的典型声明如下:

其中, other 是对同类型对象的引用,通常是常量引用。

示例代码

2.7.3.2浅拷贝

在 C++ 中,深拷贝和浅拷贝是处理对象拷贝时的两种不同方法,尤其是在对象包含指针或动态分配的内存时。我将分别给出深拷贝和浅拷贝的例子。

浅拷贝 (Shallow Copy)

浅拷贝只复制对象的成员变量的值。如果成员变量是指针,则复制指针的值(即内存地址),而不是指针所指向的实际数据。这会导致多个对象共享相同的内存地址。

data cout cout cout = new int(d); // 动态分配内存

<< "观察数据:" << endl;

<< d << endl;

<< *data << endl;

} cout cout << "观察内存在构造函数中:" << endl;

<< data << endl;

在这个例子中, obj2 是通过浅拷贝存地址。

创建的。这意味着

指向相同的内

被销毁时,同一内存地址会被尝试释放两次,导致潜在的运行时错误。

在QT中我们不能直观看见,在Linux中我们获得如下运行结果:

2.7.3.3深拷贝

深拷贝复制对象的成员变量的值以及指针所指向的实际数据。这意味着创建新的独立副本,避免了共享内存地址的问题。

在这个例子中, obj2 是通过深拷贝

创建的。这意味着

指向不同的内

存地址。每个对象有自己的内存副本,因此不会相互影响,避免了潜在的运行时错误。

2.7.3.4规则三则

在 C++ 中,规则三则(Rule of Three)是一个面向对象编程原则,它涉及到类的拷贝控制。规则三则指出,如果你需要显式地定义或重载类的任何一个拷贝控制操作(拷贝构造函数、拷贝赋值运算符、析构函数),那么你几乎肯定需要显式地定义或重载所有三个。这是因为这三个功能通常都是用于管理动态分配的资源,比如在堆上分配的内存。

下面是一个遵循规则三则的简单示例:

#include <iostream> #include <cstring>

class MyClass { private:

char* buffer;

public:

// 构造函数

MyClass(const char* str) { if (str) {

buffer = new char[strlen(str) + 1]; strcpy(buffer, str);

} else {

buffer = nullptr;

}

}

// 析构函数

~MyClass() {

delete[] buffer;

}

// 拷贝构造函数

MyClass(const MyClass& other) { if (other.buffer) {

buffer = new char[strlen(other.buffer) + 1]; strcpy(buffer, other.buffer);

} else {

buffer = nullptr;

}

}

// 拷贝赋值运算符

MyClass& operator=(const MyClass& other) { if (this != &other) {

delete[] buffer; // 首先删除当前对象的资源

if (other.buffer) {

buffer = new char[strlen(other.buffer) + 1]; strcpy(buffer, other.buffer);

} else {

buffer = nullptr;

}

}

return *this;

}

};

int main() {

MyClass obj1("Hello");

MyClass obj2 = obj1; // 调用拷贝构造函数

MyClass obj3("World");

obj3 = obj1; // 调用拷贝赋值运算符

return 0;

}

在这个例子中:

 构造函数为成员变量  析构函数释放

分配内存,并复制给定的字符串。所占用的内存,以避免内存泄露。

  拷贝构造函数创建一个新对象作为另一个现有对象的副本,并为其分配新的内存,以避免多个对象共享同一内存。

  拷贝赋值运算符更新对象时,首先释放原有资源,然后根据新对象的状态分配新资源。这个类遵循规则三则,确保了动态分配资源的正确管理,避免了内存泄露和浅拷贝问题。

2.7.3.5避免不必要的拷贝

避免不必要的拷贝是 C++ 程序设计中的一个重要原则,尤其是在处理大型对象或资源密集型对象时。使用引用(包括常量引用)和移动语义(C++11  引入)是实现这一目标的两种常见方法。下面是两个示 例:

1.使用引用传递对象

通过使用引用(尤其是常量引用)来传递对象,可以避免在函数调用时创建对象的副本。

在这个例子中, processLargeObject 函数接受一个对调用时复制整个 LargeObject 。

2.使用移动语义

类型的常量引用,避免了在函数

C++11 引入了移动语义,允许资源(如动态分配的内存)的所有权从一个对象转移到另一个对象,这避免了不必要的拷贝。

在这个例子中, MovableObject 类有一个移动构造函数和一个移动赋值运算符,它们允许对象的资源

(如动态分配的内存)在赋值或返回时被“移动”而非复制。这减少了对资源的不必要拷贝,提高了效率。

通过这些方法,你可以在 C++ 程序中有效地减少不必要的对象拷贝,尤其是对于大型或资源密集型的对象。

2.7.3.6拷贝构造函数的隐式调用

在 C++ 中,拷贝构造函数可能会在几种不明显的情况下被隐式调用。这种隐式调用通常发生在对象需要被复制时,但代码中并没有明显的赋值或构造函数调用。了解这些情况对于高效和正确地管理资源非常重要。下面是一些典型的隐式拷贝构造函数调用的例子:

1.作为函数参数传递(按值传递)

当对象作为函数参数按值传递时,会调用拷贝构造函数来创建参数的本地副本。

2.从函数返回对象(按值返回)

当函数返回一个对象时,拷贝构造函数会被用于创建返回值的副本。

3.初始化另一个对象

当用一个对象初始化另一个同类型的新对象时,会使用拷贝构造函数。

在所有这些情况下,如果类包含资源管理(例如,动态内存分配),那么正确地实现拷贝构造函数是非常重要的,以确保资源的正确复制和管理,防止潜在的内存泄漏或其他问题。此外,随着 C++11 的引入,移动语义提供了对资源的高效管理方式,可以减少这些场景中的资源复制。

2.7.3.7禁用拷贝构造函数

在 C++ 中,禁用拷贝构造函数是一种常用的做法,尤其是在设计那些不应该被复制的类时。这可以通过

将拷贝构造函数声明为

或使用 C++11 引入的

关键字来实现。这样做的目的是防止类

的对象被拷贝,从而避免可能导致的问题,如资源重复释放、无意义的资源复制等。

使用 关键字

在 C++11 及更高版本中,可以使用 关键字明确指定不允许拷贝构造:

这种方法清晰明了,它向编译器和其他程序员直接表明该类的对象不能被拷贝。

使用 声明(C++98/03)

在 C++11 之前,常见的做法是将拷贝构造函数和拷贝赋值运算符声明为 private ,并且不提供实现:

在这个例子中,任何尝试拷贝 类型对象的操作都会导致编译错误,因为拷贝构造函数和

拷贝赋值运算符是私有的,外部代码无法访问它们。

通过这些方法,你可以确保你的类的对象不会被意外地拷贝,从而避免可能出现的资源管理相关的错误。

2.7.3.8拷贝构造函数总结

C++ 中拷贝构造函数需要注意的七个要点的表格:

要点 描述

定义和作用 拷贝构造函数在创建对象作为另一个现有对象副本时调用,通常有一个对同类型对象的常量引用参数。

语法 典型声明为 ClassName(const ClassName &other) 。

深拷贝与浅拷贝 浅拷贝复制值,深拷贝创建资源的独立副本。对于包含指针的类,深拷贝通常必要。

规则三则 (Rule of Three) 如果实现了拷贝构造函数、拷贝赋值运算符或析构函数中的任何一个,通常应该实现所有三个。

要点 描述

避免不必要的拷贝 对于大型对象,使用移动语义避免不必要的拷贝,并在传递对象时使用引用或指针。

拷贝构造函数的隐式调用 不仅在显式复制时调用,也可能在将对象作为函数参数传递、从函数返回对象时隐式调用。

禁用拷贝构造函数 对于某些类,可以通过将拷贝构造函数声明为私有或使用 delete 关键字禁用拷贝。

2.7.4使用初始化列表

在C++中,使用初始化列表来初始化类的字段是一种高效的初始化方式,尤其在构造函数中。初始化列表直接在对象的构造过程中初始化成员变量,而不是先创建成员变量后再赋值。这对于提高性能尤其重

要,特别是在涉及到复杂对象或引用和常量成员的情况下。

初始化列表紧跟在构造函数参数列表后面,以冒号( : )开始,后跟一个或多个初始化表达式,每个表达式通常用逗号分隔。下面是使用初始化列表初始化字段的例子:

在这个例子中, MyClass 有三个成员变量: a ( int 类型)、 b ( double 类型)和

( std::string 类型)。当创建 的一个实例时,我们通过构造函数传递三个参数,这些参数

被用于通过初始化列表直接初始化成员变量。初始化列表

的意思是用

初始化

a ,用

初始化 b ,用

初始化 c 。

初始化列表的优点包括:

1.效率:对于非基本类型的对象,使用初始化列表比在构造函数体内赋值更高效,因为它避免了先默认构造然后再赋值的额外开销。

2.必要性:对于引用类型和常量类型的成员变量,必须使用初始化列表,因为这些类型的成员变量在构造函数体内不能被赋值。

3.顺序:成员变量的初始化顺序是按照它们在类中声明的顺序,而不是初始化列表中的顺序。使用初始化列表是C++中推荐的初始化类成员变量的方式,因为它提供了更好的性能和灵活性。

2.7.5this关键字

在 C++ 中, this 关键字是一个指向调用对象的指针。它在成员函数内部使用,用于引用调用该函数的

对象。使用展示

示例代码

可以明确指出成员函数正在操作的是哪个对象的数据成员。下面是一个使用 类来关键字用法的示例:

下面的代码展示了如何使用 关键字:

#include <iostream> #include <string>

using namespace std;

class Car { private:

string brand;

int year;

public:

Car(string brand, int year) { this->brand = brand;

this->year = year;

// cout << "构造函数中:" << endl;

// cout << this << endl;

}

void display() const {

cout << "Brand: " << this->brand << ", Year: " << this->year << endl;

// 也可以不使用 this->,直接写 brand 和 year

}

Car& setYear(int year) {

this->year = year; // 更新年份

return *this; // 返回调用对象的引用

}

};

int main()

{

Car car("宝马",2024); car.display();

// 链式调用

car.setYear(2023).display();

// cout << "main函数中:" << endl;

// cout << &car << endl;

// Car car2("宝马",2024);

// cout << "main函数中:" << endl;

// cout << &car2 << endl; return 0;

}

在这个例子中, Car 类的构造函数使用 指针来区分成员变量和构造函数参数。同样, setYear

成员函数使用 指针来返回调用该函数的对象的引用,这允许链式调用,如

些成员函数。

2.7.6new关键字

函数中创建了

类型的对象,并展示了如何使用这

在C++中, new 关键字用于动态分配内存。它是C++中处理动态内存分配的主要工具之一,允许在程序运行时根据需要分配内存。

基本用法

分配单个对象:使用

可以在堆上动态分配一个对象。例如, new int 会分配一个

类型的空

间,并返回一个指向该空间的指针。

分配对象数组: new 也可以用来分配一个对象数组。例如, new int[10] 会分配一个包含10个整数的数组。

初始化:可以在 表达式中使用初始化。对于单个对象,可以使用构造函数的参数:

与 配对使用

使用 分配的内存必须显式地通过 delete (对于单个对象)或 delete[] (对于数组)来释放,以

避免内存泄露:

释放单个对象:

释放数组:

注意事项

 异常安全:如果本)。

分配内存失败,它会抛出

异常(除非使用了 版

内存泄露:忘记释放使用 分配的内存会导致内存泄露。

匹配使用

和 delete[] :为避免未定义行为,使用

分配的单个对象应该使用

示例代码

释放,使用

分配的数组应该使用

释放。

在这个例子中, new 被用来分配一个 类型的对象和一个整数数组,然后使用 和

来释放内存。每个 都对应一个 delete ,保证了动态分配的内存被适当管理。

2.8 析构函数

2.8.1什么是析构函数

析构函数是C++中的一个特殊的成员函数,它在对象生命周期结束时被自动调用,用于执行对象销毁前的清理工作。析构函数特别重要,尤其是在涉及动态分配的资源(如内存、文件句柄、网络连接等)的情况下。

基本特性

1.名称:析构函数的名称由波浪号( ~ )后跟类名构成,如 ~MyClass() 。

2.无返回值和参数:析构函数不接受任何参数,也不返回任何值。

3.自动调用:当对象的生命周期结束时(例如,一个局部对象的作用域结束,或者使用 删除一个动态分配的对象),析构函数会被自动调用。

4.不可重载:每个类只能有一个析构函数。

5.继承和多态:如果一个类是多态基类,其析构函数应该是虚的。示例

假设我们有一个类  MyClass ,它包含了动态分配的内存或其他资源:

在这个示例中, MyClass 的构造函数分配了一块内存,而析构函数释放了这块内存。当 的生命周

期结束时(即离开了它的作用域), MyClass 的析构函数被自动调用,负责清理资源,防止内存泄露。

重要性

析构函数在管理资源方面非常重要。没有正确实现析构函数,可能导致资源泄露或其他问题。在基于RAII

(资源获取即初始化)原则的C++编程实践中,确保资源在对象析构时被适当释放是非常关键的。当使用智能指针和其他自动资源管理技术时,可以减少显式编写析构函数的需要,但了解析构函数的工作原理仍然很重要。

以下是关于 C++ 中析构函数需要了解的十个要点的表格:

标注粗体部分,是能快速上手的内容,方便后续QT的学习,而没有粗体的部分,会在QT结束后,如果安排C++深入讲解的课程的话,会安排到。

同理拷贝构造函数,考虑到学习策略安排,这里先陈列出来。

要点 描述

定义和作用 析构函数在对象生命周期结束时自动调用,用于清理对象可能持有的资源。

语法 析构函数名称由波浪线 (~) 后跟类名构成,例如 MyClass 的析构函数为

~MyClass() 。

资源管理 用于释放对象在生命周期中分配的资源,如动态内存、文件句柄、网络连接等。

自动调用机制 当对象离开其作用域或通过 delete 删除时,将自动调用其析构函数。

防止资源泄露 正确实现析构函数对防止资源泄露至关重要,特别是在涉及动态资源分配的情况。

虚析构函数 如果类作为基类设计,应有一个虚析构函数,以确保正确调用派生类的析构函数。

析构函数与异常 析构函数不应抛出异常,如果可能抛出,应在函数内捕获。

要点 描述

删除的析构函数 可以通过将析构函数声明为删除( ~MyClass() = delete; )来禁止删除某类对象。

与构造函数的关系 每个类只能有一个析构函数,不可重载,与构造函数相比。

规则三则/五则 如果类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符,可能也需要自定义另外两个(规则三则)。在 C++11 后还包括移动构造函数和移动赋值运算符(规则五则)。

这个表格概括了在学习和使用 C++ 析构函数时应考虑的主要点

2.9 静态成员

2.9.1静态成员的定义

静态成员在C++类中是一个重要的概念,它包括静态成员变量和静态成员函数。静态成员的特点和存在的意义如下:

静态成员变量

1.定义:静态成员变量是类的所有对象共享的变量。与普通成员变量相比,无论创建了多少个类的实例,静态成员变量只有一份拷贝。

2.初始化:静态成员变量需要在类外进行初始化,通常在类的实现文件中。

3.访问:静态成员变量可以通过类名直接访问,不需要创建类的对象。也可以通过类的对象访问。

4.用途:常用于存储类级别的信息(例如,计数类的实例数量)或全局数据需要被类的所有实例共享。

静态成员函数

1.定义:静态成员函数是可以不依赖于类的实例而被调用的函数。它不能访问类的非静态成员变量和非静态成员函数。

2.访问:类似于静态成员变量,静态成员函数可以通过类名直接调用,也可以通过类的实例调用。

3.用途:常用于实现与具体对象无关的功能,或访问静态成员变量。示例代码

存在的意义

  共享数据:允许对象之间共享数据,而不需要每个对象都有一份拷贝。  节省内存:对于频繁使用的类,使用静态成员可以节省内存。

  独立于对象的功能:静态成员函数提供了一种在不创建对象的情况下执行操作的方法,这对于实现工具函数或管理类级别状态很有用。

2.9.2静态成员变量的作用

静态成员变量在C++中的一个典型应用是用于跟踪类的实例数量。这个案例体现了静态成员变量的特性:它们在类的所有实例之间共享,因此适合于存储所有实例共有的信息。

下面是一个示例,展示了如何使用静态成员变量来计数一个类的实例数量:

在这个例子中:

类有一个静态成员变量  staticNumofInstance ,用来跟踪该类的实例数量。

每当创建每当一个

通过静态成员函数

静态成员变量

的新实例时,构造函数会增加 staticNumofInstance 。 实例被销毁时,析构函数会减少 staticNumofInstance 。

可以随时获取当前的实例数量。

在类外初始化为0。

这个案例展示了静态成员变量如何在类的所有实例之间共享,并为所有实例提供了一个共同的状态(在这个例子中是实例的数量)。这种技术在需要跟踪对象数量或实现某种形式的资源管理时特别有用。

2.10 继承

2.10.1继承基本概念

继承是面向对象编程(OOP)中的一个核心概念,特别是在C++中。它允许一个类(称为派生类或子    类)继承另一个类(称为基类或父类)的属性和方法。继承的主要目的是实现代码重用,以及建立一种类型之间的层次关系。

特点

1.代码重用:子类继承了父类的属性和方法,减少了代码的重复编写。

2.扩展性:子类可以扩展父类的功能,添加新的属性和方法,或者重写(覆盖)现有的方法。

3.多态性:通过继承和虚函数,C++支持多态,允许在运行时决定调用哪个函数。基本用法

在C++中,继承可以是公有(public)、保护(protected)或私有(private)的,这决定了基类成员在派生类中的访问权限。

在这个例子中, Vehicle 类公有地继承自类中也是公有的。

类,这意味着所有

类的公有成员在

让我们用一个简单而有趣的案例来说明继承的概念:动物园中的动物。

想象我们正在创o在这个程序中,我们有一个基类  Animal ,它定义了所有动物共有的特性和行为。然

后,我们可以创建几个派生类,如 Lion 、 Elephant 和 Bird ,这些类继承自修改特定于它们自己的特性和行为。

基类:Animal

类,并添加或

派生类:Lion

派生类:Elephant

派生类:Bird

使用这些类

在这个例子中:

是基类,定义了所有动物共有的属性(如

和 age )和方法(如 和

Lion 、 Elephant 和

是派生类,它们继承了方法。

的特性,并根据自身的特性重写了

在 函数中,创建了各种动物的实例,并展示了它们的行为。

这个例子展示了继承如何使代码更有组织、更易于管理,并且如何通过重写基类方法来实现多态性。

2.10.2权限对继承的影响

在C++中,访问控制符对继承的影响可以通过下表来清晰地展示。这个表格展示了不同类型的继承

( public 、 protected 、 private )如何影响基类的不同类型成员( public 、 protected 、

private )在派生类中的访问级别。

基类成员类型 public 继承 protected 继承 private 继承

public public protected private

protected protected protected private

基类成员类型 public 继承 protected 继承 private 继承

private 不可访问 不可访问 不可访问

解释:

继承:基类的 public 成员在派生类中仍然是

的, protected 成员仍然是

的。基类的 成员在派生类中不可访问。

继承:基类的

成员在派生类中都变成

的。基类

的 成员在派生类中不可访问。

继承:基类的

成员在派生类中都变成

的。基类的

成员在派生类中不可访问。

这个表格提供了一个快速参考,帮助理解在不同类型的继承中基类成员的访问级别是如何变化的。记

住,无论继承类型如何,基类的

访问权限回顾

课程上写的验证代码

成员始终不可直接在派生类中访问。

void tsetFunc(){

price = 10; //基类的公有数据被私有继承后,在派生类中权限编程私有,只限在类内部使用

}

};

//公有继承测试

class Truck : protected Vehicle{

public:

void testFunc(){

type = "数据测试"; //编程了公有权限 protectedData = 10; //保持公有权限

privateData = 10; //报错了,基类的私有成员,不管哪种方式的继承都是不可访问的。

}

};

//公有继承,基类的公有权限和保护权限不变,私有成员不能访问 class Bickle : public Vehicle{

public:

void testFunc(){ protectedData = 10;

}

};

//派生类,子类

class Roadster : public Vehicle{ //跑车,也是抽象,比父类感觉上范围缩小了点 public:

int stateOfTop;

void openTopped(); void pdrifting();

};

int main()

{

TestClass test;

test.price = 3.3; //报错了,基类的公有成员被私有继承后,降为私有权限

Truck t;

t.type = "测试"; //报错了,基类的公有成员被保护继承后,降为保护权限 t.protectedData = 10; //从报错信息看出,保护继承造成基类的保护成员还是保持保护权限 Roadster ftype;

ftype.type = "捷豹Ftype"; ftype.run();

Bickle bike; bike.type = "死飞"; bike.run();

return 0;

}

2.10.3基类构造函数

在C++中,派生类可以通过其构造函数的初始化列表来调用基类的构造函数。这是在构造派生类对象时初始化基类部分的标准做法。

当创建派生类的对象时,基类的构造函数总是在派生类的构造函数之前被调用。如果没有明确指定,将调用基类的默认构造函数。如果基类没有默认构造函数,或者你需要调用一个特定的基类构造函数,就需要在派生类构造函数的初始化列表中明确指定。

示例

假设我们有一个基类

和一个派生自

的类 Derived :

在这个例子中:

类有一个接受一个整数参数的构造函数。

类继承自  Base ,它的构造函数接受一个整数和一个双精度浮点数。在其初始化列表中,

它调用

当 数。

类的构造函数,并传递整数参数。

类的对象被创建时,首先调用

类的构造函数,然后调用

类的构造函

通过这种方式,派生类能够确保其基类部分被正确初始化。在继承层次结构中,这是非常重要的,特别是当基类需要一些特定的初始化操作时。

2.10.4虚函数

在C++中, virtual 和

关键字用于支持多态,尤其是在涉及类继承和方法重写的情况下。正

确地理解和使用这两个关键字对于编写可维护和易于理解的面向对象代码至关重要。

virtual 关键字

1.使用场景:在基类中声明虚函数。

2.目的:允许派生类重写该函数,实现多态。

3.行为:当通过基类的指针或引用调用一个虚函数时,调用的是对象实际类型的函数版本。

4.示例:

override 关键字

1.使用场景:在派生类中重写虚函数。

2.目的:明确指示函数意图重写基类的虚函数。

3.行为:确保派生类的函数确实重写了基类中的一个虚函数。如果没有匹配的虚函数,编译器会报错。

4.示例:

注意点

  只在派生类中使用 override: override 应仅用于派生类中重写基类的虚函数。

  虚析构函数:如果类中有虚函数,通常应该将析构函数也声明为虚的。

  默认情况下,成员函数不是虚的:在C++中,成员函数默认不是虚函数。只有显式地使用关键字才会成为虚函数。

  继承中的虚函数:一旦在基类中声明为虚函数,该函数在所有派生类中自动成为虚函数,无论是否

使用

正确使用

关键字。

关键字有助于清晰地表达程序员的意图,并利用编译器检查来避免常

见的错误,如签名不匹配导致的非预期的函数重写。

2.10.5多重继承

在C++中,多重继承是一种允许一个类同时继承多个基类的特性。这意味着派生类可以继承多个基类的属性和方法。多重继承增加了语言的灵活性,但同时也引入了额外的复杂性,特别是当多个基类具有相同的成员时。

基本概念

在多重继承中,派生类继承了所有基类的特性。这包括成员变量和成员函数。如果不同的基类有相同名称的成员,则必须明确指出所引用的是哪个基类的成员。

示例

假设有两个基类 和 ClassB ,以及一个同时从这两个类继承的派生类 Derived :

在这个示例中, Derived 类同时继承了法。

注意事项

和 ClassB 。因此,它可以使用这两个类中定义的方

  菱形继承问题:如果两个基类继承自同一个更高层的基类,这可能导致派生类中存在两份基类的副本,称为菱形继承(或钻石继承)问题。这可以通过虚继承来解决。

  复杂性:多重继承可能会使类的结构变得复杂,尤其是当继承层次较深或类中有多个基类时。

  设计考虑:虽然多重继承提供了很大的灵活性,但过度使用可能导致代码难以理解和维护。在一些情况下,使用组合或接口(纯虚类)可能是更好的设计选择。

多重继承是C++的一个强大特性,但应谨慎使用。合理地应用多重继承可以使代码更加灵活和强大,但不当的使用可能导致设计上的问题和维护困难。

2.10.6虚继承

虚继承是C++中一种特殊的继承方式,主要用来解决多重继承中的菱形继承问题。在菱形继承结构中,一个类继承自两个具有共同基类的类时,会导致共同基类的成员在派生类中存在两份拷贝,这不仅会导致资源浪费,还可能引起数据不一致的问题。虚继承通过确保共同基类的单一实例存在于继承层次中,来解决这一问题。

菱形继承问题示例

考虑以下的类结构:

在这个例子中, FinalDerived 类通过

间接地继承自

类两次。因此,

它包含了两份 的成员拷贝。

使用虚继承解决菱形继承问题

要解决这个问题,应使用虚继承:

通过将

的继承声明为虚继承( virtual public Base ),

的都是同一个

特点和注意事项

类中只会有一份类的成员。

类的成员。无论通过

还是

的路径,访问

初始化虚基类:在使用虚继承时,虚基类(如上例中的

FinalDerived )初始化。

类)只能由最派生的类(如

  内存布局:虚继承可能会改变类的内存布局,通常会增加额外的开销,比如虚基类指针。

  设计考虑:虚继承应谨慎使用,因为它增加了复杂性。在实际应用中,如果可以通过其他设计(如组合或接口)避免菱形继承,那通常是更好的选择。

虚继承是C++语言中处理复杂继承关系的一种重要机制,但它也带来了一定的复杂性和性能考虑。正确地使用虚继承可以帮助你建立清晰、有效的类层次结构。

C++ 继承相关的学习内容整理成表格的形式:

学习内容 描述

继承的基础 理解基类和派生类的概念,以及如何通过继承扩展类功能。了解不同继承类型(公有、私有、保护)及其影响。

构造函数和析构函数在继承中的行为 学习派生类如何调用基类的构造函数和析构函数,以及它们的调用顺序。

访问控制和继承 理解公有、私有和保护继承对成员访问权限的影响。掌握继承中的访问修饰符(public, protected, private)。

函数重写和多态 学习多态和如何通过虚函数实现它,了解如何重写基类方法,以及纯虚函数和抽象类的概念。

虚继承和解决菱形问题 理解菱形继承问题及其解决方式,学习如何使用虚继承。

C++11 新特性中的继承相关内容 理解和应用 override 和 final 关键字,了解移动语义在继承中的应用。

设计原则与最佳实践 学习正确使用继承的方法,区分何时使用继承,何时使用组合,以及面向对象设计原则的应用。

实际案例分析 通过分析和编写实际代码示例加深理解,研究设计模式中继承的应用。

这个表格概述了学习 C++ 继承的关键方面和内容,有助于系统地理解和应用继承的概念。

2.11 多态

多态的基本概念(polymorphic)

想象一下,你有一个遥控器(这就像是一个基类的指针),这个遥控器可以控制不同的电子设备(这些设备就像是派生类)。无论是电视、音响还是灯光,遥控器上的“开/关”按钮(这个按钮就像是一个虚函数)都能控制它们,但具体的操作(打开电视、播放音乐、开灯)则取决于你指向的设备。

2.11.1如何实现多态

1.使用虚函数(Virtual Function):

  我们在基类中定义一个虚函数,这个函数可以在任何派生类中被“重写”或者说“定制”。

 使用关键字

2.创建派生类并重写虚函数:

来声明。

  在派生类中,我们提供该虚函数的具体实现。这就像是告诉遥控器,“当你控制我的这个设备时,这个按钮应该这样工作”。

3.通过基类的引用或指针调用虚函数:

  当我们使用基类类型的指针或引用来调用虚函数时,实际调用的是对象的实际类型(派生类)中的函数版本。

视频课程中的手写案例

class RemoteCon{ public:

virtual void openUtils(){

cout << "遥控器的开被按下" << endl;

}

};

class TvRemoteCon : public RemoteCon{

public:

void openUtils() override{

cout << "电视遥控器的开被按下" << endl;

}

void testFunc(){

}

};

class RoundspeakerCon : public RemoteCon{

public:

void openUtils() override{

cout << "音响遥控器的开被按下" << endl;

}

};

class LightCon : public RemoteCon{

public:

void openUtils() override{

cout << "灯光遥控器的开被按下" << endl;

}

};

void test(RemoteCon& r)//引用的方式

{

r.openUtils();

}

int main()

{

RemoteCon *remoteCon = new TvRemoteCon; //多态 remoteCon->openUtils();

RemoteCon *remoteCon2 = new TvRemoteCon; //多态 remoteCon2->openUtils();

RemoteCon *remoteCon3 = new LightCon; //多态 remoteCon3->openUtils();

TvRemoteCon tvRemote; test(tvRemote);

在这个例子中,不同的对象( TvRemoteCon 和 TvRemoteCon )以它们自己的方式“开”,尽管调用的是相同的函数 openUtils 。这就是多态的魅力——相同的接口,不同的行为。

为什么使用多态

  灵活性:允许我们编写可以处理不确定类型的对象的代码。

  可扩展性:我们可以添加新的派生类而不必修改使用基类引用或指针的代码。

  接口与实现分离:我们可以设计一个稳定的接口,而将具体的实现留给派生类去处理。

2.11.2抽象类

抽象类的基本概念

想象一下,你有一个“交通工具”的概念。这个概念告诉你所有交通工具都应该能做什么,比如移动

(move),但它并不具体说明怎么移动。对于不同的交通工具,比如汽车和自行车,它们的移动方式是不同的。在这个意义上,“交通工具”是一个抽象的概念,因为它本身并不能直接被使用。你需要一个具体的交通工具,比如“汽车”或“自行车”,它们根据“交通工具”的概念具体实现了移动的功能。

在 C++ 中,抽象类就像是这样的一个抽象概念。它定义了一组方法(比如移动),但这些方法可能没有具体的实现。这意味着,抽象类定义了派生类应该具有的功能,但不完全实现这些功能。

抽象类的特点

1.包含至少一个纯虚函数:

  抽象类至少有一个纯虚函数。这是一种特殊的虚函数,在抽象类中没有具体实现,而是留给派生类去实现。

  纯虚函数的声明方式是在函数声明的末尾加上 = 0 。

2.不能直接实例化:

  由于抽象类不完整,所以不能直接创建它的对象。就像你不能直接使用“交通工具”的概念去任何地方,你需要一个具体的交通工具。

3.用于提供基础结构:

  抽象类的主要目的是为派生类提供一个共同的基础结构,确保所有派生类都有一致的接口和行为。

virtual void afterTeaching() = 0;

};

class EnglishTeacher : public Teacher{

public:

void goInClass() override{

cout << "英语老师开始进入教室" << endl;

}

void startTeaching() override{

cout << "英语老师开始教学" << endl;

}

void afterTeaching() override{

};

};

class ProTeacher : public Teacher{ public:

void goInClass() override{

cout << "编程老师开始进入教室" << endl;

}

void startTeaching() override{

cout << "编程老师开始撸代码了,拒绝读PPT" << endl;

}

void afterTeaching() override{

cout << "编程老师下课后手把手教x学员写代码" << endl;

};

};

int main()

{

// Teacher t;//抽象类,不支持被实例化 EnglishTeacher e;

e.goInClass(); ProTeacher t; t.startTeaching(); t.afterTeaching();

//抽象类,多态

Teacher *teacher = new ProTeacher; teacher->startTeaching();

return 0;

}

2.11.3纯虚函数-接口

在 C++ 中,虽然没有像其他编程语言(比如 Java 中的接口Interface)一样直接定义接口的关键字,但可以通过抽象类和纯虚函数的方式来实现接口的概念。

接口通常用于定义类应该实现的方法,但不提供具体实现。这样的实现方式允许多个类共享相同的接口,同时让每个类根据需要去实现这些接口。

一个类作为接口可以通过以下步骤来实现:

1.定义抽象类:创建一个包含纯虚函数的抽象类,这些函数构成了接口的一部分。这些函数在抽象类中只有声明而没有具体的实现。

2.派生类实现接口:派生类继承抽象类,并实现其中的纯虚函数,以具体实现接口定义的方法。以下是一个简单的示例来展示如何使用抽象类来模拟接口:

通过这种方式,您可以在 C++ 中模拟出类似接口的行为,允许多个类共享相同的接口并提供各自的实现。

2.12 友元

2.12.1什么是友元

在C++中,友元(friend)关键字用于给特定的外部函数或类访问某类的私有(private)和保护

(protected)成员的权限。友元关系不是相互的,也不是可继承的。这意味着被声明为友元的函数或类可以访问原始类的私有和保护成员,但原始类不能访问友元的私有成员,除非它们也被声明为友元。

友元主要有三种类型:

1.友元函数:一个普通函数(不是类的成员函数)可以被声明为一个类的友元函数。它可以访问该类的所有成员(公有、保护和私有)。

2.友元类:一个类可以被声明为另一个类的友元。这意味着友元类的所有成员函数都可以访问原始类的保护和私有成员。

3.友元成员函数:一个类的成员函数可以被声明为另一个类的友元。这意味着该成员函数可以访问另一个类的保护和私有成员。

为什么使用友元

友元提供了一种机制,允许某些外部函数或类直接访问类的私有或保护成员。这在某些情况下非常有用,例如:

 实现运算符重载,如重载输入输出运算符( << 和 >> )。  允许两个不同类的对象之间进行密切的交互。

  当类设计需要一种安全控制的方式来允许非成员函数或类访问私有成员时。

2.12.2友元函数

示例

下面是一个展示友元函数的例子:在这个例子中, showValue 函数被声明为 的友元函数,它

可以访问 的私有成员 value 。

友元函数实现运算符重载,如重载输入输出运算符( << 和 >> )。

在C++中,重载输入输出运算符( << 和 >> )通常需要使用友元函数。这是因为输入输出运算符通常需

要访问类的私有成员,同时又需要符合标准库中流对象(如法。

和 std::istream )的用

下面是一个如何使用友元函数来重载

示例

运算符的例子:

假设我们有一个的坐标。

类,表示二维空间中的一个点,我们想要重载

运算符以输出和输入点

在这个例子中:

运算符用于将

对象的内容输出到输出流(如 std::cout )。

运算符用于从输入流(如 std::cin )中读取值到 对象。

 这两个运算符都被声明为

注意事项

类的友元函数,以便它们可以访问类的私有成员变量

和 y 。

虽然友元提供了强大的功能,但也应谨慎使用:

  过度使用友元可能会破坏封装,使得代码难以维护和理解。

  保持友元的数量最小,仅在确实需要时使用,以维持良好的封装性。

2.12.3友元类

在C++中,一个类可以被声明为另一个类的友元类(friend class)。当一个类被声明为另一个类的友元类时,它可以访问后者的所有成员,包括私有(private)和保护(protected)成员。

友元类是一种强大的特性,它允许在保持封装性的同时,提供对类内部的深入访问。然而,由于它允许对另一个类的内部细节进行直接操作,因此应谨慎使用,以免破坏封装性。

示例

假设我们有两个类就可以访问

和 ClassB 。我们可以使的所有成员,包括私有成员。

成为

的友元类,这样

在这个示例中, ClassB 能够访问

注意事项

的私有成员 value ,因为它被声明为

的友元类。

谨慎使用:友元类应该谨慎使用,因为它们可能会破坏对象的封装性和隐藏性。

非相互性:如果

类。友元关系是单向的。

的友元类,这并不意味着

自动成为

的友元

非继承性:友元关系不会被继承。如果类

继承自类 B ,并且

的友元类,那么

不自动

成为

的友元类,除非

明确声明

为友元类。

友元类提供了一种机制,允许其他类访问私有成员,但应在不破坏封装性的前提下谨慎使用。

2.12.4友元成员函数

在C++中,除了可以将整个类或单独的函数声明为友元外,还可以将特定类中的某个成员函数单独声明为另一个类的友元。这样做可以让这个成员函数访问另一个类的所有成员(包括私有和受保护的成员),而无需将整个类声明为友元,从而提供了更细粒度的访问控制。

示例

假设我们有两个类

和 ClassB ,我们希望 ClassB 的一个特定成员函数

能够访问

的私有成员。我们可以将

函数声明为

的友元。

在这个示例中, ClassB::showValue 成为

的友元函数。这意味着

可以访问

的私有成员 value 。注意,我们在

前提供了

的前向声明。这是必须的,因为

在声明

注意事项

还未完全定义。

  精确控制:与将整个类声明为友元相比,将特定成员函数声明为友元可以更精确地控制访问权限。   破坏封装:即使是单个成员函数,过度使用友元也可能破坏类的封装性。应当谨慎使用。

  前向声明:在一个类中声明另一个类的成员函数为友元时,可能需要前向声明另一个类,特别是在两个类相互引用对方的成员函数时。

友元成员函数是C++中一种强大的特性,允许开发者在保持类封装性的同时提供必要的访问权限。然而,应当谨慎使用,以保持代码的清晰性和维护性。

2.13 模板

2.13.1类模板

在 C++ 中,模板(Template)是一种通用的编程工具,允许程序员编写泛型代码,使得类或函数能够适用于多种不同的数据类型而不需要重复编写相似的代码。C++ 提供了两种主要类型的模板:类模板和函数模板。

类模板(Class Templates):

类模板允许定义通用的类,其中某些类型可以作为参数。这样的类可以处理不同类型的数据,而不需要为每个数据类型编写单独的类。

2.13.2函数模板

函数模板允许编写通用的函数,可以处理多种不同类型的数据。

模板提供了一种在编写代码时更具通用性的方法,能够处理不同类型的数据而无需为每种类型编写特定的函数或类。通过使用模板,可以提高代码的重用性和可维护性。

2.13.3模板特化

模板特化(Template Specialization)是 C++ 中模板的一个概念,它允许针对特定的数据类型或特定的模板参数提供定制化的实现。模板特化允许您为模板提供一个特殊的实现,以覆盖或扩展默认的模板行为。

有两种类型的模板特化:完全特化(Full  Specialization)和部分特化(Partial  Specialization)。

完全特化(Full Specialization):

完全特化是对模板中的所有模板参数都进行特化的情况。在完全特化中,模板参数被指定为特定的类型,为特定的类型提供独特的实现。

以下是一个示例,演示了对模板函数的完全特化:

在这个示例中, maximum 是一个模板函数,可以比较不同类型的数据,并返回较大的值。然后,为了特

化针对

类型,我们提供了一个特化版本的

函数,该函数使用

函数来

比较字符串,并返回较大的字符串。

部分特化(Partial Specialization):

部分特化是指对模板中的部分参数进行特化,允许更具体地特化某些模板参数。这通常在模板类中使用。

以下是一个示例,展示了对模板类的部分特化:

在这个示例中, MyTemplate 是一个模板类,然后我们对 进行部分特化,当第二个模板参

数是 类型时,提供了特殊的实现。因此,对于特定的类型组合,我们可以提供自定义的实现。

2.14 标准模板库STL

2.14.1容器

当谈到 C++ 的标准模板库(STL)容器时,通常有一些常见的容器类型。以下是一些常见的 STL 容器及其特点的简要总结:

容器类型 描述 特点

vector 动态数组,支持快速随机访问和尾部插入/删除 支持动态大小调整,适用于需要随机访问和动态增删元素的场景

list 双向链表,支持快速插入/删除操作 插入/删除元素快速,但不支持随机访问元素

容器类型 描述 特点

deque 双端队列,支持在两端快速插入/删除操作 支持在头尾快速插入/删除元素,不同于 vector 在头部操作时效率较低,但随机访问效率比 list 高

stack 后进先出(LIFO)的堆栈数据结构 基于 deque 或 vector 实现,只允许在栈顶进行插入和删除操作

queue 先进先出(FIFO)的队列数据结构 基于 deque 或 list 实现,只允许在队尾进行插入,在队头进行删除操作

priority_queue 优先队列,按照一定顺序维护元素 基于 vector 实现,默认情况下是最大堆,可以通过自定义比较函数来实现不同的优先级顺序

set 有序不重复元素集合,基于红黑树实现 自动排序,插入/查找元素的平均时间复杂度为 O(log n),不允许重复元素

multiset 有序可重复元素集合,基于红黑树实现 允许存储重复元素,按序存储,插入/查找元素的平均时间复杂度为 O(log n)

map 键值对集合,基于红黑树实现 存储键值对,按键自动排序,不允许重复键,插入/查找元素的平均时间复杂度为 O(log n)

multimap 键值对集合,允许重复键,基于红黑树实现 允许存储重复键值对,按键自动排序,插入/查找元素的平均时间复杂度为 O(log n)

unordered_set 无序不重复元素集合,基于哈希表实现 不按顺序存储元素,插入/查找/删除元素的平均时间复杂度为 O(1),不允许重复元素

unordered_multiset 无序可重复元素集合,基于哈希表实现 允许存储重复元素,不按顺序存储,插入/查找/删除元素的平均时间复杂度为 O(1)

unordered_map 无序键值对集合,基于哈希表实现 键值对无序存储,插入/查找/删除元素的平均时间复杂度为 O(1),不允许重复键

unordered_multimap 无序键值对集合,允许重复键,基于哈希表实现 允许存储重复键值对,键值对无序存储,插入/查找/删除元素的平均时间复杂度为 O(1)

2.14.2vector

在C++的标准模板库(STL)中, std::vector 是一种动态数组容器。它提供了动态大小的数组功能,能够在运行时根据需要自动调整大小,允许在其尾部高效地进行元素的插入和删除操作。

1.动态大小:

管理内存。

允许动态增加或减少其大小。它会自动处理内存分配和释放,无需手动

2.随机访问:  支持使用索引进行快速的随机访问,因为它底层基于连续的内存块。

3.尾部插入/删除:  在数组的尾部插入或删除元素的操作非常高效,时间复杂度为常数时间

(Amortized Constant Time)。

4.连续内存存储:

用率。

中的元素在内存中是连续存储的,这有助于提高访问速度和缓存利

5.动态增长策略: 当向 添加元素时,如果当前容量不足,它会动态地重新分配更大的

内存空间,并将现有元素复制到新的内存位置。这种动态增长策略确保了插入操作的高效性。

的缺点是,在执行插入或删除操作时,如果不是在容器的末尾进行,可能会导致较高的时间复杂度,因为需要移动后续元素。

以下是一个使用 C++ STL 中的 容器的简单示例:

#include <iostream> #include <vector>

int main() {

// 创建一个空的 vector 容器

std::vector<int> myVector;

// 向 vector 容器尾部添加元素

myVector.push_back(3); myVector.push_back(7); myVector.push_back(12);

// 使用迭代器遍历 vector 容器并输出其中的元素

// 在 C++ 中,auto 是一个关键字,用于声明变量时的类型推断。

//  它允许编译器根据变量的初始化表达式推断出变量的类型,从而简化代码书写过程。

std::cout << "Vector elements: ";

for (auto it = myVector.begin(); it != myVector.end(); ++it) { std::cout << *it << " ";

}

std::cout << std::endl;

// 获取 vector 容器的大小和访问特定位置的元素

std::cout << "Vector size: " << myVector.size() << std::endl; std::cout << "Element at index 1: " << myVector[1] << std::endl;

// 修改特定位置的元素

myVector[2] = 20;

// 使用范围-based for 循环遍历 vector 并输出元素

std::cout << "Modified Vector elements: "; for (int num : myVector) {

std::cout << num << " ";

}

std::cout << std::endl;

// 清空 vector 容器

myVector.clear();

// 检查 vector 是否为空

if (myVector.empty()) {

std::cout << "Vector is empty." << std::endl;

} else {

std::cout << "Vector is not empty." << std::endl;

}

return 0;

}

这个例子展示了如何创建 容器、向其尾部添加元素、使用迭代器和索引访问元素、修改元素

值、清空容器以及检查容器是否为空。 是一个动态数组,可以根据需要动态地增加或减少其大

小,适合需要随机访问元素且频繁进行尾部插入/删除操作的场景。

当涉及到 的 API 方法时,参数列表、返回值以及参数的说明可以帮助更清楚地了解每个

方法的使用和含义。以下是 常用 API 列表,包括参数列表、返回值和参数说明:

API 描述 函数原型 参数说明

push_back() 在 vector尾部添加一个元素

void push_back(const T& value); value :要添加到尾部的元素

pop_back() 删除 vector 尾部的一个元素

void pop_back();

无参数

size() 返回 vector 中元素的数量

size_type size() const noexcept;

无参数

capacity() 返回 vector 当前可容纳的元素数量

size_type capacity() const noexcept;

无参数

resize() 改变 vector 的大小,可以增加或减少元素数量

void resize(size_type count);

void resize(size_type count, const T& value); count :新的 vector

大小

value :若添加元素,初始化值为 value

reserve() 修改 vector 的容量,预留足够的存储空间

void reserve(size_type new_cap);

new_cap :新的

vector 容量

clear() 清空 vector 中的所有元素

void clear() noexcept;

无参数

empty() 检查 vector 是否为空

bool empty() const noexcept;

无参数

at() 返回指定位置的元素,并进行边界检查 reference at(size_type pos);

const_reference

at(size_type pos) const; pos :要访问的位置。如果超出范围,会引发 std::out_of_range

异常

API 描述 函数原型 参数说明

operator[] 重载操作 符,用于访问指定位置的元素 reference operator[] (size_type pos); const_reference operator[](size_type

pos) const;

pos :要访问的位置。不进行边界检查,如果超出范围,行为未定义

front() 返回 vector 中第一个元素的引用

reference front(); const_reference front() const;

无参数

back() 返回 vector 中最后一个元素的引用

reference back(); const_reference back() const;

无参数

begin() 返回指向 vector 第一个元素的迭代器 iterator begin() noexcept; const_iterator begin()

const noexcept;

无参数

end() 返回指向 vector 末尾(最后一个元素的后面)的迭代器

iterator end() noexcept; const_iterator end() const noexcept;

无参数

rbegin() 返回指向 vector 最后一个元素的逆向迭代器(逆向开始迭代)

reverse_iterator rbegin() noexcept; const_reverse_iterator rbegin() const noexcept;

无参数

rend() 返回指向 vector 第一个元素之前的逆向迭代器(逆向结束迭代)

reverse_iterator rend() noexcept; const_reverse_iterator rend() const noexcept;

无参数

insert()

在指定位置插入一个或多个元素 iterator insert(const_iterator pos, const T& value); void insert(const_iterator pos, size_type count,

const T& value);

pos :插入位置

value :要插入的元素 count :要插入的元素个数

API 描述 函数原型 参数说明

erase()

删除指定位置或指定范围内的一个或多个元素 iterator erase(const_iterator pos);

iterator erase(const_iterator first, const_iterator

last);

pos :要删除的元素位置或范围的起始位置 first 、 last :要删除的范围

swap() 交换两个 vector 容器的内容

void swap(vector& other); other :要交换内容的另一个 vector 容器

emplace() 在指定位置就地构造一个元素 iterator emplace(const_iterator pos, Args&&... args); pos :就地构造的位置 args :构造元素所需的参数

emplace_back() 在 vector尾部就地构造一个元素 void emplace_back(Args&&... args); args :构造元素所需的参数

这些详细说明包括每个函数的参数、返回值以及对参数的解释,有助于更清晰地理解 中常用 API 的使用方式和含义。

2.14.3list

STL 中的 是双向链表(doubly linked list)的实现,它是 C++ 标准模板库中的一个容器,提供了

一种能够高效进行插入、删除操作的数据结构。与

任意位置快速插入和删除元素。

不同, list 不支持随机访问,但它允许在

以下是关于 的一些特点和说明:

 双向链表结构: 使用双向链表来组织其元素,每个节点都包含指向前一个节点和后一

个节点的指针,因此在任意位置进行插入和删除操作的开销较小。

不支持随机访问: 与 不同, list 不支持通过索引直接访问元素,因为它不具备随机访问

能力。要访问 中的元素,需要使用迭代器进行顺序遍历。

动态大小调整:  list 具有动态大小调整的特性,可以动态增加或减少元素的数量。对于大量的插

入和删除操作, list 往往比 更高效。

迭代器操作: 使用迭代器可以对 list 中的元素进行访问、插入和删除。 list 提供了

begin() 、 end() 、 rbegin() 、 rend() 等迭代器相关方法,支持正向和逆向迭代。

插入和删除操作效率高: 在 中,在任意位置进行插入和删除操作的时间复杂度是 O(1),因为

只需要调整相邻节点的指针,无需移动大量元素。

空间开销: 相比于 vector , list 需要额外的空间来存储指向前一个和后一个节点的指针,可能会导致更高的存储开销。

下面是一个简单的示例,演示了如何使用 STL 中的 容器。在这个案例中,我们创建了一个

来存储整数,并展示了一些基本的操作,如插入、删除、迭代等。

这个示例演示了如何创建 容器,并对其进行插入、删除和迭代操作。在实际应用中,

杂的操作。以下是

还有许多其他的功能和方法可以使用,比如 splice() 、 merge() 、 sort() 等,用于更复

常用的 API 方法,包括参数说明,整理成表格形式:

API 描述 函数原型 参数说明

push_back() 在 list 尾部添加一个元素

void push_back(const T& value); value :要添加到尾部的元素

API 描述 函数原型 参数说明

push_front() 在 list 头部添加一个元素

void push_front(const T& value); value :要添加到头部的元素

pop_back() 删除 list 尾部的一个元素

void pop_back(); 无参数

pop_front() 删除 list 头部的一个元素

void pop_front(); 无参数

size() 返回 list 中元素的数量 size_type size() const noexcept; 无参数

empty() 检查 list 是否为空

bool empty() const noexcept; 无参数

clear() 清空 list 中的所有元素

void clear() noexcept; 无参数

begin() 返回指向 list 第一个元素的迭代器 iterator begin() noexcept; const_iterator begin() const

noexcept;

无参数

end() 返回指向

list 末尾

(最后一个元素的后面)的迭代器

iterator end() noexcept; const_iterator end() const noexcept;

无参数

rbegin() 返回指向 list 最后一个元素的逆向迭代器(逆向开始迭代)

reverse_iterator rbegin() noexcept; const_reverse_iterator rbegin() const noexcept;

无参数

rend() 返回指向 list 第一个元素之前的逆向迭代器(逆向结束迭代)

reverse_iterator rend() noexcept; const_reverse_iterator rend() const noexcept;

无参数

insert()

在指定位置插入一个或多个元素

iterator insert(const_iterator pos, const T& value);

void insert(const_iterator pos,

size_type count, const T& value); pos :插入位置

value :要插

入的元素 count :要插入的元素个数

API 描述 函数原型 参数说明

erase()

删除指定位置或指定范围内的一个或多个元素

iterator erase(const_iterator pos);

iterator erase(const_iterator first, const_iterator last); pos :要删除的元素位置或范围的起始位置

first 、 last :要删除的范围

splice()

在指定位置插入另一个 list 中的元素

void splice(const_iterator pos, list& other);

void splice(const_iterator pos,

list& other, const_iterator it);

void splice(const_iterator pos, list& other, const_iterator first, const_iterator last); pos :插入位置

other :要插入的另一个

list

it :要插入的元素

first 、

last :要插入的范围

merge() 合并两个已排序的 list

void merge(list& other); void merge(list&& other); other :要合并的另一个 list

unique()

移除 list 中重复的元素

void unique();

void unique(BinaryPredicate p); p :可选的谓词函数,用于比较元素是否相等

sort() 对 list 进行排序

void sort();

void sort(Compare comp); comp :可选的比较函数,用于元素排序

这些方法使得在 上进行插入、删除、迭代和操作变得方便。每个方法都有不同的参数和作

用,可根据需要选择合适的方法来操作

2.14.4set

容器。

是 C++ 标准模板库中的关联容器,用于存储唯一值的集合。它基于红黑树实现,保持了元素的有序性,且不允许重复的元素存在。

以下是关于

 唯一性:

的一些特点和说明:

中的元素是唯一的,不允许有重复的元素存在。当尝试向

中插入重复的

元素时,新元素将不会被插入。

有序性:

对元素的快速搜索。

红黑树实现:

中的元素是根据元素值进行排序的,这使得元素按照一定顺序存储,并且支持

的底层实现通常是基于红黑树的,这保证了插入、删除和查找操作的时间

复杂度为对数时间(O(log n))。

动态操作: 可以对 进行动态操作,如插入、删除和查找元素。插入和删除操作的性能较

好,不会影响其他元素的位置。

以下是

常用的一些方法:

中插入一个元素。

erase() : 删除 中指定值的元素。

find() : 查找指定值在 中的位置。

size() : 返回 empty() : 检查 clear() : 清空

中元素的数量。是否为空。

中的所有元素。

下面是一个简单的示例,演示了如何使用 std::set :

#include <iostream> #include <set>

int main() {

// 创建一个存储 int 类型值的 set 容器

std::set<int> mySet;

// 向 set 中插入元素

mySet.insert(10); mySet.insert(20); mySet.insert(30);

// 尝试插入重复元素

mySet.insert(20); // 不会插入重复的元素

// 使用迭代器遍历 set 并输出元素

std::cout << "Set elements: ";

for (auto it = mySet.begin(); it != mySet.end(); ++it) { std::cout << *it << " ";

}

std::cout << std::endl;

// 查找特定值在 set 中的位置

int searchValue = 20;

auto found = mySet.find(searchValue); if (found != mySet.end()) {

std::cout << "Found " << searchValue << " in the set." << std::endl;

} else {

std::cout << searchValue << " not found in the set." << std::endl;

}

// 删除特定值的元素

mySet.erase(30);

// 检查 set 是否为空

if (mySet.empty()) {

std::cout << "Set is empty." << std::endl;

} else {

std::cout << "Set is not empty." << std::endl;

这个示例演示了如何创建 容器,并对其进行插入、删除、查找等操作。 std::set 是一个非

常有用的容器,适用于需要存储唯一值的场景。

2.14.5map

是 C++ 标准模板库中的关联容器,用于存储键值对。它基于红黑树实现,保持了元素的有序性,其中每个元素都是一个键值对,键和值之间存在映射关系。

以下是关于

 有序性:

的快速搜索。

的一些特点和说明:

中的元素是根据键值排序的,这使得元素按照一定顺序存储,并且支持对元素

唯一键:

中的键是唯一的,每个键对应一个值。如果尝试使用相同的键向

中插入

值,则会更新键对应的值。

红黑树实现: 的底层实现通常是基于红黑树的,这保证了插入、删除和查找操作的时间

复杂度为对数时间(O(log n))。

动态操作: 可以对 进行动态操作,如插入、删除和查找键值对。插入和删除操作的性能

较好,不会影响其他元素的位置。

以下是

常用的一些方法:

中插入一个键值对。

erase() : 删除 中指定键的键值对。

find() : 查找指定键在 中的位置。

operator[] : 通过键访问对应的值。

size() : 返回 empty() : 检查 clear() : 清空

中键值对的数量。是否为空。

中的所有键值对。

下面是一个简单的示例,演示了如何使用 std::map :

这个示例演示了如何创建 容器,并对其进行插入、删除、查找等操作。 std::map 是一个非

常有用的容器,适用于需要键值对存储和检索的场景。

2.15 异常

2.15.1异常基本

在 C++ 中,异常处理是一种机制,用于处理程序在运行时发生的异常情况。异常是指程序执行期间发生的意外事件,比如除以零、访问无效的内存地址等。通过使用异常处理机制,可以使程序更健壮,并能够处理这些意外情况,避免程序崩溃或产生不可预测的结果。

在 C++ 中,异常处理通常包括以下关键词和概念:

try-catch 块: try 块用于标识可能会引发异常的代码块,而块可以针对不同类型的异常进行处理。

块用于捕获和处理异常。

throw 关键词: throw 用于在程序中显式抛出异常。当发生异常情况时,可以使用一个特定的异常类型。

来抛出

异常类型:异常可以是任何类型的数据,但通常是标准库中的异常类或自定义的异常类。标准库提

供了一些常见的异常类,如 及其派生类,用于表示不同类型的异常情况。

下面是一个简单的示例,演示了异常处理的基本用法:

在这个示例中, divide() 函数尝试对

除以

进行除法运算。如果

为零,就会抛出一个字符串类型的异常。在

函数中调用

函数时,由

的值为零,因此会抛出异常,然后在

块中捕获并处理异常,输出错误消息。

在实际的程序开发中,可以根据具体情况设计和抛出自定义的异常类,以及使用多个同类型的异常,使程序能够更好地处理各种异常情况。

2.15.2自定义异常

块来处理不

在 C++ 中,你可以通过继承标准库的 类或其派生类来自定义异常类。自定义异常类通

常用于表示特定类型的异常情况,并允许你提供有关异常的额外信息。

以下是一个示例,演示了如何创建自定义的异常类:

在这个示例中, MyException 类继承自

类,并重写了基类的

方法,以返回

异常消息。在

中,我们抛出了一个

类型的异常,并在

函数中的

块中捕获并处理该异常。

通过自定义异常类,你可以根据需要添加其他成员变量、方法或构造函数,以便更好地描述和处理特定类型的异常情况。这种方式可以提高程序的可读性和可维护性,并允许你更精确地控制异常处理。

P3 记事本项目

3.1 项目概述

3.1.1功能介绍

  支持文本创建,打开,保存,关闭的功能

 UI样式美化

 添加打开快捷键,添加保存快捷  底部显示行列号及文本字符编码   Ctrl加鼠标滚轮支持字体放大缩小

3.1.2界面预览

3.2.3 工程概述

 MainWindows还是Widget

在Qt中,创建 "MainWindow" 与 "Widget" 项目的主要区别在于他们的用途和功能范围:

1.MainWindow:这是一个包含完整菜单栏、工具栏和状态栏的主窗口应用程序框架。它适合于更复杂的应用程序,需要这些额外的用户界面元素来提供丰富的功能和交互。

2.Widget:这通常是一个简单的窗口,没有内置的菜单栏、工具栏或状态栏。它适合于更简单或专用的应用程序,不需要复杂的用户界面组件。

简而言之,选择"MainWindow"或"Widget"取决于你的应用程序需要多少内置的用户界面元素和复杂性。 MainWindow提供了更全面的框架,而Widget则更适合简单、专注的界面。

 QApplication

在Qt应用程序中, QApplication a(argc, argv); 这行代码的作用是创建一个 类的实例。这是几乎每个Qt应用程序必须做的第一步,因为它负责管理应用程序的许多核心功能。

下表总结了 类在Qt框架中的主要功能和职责:

功能 说明

事件循环 维护事件循环,负责接收和分发各种事件,如鼠标点击、键盘输入等。

全局设置 处理应用程序的全局设置,包括字体、颜色和其他用户界面元素。

GUI的初始化 在没有创建 QApplication 的情况下,无法使用Qt的任何GUI组件,因此它负责初始化GUI环境。

命令行参数处理 可以处理命令行参数,这对于需要命令行交互的应用程序来说是必要的。

是Qt应用程序的核心,它为应用程序提供了必要的环境和框架,确保GUI组件能够正常工作并响应用户的操作。

简而言之, QApplication a(argc, argv); 用于初始化Qt应用程序的环境,设置事件循环,并准备应用程序处理GUI事件。

GUI代表图形用户界面(Graphical User  Interface)。它是一种用户界面,允许用户通过图形图标和视觉指示器(如按钮、标签、窗口等)与电子设备交互,而不是仅仅使用文本命令。GUI使得软件应用程序更加直观和易于使用,因为它提供了视觉导向的操作方式,用户可以通过点击、拖拽和输入来操作界面元素,而不需要记忆和输入复杂的命令。GUI是现代计算机和移动应用程序的标准用户界面类型。

 return a.exec()

在Qt应用程序中, QApplication::exec() 函数是用来启动应用程序的事件循环的。当你调用这个函数时,它会开始处理和分发事件,如用户的点击、键盘输入等。这个函数会一直运行,直到事件循环结

束,通常是因为调用了 函数或者关闭了应用程序的主窗口。简而言之,

是Qt程序中的主循环,负责监听和响应事件,保持应用程序运行直到用户决定退出。

  namespace Ui { class Widget; }

在Qt框架中, namespace Ui { class Widget; } 是一种常见的用法,通常出现在使用Qt Designer设

计GUI时自动生成的代码中。这里的

是一个命名空间,而

是一个前向声明,它声明

了一个名为 的类。这种做法允许你在.cpp 源文件中引用由Qt Designer创建的UI界面,而不需

要在头文件中包含完整的UI类定义。这种分离的方法有助于减少编译依赖性并保持代码的清晰和组织。

在你的源文件中,你会创建一个

 QT_BEGIN_NAMESPACE

类型的对象来访问和操作UI组件。

是Qt框架中用于支持命名空间的宏定义。Qt使用这些宏来确保其库中的类和函数不会与其他库中的同名类和函数冲突。 QT_BEGIN_NAMESPACE 宏在定义Qt类和函数之前使用,用来指定

接下来的代码位于Qt的命名空间中。它通常与 配对使用,后者标志着命名空间的结

束。这种机制对于在大型项目中维护代码的清晰度和防止命名冲突非常重要。

 Q_OBJECT

宏是Qt框架中一个非常重要的宏,用于启用Qt对象的元对象系统。当你在Qt中定义一个类

时,如果这个类继承自 或其子类,并且你想使用Qt的信号和槽机制、国际化、属性系统或其他

Qt元对象系统提供的功能,就必须在类定义中包含 宏。

这个宏允许Qt的元对象编译器(moc)识别并处理这个类,生成额外的代码,这些代码是实现信号和槽机制以及其他元对象功能所必需的。简单地说, Q_OBJECT 宏为Qt类提供了额外的元数据,使得类能够完全利用Qt框架的功能。

  Widget::Widget(QWidget *parent) : QWidget(parent),ui(new Ui::Widget)

代码

是初始化列表,用于调用基类

的构造函数,并将

传递给

它。 ui(new Ui::Widget) 是初始化类内部的

成员变量,这是通过

关键字动态分配的。

是由Qt  Designer工具生成的,用于处理用户界面。这种方式允许将用户界面的设计与后端

逻辑代码分离,有助于提高代码的可维护性和可读性。

3.2 UI设计师工具

3.2.1按键 QPushButton

涉及操作居多,在视频中演示  新建一个QPushButton  属性页面基本使用

  stylesheet初步接触,按键美化操作

 资源文件

  stylesheet使用资源文件

3.2.2水平布局 QHBoxLayout

 新建水平布局

 常用属性和功能特征  建立布局大小

 布局和父控件的关联支持窗口变化  弹簧控件

3.2.3文本编辑器 TextEdit

 新建文本编辑器  属性设置

 TextEdit的常用C++接口

 如何读取TextEdit上的内容   如何往TextEdit上写如内容

3.2.4垂直布局 QVBoxLayout

 同水平布局

3.2.5主窗体元素设计

 图标设计

 应用程序名称设计

3.3 按键响应-初识信号与槽

3.3.1信号与槽基本介绍

提出疑问,界面上已经有按键了,怎么操作才能让用户按下按键后有操作上的反应呢?

在 Qt 中,信号和槽机制是一种非常强大的事件通信机制。这是一个重要的概念,特别是对于初学者来说,理解它对于编写 Qt 程序至关重要。

概要

1.信号 (Signals):是由对象在特定事件发生时发出的消息。例如, QPushButton 有一个信号,当用户点击按钮时发出。

2.槽 (Slots):是用来响应信号的方法。一个槽可以是任何函数,当其关联的信号被发出时,该槽函数将被调用。

3.连接信号和槽:使用会自动执行。

方法将信号连接到槽。当信号发出时,关联的槽函数

3.3.2按键QPushButton设置信号与槽

在 Qt 中,有几种不同的方式来设置按键信号与槽的连接,主要包括:

Qt的信号和槽机制是其事件处理系统的核心。这种机制允许对象之间的通信,而不需要它们知道对方的具体实现。以下是Qt信号和槽的几种常见连接方式的简要概述,我将它们整理成表格形式以便于理解:

连接方式 描述 示例

使用

QObject::connect 最常用的方式,直接通过 QObject::connect 函数连接信号和槽。 QObject::connect(sender, SIGNAL(signal()),

receiver, SLOT(slot()));

使用C++11

Lambda表达式 利用C++11引入的Lambda表达式进行信号与槽的连接。这种方式可以直接在连接点使用匿名函数,使代码更加简洁。

QObject::connect(sender, &Sender::signal, [=]() {

/* lambda body */ });

使用函数指针 Qt 5中引入,允许使用函数指针直接连接信号和槽,这种方式类型安全,且可以利用IDE的代码补全和错误检查。

QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot);

自动连接(使用UI文件) 在使用Qt Designer时,可以通过命名约定自动连接信号和槽。当UI文件加载时,以 on_<objectName>_<signalName> 命名的槽会自动连接到相应的信号。 在Qt Designer中命名按钮为 pushButton ,然后在代码中定义

on_pushButton_clicked() 。

这些方式各有优劣,选择哪种方式取决于具体的应用场景、代码风格以及个人偏好。例如,直接使用 QObject::connect 是最通用的方式,而使用Lambda表达式可以在同一位置编写信号处理逻辑,提高代码的可读性。使用函数指针的方式则在编译时提供更好的类型检查。自动连接通常在使用Qt Designer设计UI时比较方便。

#include "ui_widget.h"

Widget::Widget(QWidget *parent)

: QWidget(parent)

, ui(new Ui::Widget)

{

ui->setupUi(this);

//在构造函数中进行信号与槽的绑定

//第二种方式:QObject::connect(sender, SIGNAL(signal()), receiver,

SLOT(slot()));

QObject::connect(ui->btnCon, SIGNAL(clicked()), this, SLOT(on_btnCon_clickedMyself()));

//第三方式:lambda表达式:QObject::connect(sender, &Sender::signal, [=]() { /* lambda body */ });

QObject::connect(ui->btnLambda, &QPushButton::clicked,[=](){ std::cout << "btnLambdaClicked" << std::endl;

});

//第四种方式:QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot);

QObject::connect(ui-

>btnFortch,&QPushButton::clicked,this,&Widget::on_fortch_clicked);

}

Widget::~Widget()

{

delete ui;

}

//第一种方式:通过uiDesigner

void Widget::on_btnui_clicked()

{

std::cout << "UIBtnClicked" << std::endl;

}

void Widget::on_btnCon_clickedMyself()

{

std::cout << "btnConClicked" << std::endl;

}

void Widget::on_fortch_clicked()

{

std::cout << "btnForthClicked" << std::endl;

}

3.3.3自定义信号与槽

在Qt中,自定义信号与槽是实现对象间通信的一种机制。信号和槽是Qt对象通信的核心特性,使得一个对象能够在发生某种事件时通知其他对象。自定义信号与槽的实现步骤如下:

1.定义信号:在Qt中,信号是由 signals 关键字声明的类成员函数。它们不需要实现,只需声明。例如:

在上面的例子中, MyClass 有一个名为mySignal 的信号,它带有一个整型参数。

定义槽:槽可以是任何普通的成员函数,但通常在类定义中用slots 关键字标识。槽可以有返回类型,也可以接受参数,但它们的参数类型需要与发出信号的参数类型匹配。例如:

在这个例子中,我们定义了一个名为mySlot 的槽,它接收一个整型参数。

连接信号与槽:使用QObject::connect 函数将信号与槽连接起来。当信号被发射时,连接到这个信号的槽将被调用。

这行代码连接了myObject 的mySignal 信号到同一个对象的mySlot 槽。

发射信号:使用emit 关键字发射信号。当信号被发射时,所有连接到这个信号的槽都会被调用。

这将触发所有连接到mySignal 的槽。

自定义信号和槽是Qt编程中非常强大的特性,它们使得组件之间的通信变得灵活而松耦合。通过信和槽,可以方便地实现各种复杂的事件驱动逻//辑。

QDebug()

是 Qt 框架中用于输出调试信息的一个类。它提供了一种方便的方式来输出文本到标准输出(通常是控制台),这对于调试 Qt 应用程序非常有用。 QDebug 类可以与 Qt 的信号和槽机制一起使用,使得在响应各种事件时能够输出有用的调试信息。

使用作符

的一个典型方式是通过来输出各种数据类型。例如:

函数,它返回一个

对象。然后,可以使用流操

当执行这些代码时,它们会在应用程序的控制台输出相应的文本。这对于检查程序的运行状态、变量的值或者跟踪程序的执行流程非常有帮助。

还可以使用  qDebug() 来输出自定义类型,只要为这些类型提供了适当的输出操作符重载。此外,Qt 还

提供了 qInfo() , qWarning() ,

函数,用于输出不同级别的信息,分别用

于普通信息、警告、关键错误和致命错误。这有助于对日志信息进行级别划分,从而更好地控制输出内容。

3.3 文件操作类 QFile

是 Qt 框架中用于文件处理的一个类。它提供了读取和写入文件的功能,支持文本和二进制文

件。

继承自 QIODevice ,因此它可以像其他IO设备一样使用。

主要功能

1.文件读写: QFile 支持打开文件进行读取或写入操作

2.文件信息:可以检索有关文件的信息,如大小、修改日期等。

3.文件操作:提供了对文件进行重命名、移动、删除等操作的能力。

4.错误处理: QFile 在操作文件时提供了错误处理机制,可以通过相应的函数检查和获取错误信息。

常用方法

open() :打开一个文件。需要指定模式(如只读、只写、读写等)。

close() :关闭文件。

和 write() :用于读取和写入数据。

exists() :检查文件是否存在。

remove() :删除文件。

copy() :复制文件。

示例代码

以下是使用 的一个简单例子:

Widget::Widget(QWidget *parent)

: QWidget(parent)

, ui(new Ui::Widget)

{

ui->setupUi(this);

}

Widget::~Widget()

{

delete ui;

}

void Widget::on_btnRead_clicked()

{

//1. 打开文件

//QFile file("D:/QT/test.txt"); QFile file; file.setFileName("D:/QT/test.txt");

if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){ qDebug() << "file open error";

}

//2. 读取文件

char context[100] = {'\0'};

if( file.read(context,100) == -1) return;

//3. 输出文件内容 qDebug() << context; file.close();

}

void Widget::on_btnWrite_clicked()

{

// 1.打开

QFile file("D:/QT/test2.txt"); file.open(QIODevice::WriteOnly | QIODevice::Text);

// 2. 写入

file.write("Program 45-QFile001 write something to This File 我是老陈");

// 3. 关闭

file.close();

}

3.3.3 QTextStream

的主要特性成一个表格。请看下表:

特性类别 说明

字符编码 支持 Unicode,可以处理如 UTF-8、UTF-16 等不同编码。通过 setCodec() 方法设置特定编码。

读写文本 用于读写文件、字符串或任何继承自 QIODevice 的对象。

格式化 提供文本格式化功能,如数字精度、基数(十进制、十六进制等)调整。

流操作符 支持使用 << 和 >> 操作符,类似于 C++ 中的 iostream。

特性类别 说明

换行处理 自动处理不同操作系统间的换行符差异(如 Unix 的 \n 和 Windows 的 \r\n )。

错误处理 能够检测和报告在读写过程中出现的错误。

缓冲机制 提供缓冲机制,提高读写效率。

字符串操作 可以方便地处理和解析字符串数据。

是一个功能强大的类,用于处理文本数据,特别是在需要考虑字符编码和文本格式化的情况下。通过这些特性,它提供了一种灵活而强大的方式来读写和操作文本。

使用示例

以下是一个更详细的示例,展示了如何使用 来读写文件:

3.4 文件选择对话框 QFileDialog

3.4.1QFileDialog开发流程

使用 的基本步骤通常如下:

实例化:首先,创建一个 对象的实例。

设置模式:根据需要设置对话框的模式,如打开文件、保存文件等。

设置过滤器:如果需要,可以设置文件类型过滤器,以限制用户可以选择的文件类型。

显示对话框:通过调用 方法显示对话框,并在用户作出选择后执行相应的操作。

通过

这是使用

方法获取用户选择的文件路径列表,然后对这些文件进行相应的处理。

的基本模式。Qt  也允许使用静态方法直接创建和显示对话框,例如

QFileDialog::getOpenFileName() ,这些方法更简单,但提供的自定义选项较少。

3.4.2QFileDialog 打开开发案例

3.4.3QFileDialog 保存开发案例

3.6 实现文件打开功能

3.6.1开发流程

 为QPushButton对应Open的控件设置槽函数  槽函数代码开发

 打开文件

 读取文件

  把文件数据显示在TextEdit控件上

3.6.2代码实现

3.6.3打开功能优化

 字符编码相关问题解决

在 Qt 中, QTextStream 常用的字符编码主要包括以下几种:

编码名称 描述

UTF-8 用于表示 Unicode 文本的变长字符编码,广泛用于网络和多语言文本

UTF-16 用于表示 Unicode 文本的定长字符编码

ISO 8859-1 也称为 Latin1,用于表示西欧语言字符

GBK 用于表示简体中文字符,是 GB2312 的扩展

Big5 用于表示繁体中文字符,常用于台湾和香港地区

Windows- 1252 用于表示西欧语言字符,是 ISO 8859-1 的超集

ANSI 在 Qt 中,"ANSI" 编码并不是一个明确指定的编码标准,因为 ANSI 编码可以指代不同的编码标准,这取决于操作系统的语言和区域设置。例如,在中文 Windows系统中,ANSI 编码通常指的是 GBK 编码;而在西欧语言的 Windows 系统中, ANSI 编码可能指的是 ISO 8859-1 或 Windows-1252。

这些编码覆盖了大部分常用的语言字符集,可以通过中进行设置。

方法在

 检测光标位置,并在右下角显示光标位置  在程序左上方显示当前打开的文件名称

3.6.4QComboBox

是 Qt 框架中用于创建下拉列表的一个控件。

它允许用户从一组选项中选择一个选项,并可以配置为可编辑,使用户能够在其中输入文本。

提供了一系列方法来添加、删除和修改列表中的项,支持通过索引或文本检索项,并可以通过信号和槽机制来响应用户的选择变化。该

控件广泛应用于需要从多个选项中进行选择的用户界面场景,例如表单和设置界面。

功能 描述 API 方法

添加选项 向下拉列表添加单个或多个选项 addItem() , addItems()

获取选项 获取当前选中的文本或索引 currentText() , currentIndex()

设置选项 设置当前选中的项 setCurrentIndex(int)

移除选项 从下拉列表中移除项 removeItem(int)

信号 当选项改变时触发的事件 currentIndexChanged(int)

可编辑性 设置下拉列表是否可编辑 setEditable(bool)

自定义数据 向下拉列表项关联额外的数据 setItemData(int, const QVariant&)

清空列表 移除所有选项 clear()

示例代码

这个示例展示了

需要调整和扩展这个示例。

的基本用法,包括添加选项、设置为可编辑以及连接信号和槽。您可以根据

3.6.5记事本支持字符编码

获取用户在QComboBox上选择的字符编码,用特定编码打开文件,这里注意QComboBox返回QString类型,

setCodec参数要求const char*型

QString先转成C++的String,再转换成const char *

支持打开文件后进行字符编码的重新选择和显示加载

3.6.6添加行列显示

使用QTextEdit的cursorPositionChanged信号,当光标发生移动时候刷新显示

3.6.7添加文件打开提示

3.6.8设置当前行高亮

实现策略:

 获取当前行的光标位置,使用的信号和获取行列值是一样的  通过ExtraSelection来配置相关属性

 在当前行设置该属性

实现该功能,需要用到一个API,

3.6.8.1QList

在 Qt 框架中, QList 是一个容器类,它在内部实现上类似于一个数组,但也提供了一些链表的特性。的设计旨在提供一个在多数情况下既高效又方便的通用列表容器。用于存储元素列表。它提供了

丰富的功能,包括添加、移除、访问元素等。

QList 的内部工作原理:

1.数组式存储: QList 在大多数情况下使用连续内存存储其元素,类似于数组。这意味着它提供了快速的索引访问(通过下标操作符 [] ),以及相对高效的迭代性能。

2.动态调整大小:与静态数组不同, QList 可以动态增长和缩减,自动管理内存分配。

3.链表特性:虽然 主要基于数组,但它也提供了一些链表的操作,比如在列表的开始或结束

处添加和移除元素。这些操作通常比在数组中间插入或删除元素更高效。

4.复制时共享内存: QList  使用一种称为“隐式共享”(implicit sharing)或“写时复制”(copy-on-

write)的技术。这意味着当你复制一个 时,它不会立即复制所有元素,而是共享相同的数

据,直到你尝试修改其中一个列表,此时才进行实际的复制。这使得复制

使用场景:

变得非常高效。

  当你需要快速的随机访问(如通过索引访问元素)时, QList 是一个不错的选择。  如果你的主要操作是在列表的两端添加或移除元素, QList 也表现得很好。

基本用法

包含头文件:首先,你需要包含 的头文件。

创建 QList 实例:创建一个 对象,并指定存储的元素类型。

添加元素:使用

方法添加元素。

访问元素:可以使用下标操作符或 方法访问元素。

遍历列表:使用迭代器或范围基的 for 循环遍历列表。

移除元素:使用 removeAt 、 removeOne 或 方法移除元素。

3.8.2 ExtraSelection 简介

是一个在

中用来表示额外的文本选择和高亮的结构。

如何工作

1.ExtraSelection 结构体: QTextEdit::ExtraSelection 是一个结构体,包含了两个主要成员:和 QTextCharFormat 。 QTextCursor 表示在文本中的一个位置或者区间,而

用于定义这个区间的格式,比如背景颜色、字体等。

2.设置 ExtraSelection:你可以创建一个或多个 对象,为它们设置相应的光标位

置和格式,然后通过

方法将这些对象应用到文本编辑器中。

这样,你可以对文本的特定部分应用特定的格式,而不影响其他文本。

3.高亮当前行:要高亮显示当前行,你需要在 信号的槽函数中创建一个

对象。使用当前的

对象(通过

方法获取)来确

定当前行的位置,并设置背景颜色为你选择的高亮颜色。

类是 Qt 框架中的一部分,用于描述文本字符的格式。这个类提供了丰富的接口来设置和获取文本字符的各种属性,如字体、颜色、背景色等。 QTextCharFormat 通常用于富文本处理,可

以在像

下面列出了

这样的类中使用

的一些常用功能和方法:

1.设置和获取字体样式:

 使用 方法设置字体。

通过 方法获取当前字体。

2.设置字体属性:

setFontWeight() : 设置字体的粗细。

setFontItalic() : 设置字体是否倾斜。

setFontUnderline() : 设置是否有下划线。

3.设置文本颜色和背景色:

setForeground() : 设置文本的前景色(即字体颜色)。

setBackground() : 设置文本的背景色。

4.其他文本属性:

setToolTip() : 设置文本的工具提示。

setAnchor() : 设置文本是否为超链接。

setAnchorHref() : 设置超链接的目标 URL。

示例代码

下面是一个简单的示例,展示如何在

中使用

来设置特定文本的格式:

3.7 文件保存功能优化

3.7.1开发流程

 判断当下是否有已经打开的文件,如果有打开的文件  读取TextEdit的内容

 写入新文件

3.8 关闭优化

在上节课中关闭部分稍微优化了以下,但是还是不够,  我们应该弹出窗口多一个询问!

3.8.1消息对话框 QMessageBox

是 Qt 框架中用于显示消息框的一个类,它常用于向用户显示信息、询问问题或者报告错

误。以下是 的一些主要用途:

1.显示信息:向用户显示一些信息性的消息。

2.询问用户决策:询问用户一个问题,并根据其回答做出相应的操作。

3.报告错误:向用户报告程序运行中的错误。代码示例

以下是一个简单的 使用示例,展示了如何创建一个基本的消息框:

在这个例子中,我们创建了一个 对象,并设置了窗口标题、主要文本、附加信息文本和

图标。还添加了两个按钮(OK 和 Cancel),并设置了默认按钮。通过

据用户的选择执行不同的操作。

方法显示消息框,并根

由于 是为标准对话框设计的,其定制能力有限,但你可以通过添加自定义按钮来实现一

定程度的定制。例如,如果你想要添加一个自定义的按钮,可以这样做:

在这个例子中,通过 方法添加了一个自定义按钮。按钮的角色被设置为

QMessageBox::ActionRole ,这意味着它将被放置在对话框的底部,与其他标准按钮一起。通过检查用户点击的按钮来确定是否点击了自定义按钮。

3.7.3 代码实现

3.9 实现快捷键功能

3.9.1快捷键开发基础

在 Qt 中实现快捷键功能通常涉及到

Qt 应用程序中为特定功能设置快捷键:

类的使用。下面是一个简单的代码示例,展示了如何在

在这个示例中,当用户按下 Ctrl + N 时,程序将弹出一个消息框。这是通过创建一个 对象,

并将其快捷键序列设置为

来实现的。然后,将

信号连接到一个 Lambda 函数,

该函数在快捷键被激活时执行。这种方法非常适用于为特定操作提供快速访问路径。

3.9.2上官记事本添加快捷键

3.10 实现字体放大缩小功能

3.10.1滚动调节字体大小的流程

 为TextEdit添加事件过滤器   重写窗口的eventFilter函数

 eventFilter设置滚轮事件和目标对象  实现字体放大缩小的功能函数

3.10.2本节笔记失误

啥也没有,嘿嘿嘿

3.10.3检测Ctrl键被按下

(如 Ctrl、Shift、Alt 等)。当与了 Control 键。

是 Qt 中一个静态函数,用于返回当前按下的键盘修饰符结合使用时,这个函数可以用来检测是否按下

例如,以下代码片段检查 Control 键是否被按下:

这里, QGuiApplication::keyboardModifiers() 返回当前按下的修饰符,而

前按下的修饰符中。

是一个枚举值,表示 Control 键。使用位与运算符

检查 Control 键是否在当

3.10.4记事本添加字体放大缩小

3.10.5事件

事件处理过程

众所周知Qt是一个基于C++的框架,主要用来开发带窗口的应用程序(不带窗口的也行,但不是主流)。我们使用的基于窗口的应用程序都是基于事件,其目的主要是用来实现回调(因为只有这样程序的效率才是最高的)。所以在Qt框架内部为我们提供了一些列的事件处理机制,当窗口事件产生之后,事件会经过: 事件派发 -> 事件过滤->事件分发->事件处理 几个阶段。Qt窗口中对于产生的一系列事件都有默认的处理动作,如果我们有特殊需求就需要在合适的阶段重写事件的处理动作,比如信号与槽就是一种

事件(event)是由系统或者 Qt 本身在不同的场景下发出的。当用户按下/移动鼠标、敲下键盘,或者是窗口关闭/大小发生变化/隐藏或显示都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如鼠标/键盘事件等;另一些事件则是由系统自动发出,如计时器事件。

每一个Qt应用程序都对应一个唯一的 QApplication 应用程序对象,然后调用这个对象的exec() 函数,这样Qt框架内部的事件检测就开始了( 程序将进入事件循环来监听应用程序的事件 )。

事件在Qt中产生之后,的分发过程是这样的:

1.当事件产生之后,Qt使用用应用程序对象调用 notify() 函数将事件发送到指定的窗口:

2.事件在发送过程中可以通过事件过滤器进行过滤,默认不对任何产生的事件进行过滤。

3.当事件发送到指定窗口之后,窗口的事件分发器会对收到的事件进行分类:

4.事件分发器会将分类之后的事件(鼠标事件、键盘事件、绘图事件。。。)分发给对应的事件处理器函数进行处理,每个事件处理器函数都有默认的处理动作(我们也可以重写这些事件处理器函 数),比如:鼠标事件:

重写事件案例

程序关闭之前的询问,鼠标进入,鼠标离开,窗口大小改变

#include "widget.h" #include "ui_widget.h" #include <QDebug> #include <QMessageBox> #include <QWheelEvent>

Widget::Widget(QWidget *parent)

: QWidget(parent)

, ui(new Ui::Widget)

{

ui->setupUi(this);

}

Widget::~Widget()

{

delete ui;

}

void Widget::enterEvent(QEvent *event)

{

qDebug() << "mouse enter";

}

void Widget::leaveEvent(QEvent *event)

{

qDebug() << "mouse leave";

}

void Widget::wheelEvent(QWheelEvent *event)

{

qDebug() << event->angleDelta();

}

void Widget::closeEvent(QCloseEvent *event)

{

int ret = QMessageBox::warning(this, tr("My Application"),

tr("close the window\n"

"Do you want to close the window?"), QMessageBox::Ok | QMessageBox::No

);

switch(ret){

case QMessageBox::Ok: event->accept(); break;

case QMessageBox::No: event->ignore(); break;

}

}

void Widget::resizeEvent(QResizeEvent *event)

{

qDebug() << "oldSize:" << event->oldSize()

<< "newSize:" << event->size();

自定义按键

mybutton.h

mybutton.cpp

widget.cpp

事件方式实现字体放大缩小

自定义控件MyTextEdit头文件

实现文件

事件过滤器

我们通过继承QTextEdit来重写事件实现Ctrl加滚轮的检测,还有一种处理方式,叫做事件过滤器

在Qt的事件处理过程中,引入事件过滤器(Event Filter)可以让你在事件达到目标对象之前进行拦截和处理。这是一种强大的机制,允许你在不同对象间共享事件处理逻辑或在父对象中集中处理特定事件。下面是加入事件过滤器的步骤:

1.定义事件过滤器: 事件过滤器通常是一个重写了QObject::eventFilter() 方法的对象。这个方法会在事件传递给目标对象之前被调用。

2.安装事件过滤器: 使用QObject::installEventFilter() 方法安装事件过滤器。这个方法告诉Qt在将事件发送给特定对象之前先通过过滤器对象。例如,如果你想在父窗口中过滤子窗口的事件,你需要在父窗口的对象上调用installEventFilter() ,并将子窗口作为参数传递。

3.事件过滤器逻辑: 在eventFilter() 方法内部,你可以编写自定义逻辑来决定如何处理或忽略事件。如果此方法返回true ,则表示事件已被处理,不应该继续传递;如果返回false ,则事件将正常传递给目标对象。

4.事件分发:  当事件发生时,Qt首先将事件发送到安装了事件过滤器的对象。在这一步,

eventFilter() 方法被调用。

5.决定是否传递事件: 根据eventFilter() 方法的返回值,Qt决定是否继续向目标对象传递事件。如果过滤器返回true ,事件处理到此结束;如果返回false ,事件继续传递到原始目标对象。

6.目标对象处理事件: 如果事件过滤器允许事件继续传递,目标对象将像没有事件过滤器存在时那样处理事件。

事件过滤器特别适用于以下情况:

 当你想在不修改子类代码的情况下改变事件的行为。  当多个对象需要共享相同的事件处理逻辑。

  当你需要在更高的层级上监控或修改应用程序的事件流。

通过使用事件过滤器,Qt应用程序可以获得更大的灵活性和更细粒度的事件处理控制。

3.10.6鼠标滚轮和字体大小

在 C++ 中,强制类型转换(或类型转换)是一种将变量从一种类型转换为另一种类型的方法。C++ 提供了四种强制转换运算符,每种都有其特定的用途和适用场景:

1.static_cast

是最常用的类型转换运算符,用于无风险的转换,如整数到浮点数,字符到整

数等。

它在编译时执行,不执行运行时类型检查(RTTI)。

 示例: int x = static_cast<int>(y); 其中 可能是 float 类型。

2.dynamic_cast

专门用于处理对象的多态性,只能用于指针和引用,且涉及对象类必须有虚函数。

它在运行时检查类型的安全性,如果转换失败,对于指针类型返回 nullptr ,对于引用类型抛出异常。

 示例: Derived *dp = dynamic_cast<Derived *>(bp); 其中是派生类。

3.const_cast

 用于修改类型的 const 或 volatile 属性。

是基类指针, Derived

 通常用于去除对象的 const 性质,允许修改原本被声明为 const 的变量。  示例: const int a = 10; int* b = const_cast<int*>(&a);

4.reinterpret_cast

  用于进行低级别的重新解释转换,几乎无限制,但也是最危险的。

  它可以将一种完全不相关的类型转换为另一种类型,比如将指针类型转换为整数类型。

示例: long p = reinterpret_cast<long>(&object); 其中 是某个类的对象。

3.12 记事本项目总结

类别 功能 描述

UI设计师基本控件操作 Widget 基础的用户界面单元,用于构建复杂的用户界面。

QPushButton 用于创建按钮。

QHBoxLayout 水平布局管理器,用于水平排列控件。

QVBoxLayout 垂直布局管理器,用于垂直排列控件。

TextEdit 多行文本编辑器控件。

Stylesheet 使用样式表来定制控件的外观。

文件操作类 QFile 用于读取和写入文件。

文件选择对话框类 QFileDialog 提供了一个对话框,允许用户选择文件或目录。

QT的信号与槽 - 用于对象之间的通信机制。

消息对话框 QMessageBox 用于显示信息、警告、错误等对话框。

快捷键捕获和处理 - 用于捕获和处理键盘快捷键。

Ctrl按键信号捕获和处理 - 专门处理Ctrl按键的信号。

鼠标滚轮信号捕获和处理 - 用于捕获和处理鼠标滚轮动作。

事件处理 event 用于处理不同的事件。

文本字符编码检测 - 用于检测和处理文本的字符编码。

类别 功能 描述

字体放大缩小 - 用于调整字体大小。

QT程序开发流程 - 涉及从设计到部署的整个开发流程。

P4 串口调试助手项目

4.1 项目概述

项目功能描述

见下方界面,所见即所得!

4.2 串口通信核心代码开发

代码会放在网盘上

全程高能力输出-代码开发和调试都在视频里

P5 网络调试助手

5.1 TCP网络调试助手

5.1.1项目概述

 网络相关的一些基础概念-面试用  学习QTcpServer

 学习QTcpClient

 学习TextEdit特定位置输入文字颜色  学习网络通信相关知识点

 复习巩固之前UI控件  程序运行如下图所示

5.1.2开发流程

5.1.3QTtcp服务器的关键流程

工程建立,需要在.pro加入网络权限

创建一个基于

1.创建并初始化

的服务端涉及以下关键步骤:

实例:

实例化 QTcpServer 。

 调用

2.处理新连接:  为

方法在特定端口监听传入的连接。

信号连接一个槽函数。

 在槽函数中,使用

3.读取和发送数据:

获取

以与客户端通信。

 通过连接  使用

4.关闭连接:

方法发送数据回客户端。

信号来读取来自客户端的数据。

 在适当的时候关闭 QTcpSocket 。示例代码可能如下:

确保在使用

时妥善处理网络错误和异常情况。

5.1.4QTtcp客户端的关键流程

工程建立,需要在.pro加入网络权限

创建一个基于

1.创建

的Qt客户端涉及以下步骤:

实例:

 实例化 QTcpSocket 。

2.连接到服务器:

 使用

3.发送数据到服务器:

方法连接到服务器的IP地址和端口。

使用 方法发送数据。

4.接收来自服务器的数据:

 为

5.关闭连接:

信号连接一个槽函数来接收数据。

 关闭示例代码如下:

连接。

这个客户端尝试连接到指定的服务器地址和端口,然后等待和处理来自服务器的数据。记得根据需要管理和处理网络错误和异常情况。

5.1.2 TCP协议

以下内容自省阅读和消化,主要在面试之前类似八股文问答,实际编程我们不需要关系这么多,

QTcpSocket类底下的API已经做好所有的封装。

TCP(传输控制协议)是一种广泛使用的网络通信协议,设计用于在网络中的计算机之间可靠地传输数据。它是互联网协议套件的核心部分,通常与IP(互联网协议)一起使用,合称为TCP/IP。以下是TCP协议的一些基本特点:

1.面向连接:在数据传输之前,TCP 需要在发送方和接收方之间建立一个连接。这包括三次握手过程,确保两端都准备好进行数据传输。

2.可靠传输:TCP 提供可靠的数据传输服务,这意味着它保证数据包准确无误地到达目的地。如果发生数据丢失或错误,TCP 会重新发送数据包。

3.顺序控制:TCP 保证数据包的传输顺序。即使数据包在网络中的传输顺序被打乱,接收方也能按照正确的顺序重组这些数据。

4.流量控制:TCP 使用窗口机制来控制发送方的数据传输速率,以防止网络过载。这有助于防止接收方被发送方发送的数据所淹没。

5.拥塞控制:TCP 还包括拥塞控制机制,用来检测并防止网络拥塞。当网络拥塞发生时,TCP 会减少其数据传输速率。

6.数据分段:大块的数据在发送前会被分割成更小的段,以便于传输。这些段会被独立发送并在接收端重新组装。

7.确认和重传:接收方对成功接收的数据包发送确认(ACK)信号。如果发送方没有收到确认,它会重传丢失的数据包。

8.终止连接:数据传输完成后,TCP 连接需要被正常关闭,这通常涉及到四次挥手过程。

TCP 适用于需要高可靠性的应用,如网页浏览、文件传输、电子邮件等。然而,由于它的这些特性,TCP在处理速度上可能不如其他协议(如UDP)那么快速。

TCP协议中的三次握手和四次挥手是建立和终止连接的重要过程。下面是它们的简要描述:

三次握手(建立连接)

三次握手的主要目的是在两台设备之间建立一个可靠的连接。它包括以下步骤:

1.SYN:客户端向服务器发送一个SYN(同步序列编号)报文来开始一个新的连接。此时,客户端进入SYN-SENT状态。

2.SYN-ACK:服务器接收到SYN报文后,回复一个SYN-ACK(同步和确认)报文。此时服务器进入

SYN-RECEIVED状态。

3.ACK:客户端接收到SYN-ACK后,发送一个ACK(确认)报文作为回应,并进入ESTABLISHED(已建立)状态。服务器在收到这个ACK报文后,也进入ESTABLISHED状态。这标志着连接已经建立。

四次挥手(断开连接)

四次挥手的目的是终止已经建立的连接。这个过程包括以下步骤:

1.FIN:当通信的一方完成数据发送任务后,它会发送一个FIN(结束)报文来关闭连接。发送完FIN

报文后,该方进入FIN-WAIT-1状态。

2.ACK:另一方接收到FIN报文后,发送一个ACK报文作为回应,并进入CLOSE-WAIT状态。发送FIN

报文的一方在收到ACK后,进入FIN-WAIT-2状态。

3.FIN:在等待一段时间并完成所有数据的发送后,CLOSE-WAIT状态的一方也发送一个FIN报文来请求关闭连接。

4.ACK:最初发送FIN报文的一方在收到这个FIN报文后,发送一个ACK报文作为最后的确认,并进入

TIME-WAIT状态。经过一段时间后,确保对方接收到了最后的ACK报文,该方最终关闭连接。

在这两个过程中,三次握手主要确保双方都准备好进行通信,而四次挥手则确保双方都已经完成通信并同意关闭连接。

5.1.4 Socket

Socket 不是一个协议,而是一种编程接口(API)或机制,用于在网络中实现通信。Socket 通常在应用层和传输层之间提供一个端点,使得应用程序可以通过网络发送和接收数据。它支持多种协议,主要是 TCP 和 UDP。

以下是 Socket 的一些基本特点:

  类型:有两种主要类型的 Sockets —— TCP Socket(面向连接,可靠)和 UDP Socket(无连接,不可靠)。

  应用:在各种网络应用中广泛使用,如网页服务器、聊天应用、在线游戏等。

  编程语言支持:大多数现代编程语言如 Python, Java, C++, 等都提供 Socket 编程的支持。

  功能:提供了创建网络连接、监听传入的连接、发送和接收数据等功能。

 QT: 在QT组件中,QTcpSocket用来管理和实现TCP Socket通信,QUdpSocket用来管理和实现 UDP Socket通信

总之,Socket 是实现网络通信的基础工具之一,它抽象化了网络层的复杂性,为开发者提供了一种相对简单的方式来建立和管理网络连接。

5.2 UI设计

UI设计过程,教学视频展示,都是大家熟悉的内容了

5.3 网络通信核心代码

是 Qt 网络模块的一部分,用于构建 TCP 服务器。它提供了一种机制来异步监听来自客户端的连接。一旦接受了一个连接,服务器就可以与客户端进行数据交换。

5.3.1创建TCP服务端的核心代码

主要步骤如下:

1.创建 实例:启动服务器并开始监听指定端口。

2.监听连接请求:调用 方法使服务器监听特定的 IP 地址和端口。

3.接受连接:当客户端尝试连接时, QTcpServer 产生一个信号。你需要实现一个槽(slot)来响应这个信号,并接受连接。

4.处理客户端连接:每个连接的客户端都关联一个示例代码

对象,用于数据交换。

代码解释

1.创建

2.监听端口:使用

对象:在主函数中,直接创建了一个

方法监听所有接口上的 12345 端口。

对象。

3.处理新连接:通过连接 信号,当有新客户端连接时,会调用相应的槽函数。

4.读取数据:为每个连接的客户端创建

5.发送数据:向客户端发送响应消息。

对象,并连接

信号以接收数据。

6.客户端断开连接时的处理:使用

这个代码示例展示了如何使用

信号确保客户端在断开连接时被适当地清理。

创建一个基本的 TCP 服务器,而无需通过继承来扩展类。这

种方式通常更简单,适用于不需要复杂处理的基本应用场景。

5.3.2创建TCP客户端的核心代码

为了使客户端代码更具模块化和响应性,可以使用 Qt 的信号与槽机制。这种方法允许客户端以事件驱动的方式响应网络事件,如连接建立、数据接收等。下面是一个使用信号与槽的 TCP 客户端示例。

示例代码

代码解释

1.创建

类:这个类继承自 QObject ,允许使用信号与槽机制。

2.连接信号和槽:在构造函数中,将

信号分别连接到

3.连接到服务器:使用

槽。

方法开始连接过程。

4.处理连接建立:一旦连接建立, onConnected 槽被触发,客户端向服务器发送一条消息。

5.接收数据:当数据可读时, onReadyRead 槽被触发,客户端读取并打印来自服务器的数据。

6.断开连接:在接收数据后,客户端断开与服务器的连接。

这个客户端示例展示了如何使用 Qt 的信号与槽机制来处理 TCP 连接。这种方式使得代码更加清晰,易于维护,并且能更好地处理异步事件。

5.4 TCP服务端项目开发

核心代码

for (const QNetworkInterface &interface : interfaces) {

for (const QNetworkAddressEntry &entry : interface.addressEntries()) { if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {

ui->comboBoxIpAddr->addItem(entry.ip().toString());

}

}

}

}

// 主窗口析构函数

MainWindow::~MainWindow()

{

// 释放用户界面资源

delete ui;

}

// “开始监听”按钮的点击事件处理函数

void MainWindow::on_pushButtonListen_clicked()

{

// 侦听指定 IP 地址和端口

tcpServer->listen(QHostAddress(ui->comboBoxIpAddr->currentText()), ui->lineEditPort->text().toInt());

// 更新按钮状态

ui->pushButtonListen->setEnabled(false); ui->pushButtonListenStop->setEnabled(true);

}

// 新 TCP 连接的处理函数

void MainWindow::mnewConnectionHandler()

{

// 获取下一个待处理的连接

QTcpSocket *tmpSocket = tcpServer->nextPendingConnection();

// 向文本浏览器中添加客户端信息

ui->textBrowserRev->append("服务器: 客户端IP地址是:"+ tmpSocket-

>peerAddress().toString()

+" 客户端端口号是: "+QString::number(tmpSocket-

>peerPort())+"\n");

// 连接套接字的状态变化和数据接收信号到相应槽函数

connect(tmpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(mstateChanged(QAbstractSocket::SocketState)));

connect(tmpSocket, SIGNAL(readyRead()), this, SLOT(mreadData()));

}

// 套接字状态改变时的槽函数

void MainWindow::mstateChanged(QAbstractSocket::SocketState state)

{

// 获取发送信号的套接字对象

QTcpSocket *tmp = (QTcpSocket *)sender();

// 根据套接字的不同状态进行不同处理

switch(state){

case QAbstractSocket::UnconnectedState:

// 客户端断开连接

ui->textBrowserRev->append("服务器:有客户端断开连接!"); tmp->deleteLater();

break;

case QAbstractSocket::ConnectedState:

// 客户端连接

ui->textBrowserRev->append("服务器:有新客户端接入!"); break;

default:

break;

}

}

// “停止监听”按钮的点击事件处理函数

void MainWindow::on_pushButtonListenStop_clicked()

{

// 更新按钮状态

ui->pushButtonListen->setEnabled(true);

ui->pushButtonListenStop->setEnabled(true);

// 停止监听端口

tcpServer->close();

}

// 接收到数据时的槽函数

void MainWindow::mreadData()

{

// 获取发送信号的套接字对象

QTcpSocket *tmp = (QTcpSocket *)sender(); setTextColor(0,0,0); // 设置文本颜色为红色 cursor.insertText("客户端:"+ tmp->readAll()+"\n");

}

// “发送”按钮的点击事件处理函数

void MainWindow::on_pushButtonSend_clicked()

{

// 查找所有的子 QTcpSocket 对象

QList<QTcpSocket*> socketList = tcpServer->findChildren<QTcpSocket*>();

// 向每个连接的客户端发送数据

foreach(QTcpSocket *tmp, socketList){

tmp->write(ui->textEditSnd->toPlainText().toUtf8()); setTextColor(255,0,0); // 设置文本颜色为红色 cursor.insertText("服务端:"+ui->textEditSnd-

>toPlainText().toUtf8()+"\n");

};

}

// 设置文本颜色的函数

void MainWindow::setTextColor(int r, int g, int b)

{

QTextCharFormat textFormat;

textFormat.setForeground(QBrush(QColor(r, g, b))); // 根据提供的 RGB 值设置颜色

// 应用格式到光标

cursor.setCharFormat(textFormat);

}

5.5 TCP客户端项目开发

核心代码

#include "mainwindow.h" #include "ui_mainwindow.h"

// 主窗口的构造函数

MainWindow::MainWindow(QWidget *parent)

: QMainWindow(parent)

, ui(new Ui::MainWindow)

{

ui->setupUi(this); // 设置 UI

ui->centralwidget->setLayout(ui->verticalLayoutGlobal); // 设置中央小部件的布局

this->setWindowTitle("网络调试助手客户端-上官QT案例"); // 设置窗口标题 ui->pushButtonDiscon->setEnabled(false); // 初始时禁用断开连接按钮 cursor = ui->textBrowser->textCursor(); // 获取文本浏览器的文本光标 tcpSocket = new QTcpSocket(this); // 创建一个新的 QTcpSocket

connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(mreadDataFromServer()));

// 连接信号与槽

}

// 析构函数

MainWindow::~MainWindow()

{

delete ui; // 删除 UI

}

// 当点击发送按钮时调用

void MainWindow::on_pushButtonSend_clicked()

{

tcpSocket->write(ui->textEditSend->toPlainText().toUtf8()); // 将文本编辑器中的文本发送到服务器

setTextColor(255,0,0); // 设置文本颜色为红色

cursor.insertText("客户端:  "+ui->textEditSend->toPlainText().toUtf8()+"\n");

// 在文本浏览器中插入红色文本

}

// 从服务器读取数据

void MainWindow::mreadDataFromServer()

{

setTextColor(0,0,0); // 设置文本颜色为黑色

cursor.insertText("服务端: "+tcpSocket->readAll()+"\n"); // 在文本浏览器中插入黑色文本

}

// 当点击断开连接按钮时调用

void MainWindow::on_pushButtonDiscon_clicked()

{

tcpSocket->close(); // 关闭套接字连接

// 更新 UI 状态

5.6 项目总结

 TCPServer类关于监听,连接,发送,接受的API  TCPServer在网络通信中常用的信号

 TCPScoket在QT实现Socket常用的API  TCPScoket在QT实现Socket常用的信号

  EditText的内容读取方法,内容写入方法,在特定行写入特点颜色的方法

TCPServer 、 TCPScoket 和 的信息整合到一个表格中:

类别 功能 API/方法 描述

TCPServer 监听 bool listen(const QHostAddress &address, quint16 port) 在指定的 IP 地址和端口上开始监听传入的连接。

连接

void close() 停止服务器监听传入的连接。

类别 功能 API/方法 描述

QTcpSocket

*nextPendingConnection() 返回下一个待处理连接的

QTcpSocket 指针。

TCPServer

信号

newConnection() 当有新连接时发出。

TCPScoket 连接 void connectToHost(const QString &host, quint16 port) 连接到指定的主机和端口。

发送 qint64 write(const QByteArray &data) 向连接的套接字写入数据。

接收 QByteArray readAll() 读取可用的所有数据。

断开 void disconnectFromHost() 断开与主机的连接。

TCPScoket

信号

connected() 成功连接到主机时发出。

disconnected() 从主机断开连接时发出。

readyRead() 当有可读数据时发出。

bytesWritten(qint64 bytes) 成功写入数据时发出。

EditText 读取内容

String getText() 获取 EditText 的内容。

写入内容

void setText(String text) 设置 EditText 的内容。

使用光标改变文本颜色

void changeTextColor(int start, int end, int color) 使用光标( cursor )改变 EditText 中从 start 位置到 end 位置的文本颜色为 color 。

P6 自定义控件

6.1  QPaintEvent绘图事件

是 Qt 框架中一个重要的事件类,专门用于处理绘图事件。当 Qt 视图组件需要重绘自己

的一部分时,就会产生 事件。这通常发生在以下几种情况:

1.窗口第一次显示时:当窗口或控件第一次出现在屏幕上时,系统会生成一个通知窗口进行自身的绘制。

事件,

2.窗口大小改变时:当用户改变窗口的大小时,窗口的内容通常需要重新绘制以适应新的尺寸。

3.窗口部分被遮挡后又重新显示时:如果窗口被其他窗口遮挡,然后又重新露出来,被遮挡的部分通常需要重新绘制。

4.手动请求重绘:通过调用

在 Qt 应用程序中,通常通过重写例如:

方法,可以手动触发重绘事件。方法来处理绘制逻辑。

方法中,您可以创建一个

对象并使用它来执行绘制操作。 QPainter 可以绘

制各种基本图形,如线条、矩形、椭圆等,还可以绘制文本和图像。重写

自定义绘制的标准做法。

是在 Qt 中进行

6.2 QPainter画家

6.2.1概述

是 Qt 库中用于在屏幕上进行绘画的类。它提供了各种绘制功能,比如画线、画图形、画文本

等。

以下是一些基本的用法示例:

1. 初始化 QPainter:首先,您需要一个 QPaintDevice ,比如一个

或 QPixmap ,然后使

用它来初始化 对象。

设置画笔和画刷:您可以设置画笔(用于描边)和画刷(用于填充)的颜色、样式等。

绘制图形:使用 的方法来绘制线条、矩形、圆形、文本等。

结束绘制:完成绘制后, QPainter 对象会在其析构函数中自动结束绘制。

请注意, QPainter 的使用依赖于 Qt 的事件循环,因此通常在

或者类似的事

件处理函数中使用它。如果您在 Qt 应用程序中使用 QPainter ,请确保您遵循 Qt 的事件驱动机制。

6.2.2渐变色

6.2.2.1线性渐变

是 Qt 框架中用于创建线性渐变的类。线性渐变是一种从一个颜色平滑过渡到另一个颜色的效果,其变化沿着两个点之间的直线进行。这种渐变在图形用户界面设计中非常常见,用于添加深度、立体感或动态效果。

基本用法

要使用  QLinearGradient ,你需要执行以下几个基本步骤:

1.创建 对象:指定渐变的起点和终点坐标。

2.设置颜色停靠点:在渐变线上定义颜色和相应的位置。

3.使用渐变创建 QBrush :用中进行绘制。

示例代码

以下是一个创建和使用

对象来创建一个 QBrush ,然后用它在

的示例代码:

在这个例子中, QLinearGradient 创建了一个从红色到蓝色的渐变,其方向是从小部件的左上角 (0, 0)

到右下角 (100, 100)。

注意事项

的颜色变化是沿着两个指定点之间的直线进行的。通过改变这些点的位置,你可以控制渐变的方向和长度。

方法的第一个参数是一个介于 0.0 和 1.0 之间的浮点数,表示颜色在渐变线上的位置。0.0 通常对应于起点,1.0 对应于终点。

你可以设置多个颜色停靠点来创建更复杂的渐变效果。例如,你可以在 0.0 处设置一种颜色,在 0.5

处设置另一种颜色,在 1.0 处再设置一种颜色。

使用

创建的

可以用于填充任何形状,包括矩形、椭圆、多边形等。

为了获取更好的视觉效果,可以启用 的抗锯齿选项( QPainter::Antialiasing )。

  请注意,当窗口小部件的大小发生变化时,渐变的效果可能也会随之改变,除非你相应地调整渐变的起点和终点坐标或使用其他方法来适应大小变化。

6.2.2.2径向渐变

是 Qt 框架中用于创建径向渐变的类。径向渐变是一种从中心点向外部辐射的颜色渐变,通常在中心点有一种颜色,而向外围渐渐变化为另一种颜色。这种渐变非常适合用于模拟光源、阴影或创建圆形的立体感。

基本用法

要使用  QRadialGradient ,你需要执行以下几个基本步骤:

1.创建 对象:指定渐变的中心点、半径以及焦点(可选)。

2.设置颜色停靠点:在径向渐变中定义颜色和对应的位置。

3.使用渐变创建 QBrush :利用 对象创建一个 QBrush ,然后用

它在

示例代码

中进行绘制。

以下是一个创建和使用 的示例代码:

在这个例子中, QRadialGradient 创建了一个从中心的黄色向外围的黑色渐变。渐变的中心和半径都设置在 (50, 50, 50)。

注意事项

方法的第一个参数是一个介于 0.0 和 1.0 之间的浮点数,表示颜色在径向渐变中的位置。0.0 通常对应于中心点,1.0 对应于边缘。

通过添加多个颜色停靠点,你可以创建更复杂的径向渐变效果。

方法允许你设置焦点位置,这是渐变颜色开始变化的点,可以与中心点不同。

使用

创建的

可以用于

填充任何形状,如矩形、椭圆、多边形等。  为了获得更好的视觉效果,可以启用

的抗锯齿选项( QPainter::Antialiasing )。

当绘制较大区域时,可以通过调整渐变的半径和中心点来控制渐变效果的扩展。

非常适用于创建像按钮、指示灯或其他需要有深度感和立体感的界面元素。

6.2.2.3圆锥形渐变

是 Qt 框架中用于创建圆锥形渐变的类。圆锥渐变是一种渐变效果,其中颜色沿着圆锥的轮廓变化,类似于旋转颜色轮。这种渐变以其中心点为基点,颜色沿圆周分布,可以创建出富有动感的视觉效果。

基本用法

要使用  QConicalGradient ,你通常需要做以下几个步骤:

1.创建 对象:指定渐变的中心点和起始角度。

2.设置颜色停靠点:为渐变添加不同的颜色和对应的位置(角度)。

3.使用渐变创建 QBrush :使用这个渐变对象来创建一个 QBrush ,然后应用到图。

示例代码

中进行绘

下面是一个如何创建和使用 的简单示例:

在这个例子中, QConicalGradient 被用来创建一个从红色到蓝色再回到红色的渐变。渐变的中心设置在点 (100, 100),并且从 0 度开始旋转。

注意事项

的颜色是沿着圆周分布的,其中

你可以通过添加多个颜色停靠点来创建更复杂的渐变效果。

在圆周上是相同的位置。

在使用时,角度是按照顺时针方向测量的,起始点(0度)通常在三点钟方向。

为了达到最佳的渲染效果,可以启用 的抗锯齿渲染提示

非常适合用于创建旋转或动态效果的图形,例如加载指示器、进度条或任何需要圆周颜色变化的场景。

6.3 坐标转移

在 Qt 框架中, painter.translate(rect().center()) 这行代码的作用是移动 QPainter 的坐标系统

原点到当前绘制区域(即由

解释一下各个部分:

返回的矩形)的中心。

1.painter : 这是一个 QPainter 对象实例,用于在 Qt 窗口或者图像上进行绘制。

2.translate() : 这是 QPainter 类中的一个方法,用于改变坐标系统的原点。它接受一个 QPoint 或

QPointF 作为参数,这个点指定了新的原点位置。

3.rect() : 这通常是指一个控件(如 QWidget)的矩形区域,返回一个 QRect 或 QRectF 对象,表示该控件的大小和位置。

4.rect().center() : 这个方法返回当前矩形(即控件的区域)的中心点,是一个 QPoint 或

QPointF 对象。

总之, painter.translate(rect().center()) 这行代码将 QPainter 的绘图原点移动到控件的中心。这在进行中心对称绘制或者需要以控件中心为基准进行绘图时特别有用。

6.4 画雷达案例

delete ui;

}

void Widget::paintEvent(QPaintEvent *event)

{

QPainter painter(this);

// 抗锯齿

painter.setRenderHint(QPainter::Antialiasing,true);

// 把背景色刷成黑色

QBrush brush(Qt::black); painter.setBrush(brush); painter.drawRect(rect());

// 平移坐标轴到窗口正中间

painter.translate(rect().center());

// 最小圆的半径

int rEve = height()/2/7; //800 600 600/7

int dataTmp = rEve * 7; //用height()/2 导致突出,上面那行代码除不尽

// 设置画笔,绿色,像素4 QPen pen(Qt::green,4); painter.setPen(pen);

painter.setBrush(Qt::NoBrush); //不要画刷,否则只能看到最外面的圈 for(int i=1; i <= 7; i++){

painter.drawEllipse(QPoint(0,0),rEve*i,rEve*i);  //依次画出7个圆形

}

painter.drawLine(-rEve*7,0,rEve*7,0); painter.drawLine(0,-rEve*7,0,rEve*7);

//设置锥形渐变

QConicalGradient conGradient(0,0,-startAngle); conGradient.setColorAt(0,QColor(0,255,0,200)); conGradient.setColorAt(0.1,QColor(0,255,0,100)); conGradient.setColorAt(0.2,QColor(0,255,0,0)); conGradient.setColorAt(1,QColor(0,255,0,0));

//直接用渐变色指定画刷

painter.setBrush(conGradient); painter.setPen(Qt::NoPen); //去除扇形区域边框

//画出扇形,启动角度是startAngle,由定时器来修改

painter.drawPie(QRect(-dataTmp,-dataTmp,dataTmp*2,dataTmp*2),

-startAngle*16,70*16);

}

6.5仪表表盘

6.5.1初步完成

mark = 0;

}

}

update();

});

timer->start(50);

}

Widget::~Widget()

{

delete ui;

}

void Widget::paintEvent(QPaintEvent *event)

{

QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing,true);

//底色弄成黑色 painter.setBrush(Qt::black);

painter.drawRect(rect());

//坐标系 平移到中心

painter.translate(rect().center());

QRadialGradient radialGradient(0, 0, height()/2); // 中心和半径 (50, 50, 50)

// 设置颜色停靠点

radialGradient.setColorAt(0.0, QColor(255,0,0,50)); // 中心颜色

radialGradient.setColorAt(1.0, QColor(255,0,0,250)); // 外围颜色

// 使用这个渐变创建 QBrush QBrush brush(radialGradient);

painter.setBrush(brush);

// 画大圆

painter.drawEllipse(QPoint(0,0),height()/2,height()/2); painter.setBrush(Qt::NoBrush);

// 画小圆

painter.setPen(QPen(Qt::white,3)); painter.drawEllipse(QPoint(0,0),60,60);

//当前值

painter.setFont(QFont("华文宋体",25));

// painter.drawText(0,0,QString::number(currentValue));

painter.drawText(QRect(-60,-60,120,120),Qt::AlignCenter,QString::number(currentV  alue));

//画刻度

//1. 算出一个刻度需要旋转的角度

double angle = 270*1.0 / 50;//270*1.0的作用是扩大变量类型,把int型阔成double,保留小

//2. 设置第一个刻度的位置

painter.setFont(QFont("华文宋体",15));

painter.save();//保存当前坐标位置,此时此刻是在原点,x在3点钟方向 painter.rotate(135);

for(int i=0;i<=50;i++){ if(i % 10 == 0){

6.5.2稍微美化

稍微修改后,依然存在数字方向的问题!

}

Widget::~Widget()

{

delete ui;

}

void Widget::paintEvent(QPaintEvent *event)

{

QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing,true);

//底色弄成黑色 painter.setBrush(Qt::black);

painter.drawRect(rect());

//坐标系 平移到中心

painter.translate(rect().center());

// 画大圆

painter.drawEllipse(QPoint(0,0),height()/2,height()/2); painter.setBrush(Qt::NoBrush);

// 画小圆

painter.setPen(QPen(Qt::white,3)); painter.drawEllipse(QPoint(0,0),60,60);

//当前值

painter.setFont(QFont("华文宋体",25));

// painter.drawText(0,0,QString::number(currentValue));

painter.drawText(QRect(-60,-60,120,120),Qt::AlignCenter,QString::number(currentV  alue));

//画刻度

//1. 算出一个刻度需要旋转的角度

double angle = 240*1.0 / 60;//270*1.0的作用是扩大变量类型,把int型阔成double,保留小

//2. 设置第一个刻度的位置

painter.setFont(QFont("华文宋体",15));

painter.save();//保存当前坐标位置,此时此刻是在原点,x在3点钟方向 painter.rotate(150);

for(int i=0;i<=60;i++){ if(i % 5 == 0){

//画字

if(150 + angle * i < 270){ painter.rotate(180);

painter.drawText(-(height()/2 - 20 - 10),

8,QString::number(i*4));

painter.rotate(-180);

}else{

painter.drawText(height()/2 - 20 - 45, 8,QString::number(i*4));

}

//画长的刻度线

painter.drawLine(height()/2 - 20, 0, height()/2 - 3 ,0);

}else{//画短的刻度线

6.5.3优化数字显示后代码整理

void Widget::initCanvas(QPainter& painter)

{

painter.setRenderHint(QPainter::Antialiasing,true);

//底色弄成黑色 painter.setBrush(Qt::black); painter.drawRect(rect());

//坐标系 平移到中心

QPoint cent(rect().width()/2, rect().height()*0.6); painter.translate(cent);

}

void Widget::drawMiddleCircle(QPainter &painter, int radius)

{

// 画小圆

painter.setPen(QPen(Qt::white,3)); painter.drawEllipse(QPoint(0,0),radius,radius);

}

void Widget::drawCurrentSpeed(QPainter &painter)

{

//当前值

painter.setFont(QFont("华文宋体",20));

// painter.drawText(0,0,QString::number(currentValue));

painter.drawText(QRect(-60,-60,120,120),Qt::AlignCenter,QString::number(currentV  alue));

}

void Widget::drawScale(QPainter &painter, int radius)

{

//画刻度

//1. 算出一个刻度需要旋转的角度

angle = 240*1.0 / 60;//270*1.0的作用是扩大变量类型,把int型阔成double,保留小数

//保存当前坐标位置,此时此刻是在原点,x在3点钟方向 painter.save(); painter.setPen(QPen(Qt::white,5));

//2. 设置第一个刻度的位置

painter.rotate(startAngle); for(int i=0;i<=60;i++){

if(i % 5 == 0){

//画长的刻度线

painter.drawLine(radius - 20, 0, radius - 3 ,0);

}else{//画短的刻度线

painter.drawLine(radius - 8, 0, radius - 3 ,0);

}

//画完后旋转 painter.rotate(angle);

}

painter.restore();

}

void Widget::drawScaleText(QPainter &painter, int radius)

{

//写刻度文字

painter.setFont(QFont("华文宋体",18)); int r = radius - 46;

for(int i=0; i<=60; i++){ if(i % 5 == 0){

//保存坐标系

painter.save();

//算出平移点,弧度=角度*3.1415/180

int delX = qCos( (210-angle*i)*M_PI/180) * r;//QT中sin认的是弧度 int delY = qSin(qDegreesToRadians(210-angle*i)) * r;

//平移坐标系

painter.translate(QPoint(delX,-delY));

//旋转坐标系

painter.rotate(-120+angle*i);//angle=4,30*4=120的时候,实参是0,120

//写上文字

painter.drawText(-25,-25,50,30,Qt::AlignCenter,QString::number(i*4));

//恢复坐标系 painter.restore();

}

}

}

void Widget::drawPointLine(QPainter &painter,int lenth)

{

//画指针,线

//坐标轴先回到原点 painter.restore();

painter.save();

painter.rotate(startAngle + angle * currentValue); painter.drawLine(60,0,lenth,0);

}

void Widget::drawSpeedPie(QPainter &painter, int radius)

{

painter.restore();

QRect rentangle(-radius,-radius,radius*2,radius*2); painter.setPen(Qt::NoPen); painter.setBrush(QColor(235,152,50,150));

painter.drawPie(rentangle,(360-startAngle)*16,-angle*currentValue*16);//angle

前面取 负数,为了让它顺时针方向画

}

void Widget::startSpeed()

{

timer = new QTimer(this);

currentValue = 0;

connect(timer, &QTimer::timeout,[=](){ if(mark == 0){

currentValue++;

if(currentValue >= 60){

mark = 1;

}

}

if(mark == 1){

6.5.4画一个指针

在Qt中,使用 QPainter 来绘制一个类似指南针的指针,你通常会遵循以下步骤:

1.创建一个QWidget或QMainWindow的子类:这是你绘图的画布。

2.重写 paintEvent 方法:这个方法是Qt中绘制自定义图形的关键地方。

3.使用QPainter:在 paintEvent 中创建一个QPainter 对象,并用它来绘制你的指针。

4.绘制指针:可以通过绘制一个线条或者一个具有特定形状的多边形来创建指针,比如一个三角形。

5.旋转指针:如果你想让指针能够像指南针那样旋转,你可以使用QPainter 的旋转功能。下面是一个简单的例子,展示如何在Qt中绘制一个静态的指针:

这个代码片段创建了一个自定义的QWidget,它在中间绘制了一个三角形作为指针。你可以通过添加更多的逻辑来使指针动态旋转,比如根据数据改变指针的方向。在实际应用中,指针的样式、大小和颜色都可以根据你的需要进行自定义。

6.5.5内环

6.5.6完结

painter.drawRect(rect());

//坐标系 平移到中心

QPoint cent(rect().width()/2, rect().height()*0.6); painter.translate(cent);

}

void Widget::drawMiddleCircle(QPainter &painter, int radius)

{

// 画小圆

painter.setPen(QPen(Qt::white,3)); painter.drawEllipse(QPoint(0,0),radius,radius);

}

void Widget::drawCurrentSpeed(QPainter &painter)

{

//当前值 painter.setPen(Qt::white);

QFont font("Arial",30); font.setBold(true); painter.setFont(font);

// painter.drawText(0,0,QString::number(currentValue));

painter.drawText(QRect(-60,-60,120,70),Qt::AlignCenter,QString::number(currentVa  lue*4));

QFont font2("Arial",13); font.setBold(true); painter.setFont(font2);

painter.drawText(QRect(-60,-60,120,160),Qt::AlignCenter,"Km/h");

}

void Widget::drawScale(QPainter &painter, int radius)

{

//画刻度

//1. 算出一个刻度需要旋转的角度

angle = 240*1.0 / 60;//270*1.0的作用是扩大变量类型,把int型阔成double,保留小数

//保存当前坐标位置,此时此刻是在原点,x在3点钟方向 painter.save();

painter.setPen(QPen(Qt::white,5));

//2. 设置第一个刻度的位置

painter.rotate(startAngle); for(int i=0;i<=60;i++){

if( i >= 40){

painter.setPen(QPen(Qt::red,5));

}

if(i % 5 == 0){

//画长的刻度线

painter.drawLine(radius - 20, 0, radius - 3 ,0);

}else{//画短的刻度线

painter.drawLine(radius - 8, 0, radius - 3 ,0);

}

//画完后旋转 painter.rotate(angle);

}

painter.restore();

painter.setPen(QPen(Qt::white,5));

}

void Widget::drawScaleText(QPainter &painter, int radius)

{

//写刻度文字

QFont font("Arial",15); font.setBold(true); painter.setFont(font); int r = radius - 49; for(int i=0; i<=60; i++){

if(i % 5 == 0){

//保存坐标系 painter.save();

//算出平移点,弧度=角度*3.1415/180

int delX = qCos( (210-angle*i)*M_PI/180) * r;//QT中sin认的是弧度 int delY = qSin(qDegreesToRadians(210-angle*i)) * r;

//平移坐标系

painter.translate(QPoint(delX,-delY));

//旋转坐标系

painter.rotate(-120+angle*i);//angle=4,30*4=120的时候,实参是0,120

//写上文字

painter.drawText(-25,-25,50,30,Qt::AlignCenter,QString::number(i*4));

//恢复坐标系 painter.restore();

}

}

}

void Widget::drawPointLine(QPainter &painter,int lenth)

{

//画指针,线 painter.save();

painter.setBrush(Qt::white); painter.setPen(Qt::NoPen);

static const QPointF points[4] = {

QPointF(0,0.0),

QPointF(200.0,-1.1),

QPointF(200.0,1.1),

QPointF(0,15.0),

};

painter.rotate(startAngle + angle * currentValue); painter.drawPolygon(points, 4);

// painter.drawLine(60,0,lenth,0);

//坐标轴先回到原点 painter.restore();

}

void Widget::drawSpeedPie(QPainter &painter, int radius)

{

QRect rentangle(-radius,-radius,radius*2,radius*2); painter.setPen(Qt::NoPen); painter.setBrush(QColor(255,0,0,80));

painter.drawPie(rentangle,(360-startAngle)*16,-angle*currentValue*16);//angle

前面取 负数,为了让它顺时针方向画

}

void Widget::startSpeed()

{

timer = new QTimer(this);

currentValue = 0;

connect(timer, &QTimer::timeout,[=](){ if(mark == 0){

currentValue++; if(currentValue >= 61){

mark = 1;

}

}

if(mark == 1){ currentValue--; if(currentValue == 0){

mark = 0;

}

}

update();

});

timer->start(50);

}

void Widget::drawEllipseInnerBlack(QPainter &painter, int radius)

{

painter.setBrush(Qt::black); painter.drawEllipse(QPoint(0,0),radius,radius);

}

void Widget::drawEllipseInnerShine(QPainter &painter, int radius)

{

QRadialGradient radialGradient(0,0,radius); radialGradient.setColorAt(0.0, QColor(255,0,0,200)); // 中心颜色 radialGradient.setColorAt(1.0, QColor(0,0,0,100)); // 外围颜色

painter.setBrush(radialGradient); painter.drawEllipse(QPoint(0,0),radius,radius);

}

void Widget::drawEllipseOutterShine(QPainter &painter, int radius)

{

QRect rentangle(-radius,-radius,radius*2,radius*2); painter.setPen(Qt::NoPen);

QRadialGradient radiaGradient(0,0,radius); radiaGradient.setColorAt(1,QColor(255,0,0,200));

radiaGradient.setColorAt(0.97,QColor(255,0,0,120)); radiaGradient.setColorAt(0.9,QColor(0,0,0,0)); radiaGradient.setColorAt(0,QColor(0,0,0,0)); painter.setBrush(radiaGradient);

painter.drawPie(rentangle,(360-150)*16,-angle*61*16);//angle前面取 负数,为了让它顺时针方向画

}

void Widget::drawLogo(QPainter &painter, int radius)

{

QRect rectangle(-65,radius*0.38,130,50); painter.drawPixmap(rectangle,QPixmap(":/icon.png"));

}

void Widget::paintEvent(QPaintEvent *event)

{

QPainter painter(this); int rad = height()/2;

//初始化画布

initCanvas(painter);

//画小圆 drawMiddleCircle(painter,60);

//画刻度 drawScale(painter, rad);

//画刻度文字 drawScaleText(painter, rad);

//指针

drawPointLine(painter, rad-58);

//画扇形

drawSpeedPie(painter, rad+25);

//画渐变内圈圆 drawEllipseInnerShine(painter,110);

//画黑色内圈 drawEllipseInnerBlack(painter, 80);

//画当前速度 drawCurrentSpeed(painter);

//画外环发光圈 drawEllipseOutterShine(painter,rad+25);

//画一个汽车logo drawLogo(painter, rad);

}

6.6 汽车表盘参考样式

P7 天气预报项目

7.1项目概述

 stylesheet界面美化  Json数据解析

 HTTP通信

 自定义控件绘制温度  多控件

 代码整合调试能力

7.2 stylesheet样式

设置边框弧度

设置某方向边框弧度

设置背景颜色

父控件影响

父控件指定某类控件的样式,子控件都要遵守此样式进行显示,除非子控件内部有做相关修改

7.3 窗体无状态栏-关闭

设置无状态栏

设置左键弹窗关闭功能

7.4 窗口跟随移动

代码实现

实现的逻辑

7.5 天气预报数据接口

第一种:

数据返回:

{"message":"success感谢又拍云(upyun.com)提供CDN赞

助","status":200,"date":"20240122","time":"2024-01-22 11:20:56","cityInfo":

{"city":"北京市","citykey":"101010100","parent":"北

京","updateTime":"07:16"},"data":

{"shidu":"30%","pm25":4.0,"pm10":14.0,"quality":"优","wendu":"-16","ganmao":"各类

人群可自由活动","forecast":[{"date":"22","high":"高温  -3℃","low":"低温

-11℃","ymd":"2024-01-22","week":"星期

一","sunrise":"07:30","sunset":"17:20","aqi":24,"fx":"西北风","fl":"3

级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"23","high":"高温

1℃","low":"低温  -9℃","ymd":"2024-01-23","week":"星期

二","sunrise":"07:29","sunset":"17:22","aqi":37,"fx":"西北风","fl":"3

级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"24","high":"高温

4℃","low":"低温  -7℃","ymd":"2024-01-24","week":"星期

三","sunrise":"07:29","sunset":"17:23","aqi":74,"fx":"北风","fl":"2

级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"25","high":"高温

5℃","low":"低温  -8℃","ymd":"2024-01-25","week":"星期

四","sunrise":"07:28","sunset":"17:24","aqi":86,"fx":"西北风","fl":"2级","type":"多

云","notice":"阴晴之间,谨防紫外线侵扰"},{"date":"26","high":"高温  5℃","low":"低温

-7℃","ymd":"2024-01-26","week":"星期

五","sunrise":"07:27","sunset":"17:25","aqi":79,"fx":"北风","fl":"2

级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"27","high":"高温

6℃","low":"低温  -4℃","ymd":"2024-01-27","week":"星期

六","sunrise":"07:26","sunset":"17:26","aqi":53,"fx":"西北风","fl":"2

级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"28","high":"高温

4℃","low":"低温  -5℃","ymd":"2024-01-28","week":"星期

日","sunrise":"07:26","sunset":"17:28","aqi":52,"fx":"北风","fl":"1级","type":"多

云","notice":"阴晴之间,谨防紫外线侵扰"},{"date":"29","high":"高温  1℃","low":"低温

-6℃","ymd":"2024-01-29","week":"星期

一","sunrise":"07:25","sunset":"17:29","aqi":22,"fx":"东北风","fl":"1

级","type":"晴","notice":"愿你拥有比阳光明媚的心情"},{"date":"30","high":"高温

3℃","low":"低温  -6℃","ymd":"2024-01-30","week":"星期

二","sunrise":"07:24","sunset":"17:30","aqi":34,"fx":"东风","fl":"2

级","type":"阴","notice":"不要被阴云遮挡住好心情"},{"date":"31","high":"高温

4℃","low":"低温  -4℃","ymd":"2024-01-31","week":"星期

三","sunrise":"07:23","sunset":"17:31","aqi":48,"fx":"东南风","fl":"2

级","type":"阴","notice":"不要被阴云遮挡住好心情"},{"date":"01","high":"高温

8℃","low":"低温  -3℃","ymd":"2024-02-01","week":"星期

四","sunrise":"07:22","sunset":"17:32","aqi":42,"fx":"西风","fl":"1

级","type":"阴","notice":"不要被阴云遮挡住好心情"},{"date":"02","high":"高温

7℃","low":"低温  -3℃","ymd":"2024-02-02","week":"星期

五","sunrise":"07:21","sunset":"17:34","aqi":59,"fx":"东南风","fl":"1

级","type":"阴","notice":"不要被阴云遮挡住好心情"},{"date":"03","high":"高温

2℃","low":"低温  -4℃","ymd":"2024-02-03","week":"星期

六","sunrise":"07:20","sunset":"17:35","aqi":41,"fx":"南风","fl":"2级","type":"小

雪","notice":"小雪虽美,赏雪别着凉"},{"date":"04","high":"高温  2℃","low":"低温

-5℃","ymd":"2024-02-04","week":"星期

日","sunrise":"07:19","sunset":"17:36","aqi":45,"fx":"西北风","fl":"1级","type":"多

云","notice":"阴晴之间,谨防紫外线侵扰"},{"date":"05","high":"高温  3℃","low":"低温

-5℃","ymd":"2024-02-05","week":"星期

一","sunrise":"07:18","sunset":"17:37","aqi":48,"fx":"北风","fl":"1级","type":"多云","notice":"阴晴之间,谨防紫外线侵扰"}],"yesterday":{"date":"21","high":"高温

-5℃","low":"低温  -10℃","ymd":"2024-01-21","week":"星期

日","sunrise":"07:31","sunset":"17:19","aqi":26,"fx":"西北风","fl":"4级","type":"晴","notice":"愿你拥有比阳光明媚的心情"}}}

第二种:

数据返回

{"cityid":"101230101","date":"2024-01-22","week":"星期

一","update_time":"11:29","city":"福州","cityEn":"fuzhou","country":"中

国","countryEn":"China","wea":"阴","wea_img":"yin","tem":"8.7","tem1":"7","tem2":"

-1","win":"东北风","win_speed":"2

级","win_meter":"4km\/h","humidity":"78%","visibility":"10km","pressure":"1019"," air":"21","air_pm25":"21","air_level":"优","air_tips":"各类人群可多参加户外活动,多呼吸一下清新的空气。","alarm":[{"alarm_type":"降温","alarm_level":"蓝

色","alarm_title":"福建省福州市发布降温蓝色预警","alarm_content":"福州市气象台2024年01月 22日09时44分继续发布降温蓝色预警信号:受寒潮影响,今天到24日我市气温继续下降,日最低气温过程降幅可达9~12℃;今天到24日夜晨气温较低;过程最低气温晋安区山区可达-3~0℃,有结冰;其余地区1~3℃,有霜或霜冻;22日傍晚到23日上午部分乡镇将出现小雪或雨夹雪。请注意防范!(预警信息来源:国家预警信息发布中心)"},{"alarm_type":"降温","alarm_level":"蓝色","alarm_title":"福建省福州市发布降温蓝色预警","alarm_content":"福州市气象台2024年01月22日09时44分继续发布降温蓝色预警信   号:受寒潮影响,今天到24日我市气温继续下降,日最低气温过程降幅可达9~12℃;今天到24日夜晨气温较低;过程最低气温晋安区山区可达-3~0℃,有结冰;其余地区1~3℃,有霜或霜冻;22日傍晚到23日上午部分乡镇将出现小雪或雨夹雪。请注意防范!(预警信息来源:国家预警信息发布中心)"},

{"alarm_type":"降温","alarm_level":"蓝色","alarm_title":"福建省福州市发布降温蓝色预

警","alarm_content":"福州市气象台2024年01月21日09时29分发布降温蓝色预警信号:受寒潮影响,今天到23日我市气温持续下降,日最低气温过程降幅可达9~12℃,22~24日夜晨气温较低,过程日最低气温晋安区北部可达-3~0℃,有霜或霜冻和结冰;其余地区1~3℃,有霜或霜冻,请注意防范!(预警信息来源:国家预警信息发布中心)"},{"alarm_type":"降温","alarm_level":"蓝色","alarm_title":"福建省福州市发布降温蓝色预警","alarm_content":"福州市气象台2024年01月21日09时29分发布降温蓝色预警信号:受寒潮影响,今天到23日我市气温持续下降,日最低气温过程降幅可达9~12℃,22~24日夜晨气温较低,过程日最低气温晋安区北部可达-3~0℃,有霜或霜冻和结冰;其余地区1~3℃,有霜或霜冻,请注意防 范!(预警信息来源:国家预警信息发布中

心)"}],"rain_pcpn":"2.8","uvIndex":"2","uvDescription":"低","wea_day":"小雨","wea_day_img":"yu","wea_night":"小

雨","wea_night_img":"yu","sunrise":"06:50","sunset":"17:36","hours":

[{"hours":"10:00","wea":"小雨","wea_img":"yu","tem":"9","win":"东北

风","win_speed":"2级","vis":"13","aqinum":"21","aqi":"优"},

{"hours":"11:00","wea":"小雨","wea_img":"yu","tem":"7","win":"东北

风","win_speed":"2级","vis":"12.92","aqinum":"22","aqi":"优"},

{"hours":"12:00","wea":"小雨","wea_img":"yu","tem":"7","win":"东北

风","win_speed":"2级","vis":"13.02","aqinum":"21","aqi":"优"},

{"hours":"13:00","wea":"雾","wea_img":"wu","tem":"7","win":"东北风","win_speed":"2

级","vis":"13.02","aqinum":"20","aqi":"优"},

{"hours":"14:00","wea":"雾","wea_img":"wu","tem":"5","win":"东北风","win_speed":"2

级","vis":"12.92","aqinum":"21","aqi":"优"},{"hours":"15:00","wea":"小

雨","wea_img":"yu","tem":"4","win":"东北风","win_speed":"2

级","vis":"12.78","aqinum":"24","aqi":"优"},{"hours":"16:00","wea":"小

雨","wea_img":"yu","tem":"3","win":"东北风","win_speed":"2

级","vis":"12.21","aqinum":"24","aqi":"优"},{"hours":"17:00","wea":"中

雨","wea_img":"yu","tem":"2","win":"东北风","win_speed":"2

级","vis":"11.77","aqinum":"27","aqi":"优"},{"hours":"18:00","wea":"中

雨","wea_img":"yu","tem":"1","win":"东北风","win_speed":"2

级","vis":"11.42","aqinum":"26","aqi":"优"},{"hours":"19:00","wea":"小

雨","wea_img":"yu","tem":"1","win":"东北风","win_speed":"2

级","vis":"10.77","aqinum":"24","aqi":"优"},{"hours":"20:00","wea":"小

雨","wea_img":"yu","tem":"1","win":"东北风","win_speed":"2

级","vis":"10.13","aqinum":"23","aqi":"优"},{"hours":"21:00","wea":"小

雨","wea_img":"yu","tem":"1","win":"东北风","win_speed":"2

级","vis":"9.3","aqinum":"23","aqi":"优"},{"hours":"22:00","wea":"小

雨","wea_img":"yu","tem":"1","win":"东北风","win_speed":"2

级","vis":"9.11","aqinum":"24","aqi":"优"},{"hours":"23:00","wea":"小

雨","wea_img":"yu","tem":"1","win":"东北风","win_speed":"1

级","vis":"9.11","aqinum":"27","aqi":"优"},

{"hours":"00:00","wea":"雾","wea_img":"wu","tem":"2","win":"东北风","win_speed":"1

级","vis":"8.91","aqinum":"27","aqi":"优"},{"hours":"01:00","wea":"小

雨","wea_img":"yu","tem":"2","win":"东北风","win_speed":"1

级","vis":"8.91","aqinum":"28","aqi":"优"},{"hours":"02:00","wea":"小

雨","wea_img":"yu","tem":"2","win":"东北风","win_speed":"1

级","vis":"8.91","aqinum":"30","aqi":"优"},{"hours":"03:00","wea":"小

雨","wea_img":"yu","tem":"2","win":"东北风","win_speed":"1

级","vis":"8.91","aqinum":"28","aqi":"优"},{"hours":"04:00","wea":"小

雨","wea_img":"yu","tem":"2","win":"北东北风","win_speed":"1

级","vis":"9.28","aqinum":"28","aqi":"优"},

{"hours":"05:00","wea":"阴","wea_img":"yin","tem":"2","win":"东北

风","win_speed":"1级","vis":"9.83","aqinum":"28","aqi":"优"},

{"hours":"06:00","wea":"多云","wea_img":"yun","tem":"3","win":"东北

风","win_speed":"1级","vis":"10.7","aqinum":"28","aqi":"优"},

{"hours":"07:00","wea":"晴","wea_img":"qing","tem":"4","win":"东北

风","win_speed":"1级","vis":"11","aqinum":"28","aqi":"优"},

{"hours":"08:00","wea":"晴","wea_img":"qing","tem":"5","win":"东北

风","win_speed":"1级","vis":"11.59","aqinum":"28","aqi":"优"},

{"hours":"09:00","wea":"晴","wea_img":"qing","tem":"6","win":"东北

风","win_speed":"1级","vis":"12.41","aqinum":"27","aqi":"优"},

{"hours":"10:00","wea":"晴","wea_img":"qing","tem":"7","win":"东北

风","win_speed":"1级","vis":"13.43","aqinum":"27","aqi":"优"},

{"hours":"11:00","wea":"晴","wea_img":"qing","tem":"8","win":"东北

风","win_speed":"1级","vis":"14.42","aqinum":"27","aqi":"优"},

{"hours":"12:00","wea":"晴","wea_img":"qing","tem":"8","win":"东北

风","win_speed":"1级","vis":"15.58","aqinum":"25","aqi":"优"},

{"hours":"13:00","wea":"晴","wea_img":"qing","tem":"9","win":"东北

风","win_speed":"1级","vis":"16.85","aqinum":"24","aqi":"优"}],"aqi":

{"update_time":"10:47","air":"21","air_level":"优","air_tips":"各类人群可多参加户外活动,多呼吸一下清新的空

气。","pm25":"14","pm25_desc":"优","pm10":"21","pm10_desc":"优","o3":"42","o3_desc

":"","no2":"17","no2_desc":"","so2":"5","so2_desc":"","co":"0.8","co_desc":"","ko  uzhao":"不用佩戴口罩","yundong":"适宜运动","waichu":"适宜外出","kaichuang":"适宜开

窗","jinghuaqi":"不需要打开"}}

未来7天

数据返回

{"cityid":"101230101","city":"福州","cityEn":"fuzhou","country":"中

国","countryEn":"China","update_time":"2024-01-22  11:47:00","data":[{"day":"22日

(星期一)","date":"2024-01-22","week":"星期

一","wea":"阴","wea_img":"yin","wea_day":"小雨","wea_day_img":"yu","wea_night":"小

雨","wea_night_img":"yu","tem":"8.7","tem1":"7","tem2":"-1","humidity":"79%","vis

ibility":"11km","pressure":"1019","win":["无持续风向","无持续风向"],"win_speed":"<3

级","win_meter":"2km\/h","sunrise":"06:50","sunset":"17:36","air":"21","air_level  ":"优","air_tips":"各类人群可多参加户外活动,多呼吸一下清新的空气。","alarm":

{"alarm_type":"降温","alarm_level":"蓝色","alarm_title":"福建省福州市发布降温蓝色预 警","alarm_content":"福州市气象台2024年01月22日09时44分继续发布降温蓝色预警信号:受寒潮影

响,今天到24日我市气温继续下降,日最低气温过程降幅可达9~12℃;今天到24日夜晨气温较低;过程最低气温晋安区山区可达-3~0℃,有结冰;其余地区1~3℃,有霜或霜冻;22日傍晚到23日上午部分乡镇将出现小雪或雨夹雪。请注意防范!(预警信息来源:国家预警信息发布中心)"},"hours":[{"hours":"08

时","wea":"阴","wea_img":"yin","tem":"7","win":"无持续风向","win_speed":"<3级"},

{"hours":"09时","wea":"小雨","wea_img":"yu","tem":"6","win":"东北风","win_speed":"

<3级"},{"hours":"10时","wea":"小雨","wea_img":"yu","tem":"6","win":"东北风","win_speed":"<3级"},{"hours":"11时","wea":"小

雨","wea_img":"yu","tem":"6","win":"东北风","win_speed":"<3级"},{"hours":"12

时","wea":"小雨","wea_img":"yu","tem":"6","win":"东北风","win_speed":"<3级"},

{"hours":"13时","wea":"小雨","wea_img":"yu","tem":"6","win":"东北风","win_speed":"

<3级"},{"hours":"14时","wea":"小雨","wea_img":"yu","tem":"6","win":"东北风","win_speed":"<3级"},{"hours":"15时","wea":"小

雨","wea_img":"yu","tem":"5","win":"东北风","win_speed":"<3级"},{"hours":"16

时","wea":"小雨","wea_img":"yu","tem":"5","win":"东北风","win_speed":"<3级"},

{"hours":"17时","wea":"小雨","wea_img":"yu","tem":"4","win":"东北风","win_speed":"

<3级"},{"hours":"18时","wea":"小雨","wea_img":"yu","tem":"4","win":"东北风","win_speed":"<3级"},{"hours":"19时","wea":"小

雨","wea_img":"yu","tem":"4","win":"东北风","win_speed":"<3级"},{"hours":"20

时","wea":"小雨","wea_img":"yu","tem":"4","win":"东北风","win_speed":"<3级"},

{"hours":"21时","wea":"小雨","wea_img":"yu","tem":"3","win":"东北风","win_speed":"

<3级"},{"hours":"22时","wea":"小雨","wea_img":"yu","tem":"3","win":"东北风","win_speed":"<3级"},{"hours":"23时","wea":"小

雨","wea_img":"yu","tem":"3","win":"东北风","win_speed":"<3级"},{"hours":"00

时","wea":"雨夹雪","wea_img":"yu","tem":"1","win":"东北风","win_speed":"<3级"},

{"hours":"01时","wea":"雨夹雪","wea_img":"yu","tem":"0","win":"东北风","win_speed":"<3级"},{"hours":"02时","wea":"雨夹

雪","wea_img":"yu","tem":"0","win":"东北风","win_speed":"<3级"},{"hours":"03

时","wea":"雨夹雪","wea_img":"yu","tem":"0","win":"东北风","win_speed":"<3级"},

{"hours":"04时","wea":"雨夹雪","wea_img":"yu","tem":"0","win":"东北风","win_speed":"<3级"},{"hours":"05时","wea":"雨夹

雪","wea_img":"yu","tem":"1","win":"东北风","win_speed":"<3级"},{"hours":"06

时","wea":"小雨","wea_img":"yu","tem":"2","win":"东北风","win_speed":"<3级"},

{"hours":"07时","wea":"小雨","wea_img":"yu","tem":"2","win":"东北风","win_speed":"

<3级"}],"index":[{"title":"紫外线指数","level":"最弱","desc":"辐射弱,涂擦SPF8-12防晒护

肤品。"},{"title":"减肥指数","level":"较不宜","desc":"有降水,推荐您在室内进行休闲运

动。"},{"title":"血糖指数","level":"极不易发","desc":"无需担心过敏,可放心外出,享受生

活。"},{"title":"穿衣指数","level":"冷","desc":"建议着棉衣加羊毛衫等冬季服装。"},

{"title":"洗车指数","level":"不宜","desc":"有雨,雨水和泥水会弄脏爱车。"},{"title":"空气污染扩散指数","level":"良","desc":"气象条件有利于空气污染物扩

散。"}],"uvIndex":"2","uvDescription":"低"},{"day":"23日(星期二)","date":"2024-01-

23","week":"星期二","wea":"多云转晴","wea_img":"yun","wea_day":"多

云","wea_day_img":"yun","wea_night":"晴","wea_night_img":"qing","tem":"10","tem1":

"10","tem2":"2","humidity":"63","visibility":"","pressure":"","win":["无持续风向","无持续风向"],"win_speed":"<3

级","win_meter":"","sunrise":"06:50","sunset":"17:37","air":"31","air_level":"优",  "air_tips":"","alarm":

{"alarm_type":"","alarm_level":"","alarm_content":""},"hours":[{"hours":"08

时","wea":"小雨","wea_img":"yu","tem":"3","win":"东北风","win_speed":"<3级"},

{"hours":"09时","wea":"多云","wea_img":"yun","tem":"4","win":"东北风","win_speed":"

<3级"},{"hours":"10时","wea":"多云","wea_img":"yun","tem":"5","win":"东北风","win_speed":"<3级"},{"hours":"11时","wea":"多

云","wea_img":"yun","tem":"7","win":"北风","win_speed":"<3级"},{"hours":"12

时","wea":"多云","wea_img":"yun","tem":"8","win":"东北风","win_speed":"<3级"},

{"hours":"13时","wea":"多云","wea_img":"yun","tem":"8","win":"东北风","win_speed":"

<3级"},{"hours":"14时","wea":"晴","wea_img":"qing","tem":"9","win":"东风","win_speed":"<3级"},{"hours":"15

时","wea":"晴","wea_img":"qing","tem":"9","win":"东风","win_speed":"<3级"},

{"hours":"16时","wea":"晴","wea_img":"qing","tem":"8","win":"东风","win_speed":"<3

级"},{"hours":"17时","wea":"晴","wea_img":"qing","tem":"7","win":"东风","win_speed":"<3级"},{"hours":"18

时","wea":"晴","wea_img":"qing","tem":"6","win":"东风","win_speed":"<3级"},

{"hours":"19时","wea":"晴","wea_img":"qing","tem":"6","win":"东北风","win_speed":"

<3级"},{"hours":"20时","wea":"晴","wea_img":"qing","tem":"5","win":"东北风","win_speed":"<3级"},{"hours":"21

时","wea":"晴","wea_img":"qing","tem":"4","win":"东北风","win_speed":"<3级"},

{"hours":"22时","wea":"晴","wea_img":"qing","tem":"4","win":"东北风","win_speed":"

<3级"},{"hours":"23时","wea":"晴","wea_img":"qing","tem":"4","win":"东北风","win_speed":"<3级"},{"hours":"00

时","wea":"晴","wea_img":"qing","tem":"4","win":"东北风","win_speed":"<3级"},

{"hours":"01时","wea":"晴","wea_img":"qing","tem":"3","win":"东北风","win_speed":"

<3级"},{"hours":"02时","wea":"晴","wea_img":"qing","tem":"3","win":"东北风","win_speed":"<3级"},{"hours":"03

时","wea":"晴","wea_img":"qing","tem":"2","win":"东南风","win_speed":"<3级"},

{"hours":"04时","wea":"晴","wea_img":"qing","tem":"2","win":"西南风","win_speed":"

<3级"},{"hours":"05时","wea":"晴","wea_img":"qing","tem":"2","win":"北风","win_speed":"<3级"},{"hours":"06

时","wea":"晴","wea_img":"qing","tem":"2","win":"北风","win_speed":"<3级"},

{"hours":"07时","wea":"晴","wea_img":"qing","tem":"3","win":"北风","win_speed":"<3

级"}],"index":[{"title":"紫外线指数","level":"中等","desc":"涂擦SPF大于15、PA+防晒护肤

品。"},{"title":"减肥指数","level":"较适宜","desc":"气温较低,在户外运动请注意增减衣

物。"},{"title":"血糖指数","level":"极不易发","desc":"无需担心过敏,可放心外出,享受生

活。"},{"title":"穿衣指数","level":"较冷","desc":"建议着厚外套加毛衣等服装。"},

{"title":"洗车指数","level":"较不宜","desc":"路面有积水,车子易被溅上泥水。"},

{"title":"空气污染扩散指数","level":"中","desc":"易感人群应适当减少室外活

动。"}],"uvIndex":"5","uvDescription":"中等"},{"day":"24日(星期三)","date":"2024-  01-24","week":"星期

三","wea":"晴","wea_img":"qing","wea_day":"晴","wea_day_img":"qing","wea_night":"

晴","wea_night_img":"qing","tem":"11","tem1":"11","tem2":"4","humidity":"46","vis

ibility":"","pressure":"","win":["无持续风向","无持续风向"],"win_speed":"<3

级","win_meter":"","sunrise":"06:50","sunset":"17:38","air":"34","air_level":"优",  "air_tips":"","alarm":

{"alarm_type":"","alarm_level":"","alarm_content":""},"hours":[{"hours":"08

时","wea":"晴","wea_img":"qing","tem":"3","win":"西北风","win_speed":"<3级"},

{"hours":"09时","wea":"晴","wea_img":"qing","tem":"5","win":"西风","win_speed":"<3

级"},{"hours":"10时","wea":"晴","wea_img":"qing","tem":"7","win":"南风","win_speed":"<3级"},{"hours":"11

时","wea":"晴","wea_img":"qing","tem":"9","win":"东风","win_speed":"<3级"},

{"hours":"12时","wea":"晴","wea_img":"qing","tem":"9","win":"东风","win_speed":"<3

级"},{"hours":"13时","wea":"晴","wea_img":"qing","tem":"10","win":"东风","win_speed":"<3级"},{"hours":"14

时","wea":"晴","wea_img":"qing","tem":"10","win":"东风","win_speed":"<3级"},

{"hours":"15时","wea":"晴","wea_img":"qing","tem":"10","win":"东风","win_speed":"

<3级"},{"hours":"16时","wea":"晴","wea_img":"qing","tem":"9","win":"东风","win_speed":"<3级"},{"hours":"17

时","wea":"晴","wea_img":"qing","tem":"9","win":"东风","win_speed":"<3级"},

{"hours":"18时","wea":"晴","wea_img":"qing","tem":"8","win":"东风","win_speed":"<3

级"},{"hours":"19时","wea":"晴","wea_img":"qing","tem":"8","win":"东风","win_speed":"<3级"},{"hours":"20

时","wea":"晴","wea_img":"qing","tem":"8","win":"东北风","win_speed":"<3级"},

{"hours":"21时","wea":"晴","wea_img":"qing","tem":"7","win":"东北风","win_speed":"

<3级"},{"hours":"22时","wea":"晴","wea_img":"qing","tem":"5","win":"东北风","win_speed":"<3级"},{"hours":"23

时","wea":"晴","wea_img":"qing","tem":"4","win":"北风","win_speed":"<3级"},

{"hours":"00时","wea":"晴","wea_img":"qing","tem":"4","win":"东南风","win_speed":"

<3级"},{"hours":"01时","wea":"晴","wea_img":"qing","tem":"4","win":"西南风","win_speed":"<3级"},{"hours":"02

时","wea":"晴","wea_img":"qing","tem":"4","win":"北风","win_speed":"<3级"},

{"hours":"03时","wea":"晴","wea_img":"qing","tem":"5","win":"北风","win_speed":"<3

级"},{"hours":"04时","wea":"晴","wea_img":"qing","tem":"5","win":"西北风","win_speed":"<3级"},{"hours":"05

时","wea":"晴","wea_img":"qing","tem":"5","win":"西北风","win_speed":"<3级"},

{"hours":"06时","wea":"晴","wea_img":"qing","tem":"5","win":"西北风","win_speed":"

<3级"},{"hours":"07时","wea":"晴","wea_img":"qing","tem":"5","win":"西北

风","win_speed":"<3级"}],"index":[{"title":"紫外线指数","level":"强","desc":"涂擦SPF大于15、PA+防晒护肤品。"},{"title":"减肥指数","level":"较适宜","desc":"气温较低,在户外运

动请注意增减衣物。"},{"title":"血糖指数","level":"极不易发","desc":"无需担心过敏,可放心外

出,享受生活。"},{"title":"穿衣指数","level":"较冷","desc":"建议着厚外套加毛衣等服装。"},

{"title":"洗车指数","level":"适宜","desc":"天气较好,适合擦洗汽车。"},{"title":"空气污染扩散指数","level":"中","desc":"易感人群应适当减少室外活

动。"}],"uvIndex":"6","uvDescription":"强"},{"day":"25日(星期四)","date":"2024-01-

25","week":"星期四","wea":"晴转多

云","wea_img":"yun","wea_day":"晴","wea_day_img":"qing","wea_night":"多

云","wea_night_img":"yun","tem":"13","tem1":"13","tem2":"5","humidity":"51","visi

bility":"","pressure":"","win":["无持续风向","无持续风向"],"win_speed":"<3

级","win_meter":"","sunrise":"06:50","sunset":"17:39","air":"31","air_level":"优",  "air_tips":"","alarm":

{"alarm_type":"","alarm_level":"","alarm_content":""},"hours":[{"hours":"08

时","wea":"晴","wea_img":"qing","tem":"6","win":"西北风","win_speed":"<3级"},

{"hours":"11时","wea":"晴","wea_img":"qing","tem":"11","win":"东南风","win_speed":"

<3级"},{"hours":"14时","wea":"晴","wea_img":"qing","tem":"12","win":"东风","win_speed":"<3级"},{"hours":"17

时","wea":"晴","wea_img":"qing","tem":"13","win":"东北风","win_speed":"<3级"},

{"hours":"20时","wea":"晴","wea_img":"qing","tem":"8","win":"东北风","win_speed":"

<3级"},{"hours":"23时","wea":"晴","wea_img":"qing","tem":"7","win":"西风","win_speed":"<3级"},{"hours":"02时","wea":"多

云","wea_img":"yun","tem":"5","win":"北风","win_speed":"<3级"},{"hours":"05

时","wea":"多云","wea_img":"yun","tem":"5","win":"北风","win_speed":"<3

级"}],"index":[{"title":"紫外线指数","level":"强","desc":"涂擦SPF大于15、PA+防晒护肤

品。"},{"title":"减肥指数","level":"较适宜","desc":"气温较低,在户外运动请注意增减衣

物。"},{"title":"血糖指数","level":"极不易发","desc":"无需担心过敏,可放心外出,享受生

活。"},{"title":"穿衣指数","level":"较冷","desc":"建议着厚外套加毛衣等服装。"},

{"title":"洗车指数","level":"适宜","desc":"天气较好,适合擦洗汽车。"},{"title":"空气污染扩散指数","level":"中","desc":"易感人群应适当减少室外活

动。"}],"uvIndex":"6","uvDescription":"强"},{"day":"26日(星期五)","date":"2024-01-  26","week":"星期

五","wea":"晴","wea_img":"qing","wea_day":"晴","wea_day_img":"qing","wea_night":"

晴","wea_night_img":"qing","tem":"15","tem1":"15","tem2":"6","humidity":"53","vis

ibility":"","pressure":"","win":["无持续风向","无持续风向"],"win_speed":"<3

级","win_meter":"","sunrise":"06:49","sunset":"17:39","air":"30","air_level":"优",  "air_tips":"","alarm":

{"alarm_type":"","alarm_level":"","alarm_content":""},"hours":[{"hours":"08

时","wea":"多云","wea_img":"yun","tem":"6","win":"西北风","win_speed":"<3级"},

{"hours":"11时","wea":"多云","wea_img":"yun","tem":"10","win":"东南风","win_speed":"<3级"},{"hours":"14

时","wea":"晴","wea_img":"qing","tem":"14","win":"东风","win_speed":"<3级"},

{"hours":"17时","wea":"晴","wea_img":"qing","tem":"12","win":"东北风","win_speed":"

<3级"},{"hours":"20时","wea":"晴","wea_img":"qing","tem":"10","win":"东风","win_speed":"<3级"},{"hours":"23

时","wea":"晴","wea_img":"qing","tem":"8","win":"东风","win_speed":"<3级"},

{"hours":"02时","wea":"晴","wea_img":"qing","tem":"6","win":"西北风","win_speed":"

<3级"},{"hours":"05时","wea":"晴","wea_img":"qing","tem":"7","win":"南

风","win_speed":"<3级"}],"index":[{"title":"紫外线指数","level":"强","desc":"涂擦SPF大于15、PA+防晒护肤品。"},{"title":"减肥指数","level":"较适宜","desc":"天气凉,在户外运动

请注意增减衣物。"},{"title":"血糖指数","level":"极不易发","desc":"无需担心过敏,可放心外

出,享受生活。"},{"title":"穿衣指数","level":"较冷","desc":"建议着厚外套加毛衣等服装。"},

{"title":"洗车指数","level":"适宜","desc":"天气较好,适合擦洗汽车。"},{"title":"空气污染扩散指数","level":"中","desc":"易感人群应适当减少室外活

动。"}],"uvIndex":"6","uvDescription":"强"},{"day":"27日(星期六)","date":"2024-01-

27","week":"星期六","wea":"多云","wea_img":"yun","wea_day":"多云","wea_day_img":"yun","wea_night":"多

云","wea_night_img":"yun","tem":"16","tem1":"16","tem2":"8","humidity":"53","visi

bility":"","pressure":"","win":["无持续风向","无持续风向"],"win_speed":"<3

级","win_meter":"","sunrise":"06:49","sunset":"17:40","air":"36","air_level":"优",  "air_tips":"","alarm":

{"alarm_type":"","alarm_level":"","alarm_content":""},"hours":[{"hours":"08

时","wea":"晴","wea_img":"qing","tem":"8","win":"西北风","win_speed":"<3级"},

{"hours":"11时","wea":"多云","wea_img":"yun","tem":"12","win":"东南风","win_speed":"<3级"},{"hours":"14时","wea":"多

云","wea_img":"yun","tem":"15","win":"东北风","win_speed":"<3级"},{"hours":"17

时","wea":"多云","wea_img":"yun","tem":"14","win":"东风","win_speed":"<3级"},

{"hours":"20时","wea":"多云","wea_img":"yun","tem":"13","win":"东风","win_speed":"

<3级"},{"hours":"23时","wea":"多云","wea_img":"yun","tem":"12","win":"东南风","win_speed":"<3级"},{"hours":"02时","wea":"多

云","wea_img":"yun","tem":"8","win":"西北风","win_speed":"<3级"},{"hours":"05

时","wea":"多云","wea_img":"yun","tem":"8","win":"南风","win_speed":"<3

级"}],"index":[{"title":"紫外线指数","level":"弱","desc":"辐射较弱,涂擦SPF12-15、

PA+护肤品。"},{"title":"减肥指数","level":"较适宜","desc":"天气凉,在户外运动请注意增减衣

物。"},{"title":"血糖指数","level":"极不易发","desc":"无需担心过敏,可放心外出,享受生

活。"},{"title":"穿衣指数","level":"较冷","desc":"建议着厚外套加毛衣等服装。"},

{"title":"洗车指数","level":"适宜","desc":"天气较好,适合擦洗汽车。"},{"title":"空气污染扩散指数","level":"中","desc":"易感人群应适当减少室外活

动。"}],"uvIndex":"5","uvDescription":"中等"},{"day":"28日(星期日)","date":"2024-

01-28","week":"星期日","wea":"多云","wea_img":"yun","wea_day":"多云","wea_day_img":"yun","wea_night":"多

云","wea_night_img":"yun","tem":"17","tem1":"17","tem2":"10","humidity":"62","vis

ibility":"","pressure":"","win":["无持续风向","无持续风向"],"win_speed":"<3

级","win_meter":"","sunrise":"06:49","sunset":"17:41","air":"33","air_level":"优",  "air_tips":"","alarm":

{"alarm_type":"","alarm_level":"","alarm_content":""},"hours":[{"hours":"08

时","wea":"多云","wea_img":"yun","tem":"10","win":"西北风","win_speed":"<3级"},

{"hours":"11时","wea":"多云","wea_img":"yun","tem":"13","win":"东南风","win_speed":"<3级"},{"hours":"14时","wea":"多

云","wea_img":"yun","tem":"16","win":"东风","win_speed":"<3级"},{"hours":"17

时","wea":"多云","wea_img":"yun","tem":"15","win":"东风","win_speed":"<3级"},

7.6 软件开发网络通信架构

7.6.1BS架构/CS架构

在计算机网络和软件开发中,CS架构(Client-Server  Architecture,客户端-服务器架构)和BS架构

(Browser-Server  Architecture,浏览器-服务器架构)是两种主要的应用程序架构。

CS架构(客户端-服务器架构)

CS架构是一种典型的两层结构,包括客户端和服务器两个部分。在这种架构中,客户端和服务器通过网络进行通信,每部分都有明确的职责。

1.客户端:

 用户界面通常在客户端呈现。

  可以是桌面应用程序、移动应用或专用软件。

  负责向服务器发送请求,接收和处理服务器响应。

2.服务器:

 管理数据和业务逻辑。

  处理来自客户端的请求,并发送回响应。

  通常承载在远程系统上,如数据库服务器、应用服务器等。

3.特点:

 需要为每种操作系统或平台单独开发客户端。  高效的数据处理和响应能力。

  在客户端设备上占用资源(如内存和处理能力)。

BS架构(浏览器-服务器架构)

BS架构是一种基于Web的三层或多层架构,主要通过Web浏览器作为客户端访问服务器上的应用程序。

1.浏览器(客户端):

  使用标准Web浏览器(如Chrome、Firefox等)作为客户端。

 无需安装额外的软件,使用HTML、CSS和JavaScript显示内容。

2.服务器:

 和CS架构中的服务器类似,处理业务逻辑和数据存储。  通过Web服务(如HTTP服务器)提供页面和数据。

3.特点:

 跨平台兼容性强,可以在任何支持Web浏览器的设备上运行。  客户端无需安装专用软件,容易维护和更新。

  可能依赖网络性能,因为所有操作都在服务器上进行。

对比

  部署和维护:BS架构易于部署和维护,而CS架构通常需要在每个客户端单独安装和更新。

 性能:CS架构可以更有效地利用客户端的计算资源,适合高性能要求的应用。BS架构依赖于服务器的性能和网络延迟。

 安全性:CS架构中,数据经常在客户端和服务器之间传输,可能需要更复杂的安全措施。BS架构中,敏感数据主要存储在服务器端。

 用户体验:CS架构通常能提供更丰富的用户界面和交互功能。BS架构的用户体验受限于Web技术的能力。

在实际应用中,选择哪种架构取决于具体的业务需求、目标用户群、性能要求以及开发和维护的成本。

7.6.2HTTP基本概念

HTTP(超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。它是万维网

(WWW)的数据通信的基础。了解HTTP的基本概念对于理解现代网络通信至关重要。以下是HTTP的一些核心概念:

1.请求和响应

HTTP是一个基于请求-响应模式的协议。客户端(通常是Web浏览器)向服务器发送一个HTTP请求,然后服务器返回一个HTTP响应。请求包含请求的资源(如网页),而响应包含请求的资源的内容。

2.HTTP方法

HTTP定义了一系列的方法来表明对资源的不同操作,最常用的包括:

 GET: 用于请求资源。

 POST: 用于提交数据给服务器(例如,表单数据)。

 PUT: 用于上传文件或内容。

 DELETE: 用于请求删除资源。

 HEAD: 用于获取资源的元信息,而不是资源本身。

3.状态码

服务器对请求的响应中包含一个状态码,它表示请求的成功或失败,以及失败的原因。常见的状态码包括:

200 OK: 请求成功。

404 Not Found: 请求的资源未找到。

500 Internal Server Error: 服务器内部错误。

 301 Moved Permanently: 请求的资源已永久移动到新位置。

4.URL(统一资源定位符)

URL是Web上资源的地址。它指定了资源的位置以及用于访问资源的协议(例如,http://)。

5.HTTP头

HTTP请求和响应包含头部信息,这些信息包括元数据,如内容类型、内容长度、服务器信息、客户端信息等。例如, Content-Type 头部指示响应中的媒体类型(如text/html,application/json)。

6.无状态协议

HTTP是一个无状态协议,这意味着服务器不会保留任何请求的数据(状态)。然而,通过使用如 Cookies这样的机制,可以在多个请求之间维持状态。

7.安全性(HTTPS)

HTTPS是HTTP的安全版本,它在HTTP和TCP层之间增加了一个加密层(通常是SSL/TLS)。这提供了数据传输的加密和更好的安全性。

8.RESTful API

RESTful是一种使用HTTP协议的Web服务设计风格,它利用HTTP的方法来实现API的不同操作。在 RESTful架构中,每个URL代表一个资源,并使用HTTP的方法(如GET, POST)来处理这些资源。

9.Session和Cookies

由于HTTP本身是无状态的,Cookies和会话(Session)被用来在多个请求之间存储用户数据,从而为用户提供连贯的体验。

这些概念构成了HTTP的基础,是理解和使用HTTP协议的关键。每个概念都有它的具体细节和使用场景,了解这些有助于更好地在网络应用开发中应用HTTP。

7.7 QT的HTTP编程

Qt中的HTTP编程主要涉及使用Qt的网络模块来进行HTTP请求和处理HTTP响应。Qt提供了一系列类来处理网络通信,其中最常用的类是QNetworkAccessManager 、 QNetworkRequest 、 QNetworkReply 以及相关的支持类。

以下是一个基本的HTTP编程示例,展示了如何使用Qt发送一个简单的HTTP GET请求并处理响应:

步骤 1: 包含必要的头文件

步骤 2: 发送HTTP请求

创建一个QNetworkAccessManager 对象,并使用它发送HTTP请求。 QNetworkAccessManager 对象会异步地处理请求,并返回一个QNetworkReply 对象。

在这个例子中,我们使用QNetworkAccessManager 的get 方法发送了一个HTTP GET请求到"http://exa mple.com"。然后,我们连接了QNetworkReply 对象的finished 信号到一个lambda函数,该函数在收到HTTP响应时被调用。

注意事项

1.异步处理: QNetworkAccessManager 的请求是异步的。这意味着get 方法会立即返回,而HTTP响应将在稍后通过信号处理。

2.错误处理: 应该检查QNetworkReply 对象是否有错误,并相应地处理。

3.内存管理:  QNetworkReply 对象需要被正确地管理,以避免内存泄漏。通常情况下,使用

QObject::deleteLater 来安排删除它是一个好方法。

7.8 JSON数据

7.8.1概述

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它易于人阅读和编写,同时也易于机器解析和生成。JSON是基于JavaScript的一个子集,尽管它是独立于语言的,且有多种语言支持。 JSON常用于网络应用程序中的数据传输,尤其是在Web应用程序中与后端服务器通信。

使用JSON的原因总结如下:

原因 描述

易于阅读和编写 JSON的结构简单、清晰,对人类来说易于阅读和编写。

轻量级数据格式 相较于XML等标记语言,JSON更轻量,使用更少的符号,数据体积更小。

易于解析和生成 大多数编程语言都提供了解析和生成JSON的内置支持或库。

跨语言支持 JSON是独立于语言的,被广泛支持和使用在多种编程语言中。

原因 描述

网络友好 JSON格式适合Web环境,易于通过网络传输,是Web API的常用格式。

数据互操作性 作为一种标准化格式,JSON提高了不同系统间的数据互操作性。

BS/CS开发过程中,会使用不同的编程语言,JSON作为数据传输的标准化格式,方便程序员协议约定和数据处理,以下是不同编程语言处理JSON的方案

语言/平台 JSON处理库/接口 特点/描述

C Jansson 提供JSON的编码、解码和处理功能

C++ nlohmann/json 现代C++(从C++11开始)的JSON库,易于使用

Java Jackson 强大的JSON处理库,支持JSON的序列化和反序列化

Gson Google提供的JSON序列化/反序列化库

Python json Python标准库中的JSON处理模块

Qt QJsonDocument Qt框架中用于JSON处理的类

QJsonObject 用于表示JSON对象的Qt类

QJsonArray 用于表示JSON数组的Qt类

Android org.json Android SDK自带的JSON处理类,提供基础JSON操作功能

iOS JSONSerialization Apple提供的用于JSON处理的类,部分Swift和Objective-C标准库中

7.8.2QT生成JSON数据

在Qt中生成JSON数据并将其保存到文件的一个基本示例涉及使用 QJsonDocument 、 QJsonObject 和

QJsonArray 类。以下是创建一个简单JSON对象并将其保存到文件的示例代码。

说明

1.创建JSON对象:使用 QJsonObject 来构建JSON对象,并使用键值对填充数据。

2.创建JSON数组:使用 QJsonArray 来创建一个数组,并添加元素。

3.组合JSON结构:将JSON数组添加到JSON对象中。

4.生成JSON文档:通过 QJsonDocument 来处理JSON数据,可以选择格式化(缩进)或压缩形式。

5.保存到文件:创建QFile 对象,打开文件,写入JSON数据,并关闭文件。

这个例子展示了Qt中处理JSON的基础流程,包括创建、填充数据、转换为字符串,以及写入文件。您可以根据需要调整这个流程来适应更复杂的JSON结构或数据。

在JSON中,数组可以包含多种类型的元素,包括对象。当您在Qt中处理JSON数组,其中的元素是对象时,您可以使用QJsonArray 和QJsonObject 来创建和处理这些数据结构。以下是一个示例,展示了如何创建一个包含多个对象的JSON数组,并将该数组添加到一个JSON对象中。

示例代码

rootObj["tmp"] = 3;

//Json数组

QJsonArray jsonArray; jsonArray.append("data1"); jsonArray.append("data2"); jsonArray.append("data3"); jsonArray.append(100);

rootObj["testArry"] = jsonArray;

QJsonObject alarmObj; alarmObj["alamType"] = "雪灾"; alarmObj["alamLeve"] = "黄色";

alarmObj["alamTitle"] = "福州市警告老陈多穿点衣服";

rootObj["alam"] = alarmObj;

QJsonObject day0; day0["day"] = "星期一";

day0["wea"] = "晴";

day0["tem"] = 5.7;

QJsonObject day1; day1["day"] = "星期二";

day1["wea"] = "晴";

day1["tem"] = 7;

QJsonObject day2; day2["day"] = "星期三";

day2["wea"] = "多云";

day2["tem"] = 17;

QJsonArray dayArray; dayArray.append(day0); dayArray.append(day1); dayArray.append(day2);

rootObj["days"] = dayArray;

//通过QJsonDocument把JSON数据转换成QByteArray QJsonDocument jsonDoc(rootObj);

QByteArray jsonArry = jsonDoc.toJson();

QFile file("D:/QT/test.json"); file.open(QIODevice::WriteOnly); file.write(jsonArry); file.close();

}

Widget::~Widget()

{

delete ui;

}

7.8.3QT解析JSON数据

在Qt中解析JSON数据通常涉及到使用 QJsonDocument 、 QJsonObject 和QJsonArray 类。这些类提供了处理JSON数据的必要工具,使您能够从JSON字符串中提取信息、遍历JSON对象或数组,并访问具体的数据项。以下是一个基本的示例,展示了如何在Qt中解析JSON字符串。

示例:解析JSON字符串

假设您有一个JSON字符串,例如:

以下是如何在Qt中解析这个JSON字符串的步骤:

说明

1.字符串转换为 QJsonDocument :使用QJsonDocument::fromJson 方法将JSON字符串转换为

QJsonDocument 对象。

2.提取 QJsonObject :如果QJsonDocument 包含一个JSON对象,使用object() 方法获取它。

3.访问对象数据:使用键(如"name" 、"age" )访问QJsonObject 中的数据。

4.处理数组:如果对象包含一个数组,使用QJsonArray 来遍历数组中的元素。

这个示例提供了一个基础框架,用于在Qt中解析和处理JSON数据。您可以根据实际需要调整这个过程,以适应不同的JSON结构和数据类型。

在Qt中,如果你想要将JSON数据解析到一个 QMap 中,你可以遍历JSON对象的所有键值对,并将它们添加到QMap 里。这个方法特别适合于当你的JSON对象是一个简单的键值对集合时。以下是一个如何实现这一点的示例。

示例:将JSON数据解析到QMap中

假设你有以下JSON数据:

以下是如何将这些数据解析到QMap<QString, QString> 中的步骤:

说明

1.从JSON字符串创建 QJsonDocument :使用QJsonDocument::fromJson 来解析JSON字符串。

2.创建 QMap :定义一个QMap<QString, QString> 来存储键值对。

3.遍历JSON对象:使用 keys() 方法获取所有键,然后遍历这些键,将对应的值添加到QMap 中。

4.打印 QMap 内容:遍历QMap 并打印键值对。

这个示例展示了如何将JSON对象的键值对解析到QMap 中。这种方法适用于键值对类型的简单JSON对象。对于更复杂的JSON结构,可能需要更详细的解析逻辑。

解析如下JSON

int tempretrue = jsonRoot["tmp"].toInt();

qDebug() << strW; qDebug() << strCityId;

qDebug() << QString::number(tempretrue);

//第五步:判读是否是一个数组

if(jsonRoot.contains("testArry") && jsonRoot["testArry"].isArray()){ qDebug() << "array";

//如果是数组,转换成JSON数组

QJsonArray testArray = jsonRoot["testArry"].toArray();

//遍历数组,访问每一项 for(QJsonValue val : testArray){

//QJsonValue的type函数返回数据类型,根据不同的数据类型处理数据

// QJsonValue::Type t = val.type(); switch (val.type()) {

case QJsonValue::Double:

qDebug() << QString::number(val.toDouble()); break;

case QJsonValue::String: qDebug() << val.toString(); break;

case QJsonValue::Object: break;

}

}

}

//第六步:判断某个键对应的值,是否是一个json对象 if(jsonRoot.contains("alam") && jsonRoot["alam"].isObject()){

//转成Json对象后处理

QJsonObject alamObj = jsonRoot["alam"].toObject(); qDebug() << alamObj["alamLeve"].toString(); qDebug() << alamObj["alamTitle"].toString(); qDebug() << alamObj["alamType"].toString();

}

if(jsonRoot.contains("days") && jsonRoot["days"].isArray()){ QJsonArray dayArray = jsonRoot["days"].toArray(); for(QJsonValue val : dayArray){

//if(val.type() ==QJsonValue::Object ){ if(val.isObject()){

QJsonObject obj = val.toObject();

qDebug() << obj["day"].toString();

qDebug() << QString::number(obj["tem"].toDouble()); qDebug() << obj["wea"].toString();

}

}

}

}

}

Widget::~Widget()

{

delete ui;

}

天气类型和图标

//根据keys,设置icon的路径

mTypeMap.insert("暴雪",":/res/type/BaoXue.png"); mTypeMap.insert("暴雨",":/res/type/BaoYu. png"); mTypeMap.insert("暴雨到大暴雨",":/res/type/BaoYuDaoDaBaoYu.png"); mTypeMap.insert("大暴雨",":/res/type/DaBaoYu.png");

mTypeMap.insert("大暴雨到特大暴雨",":/res/type/DaBaoYuDaoTeDaBaoYu.png"); mTypeMap.insert("大到暴雪",":/res/type/DaDaoBaoXue.png"); mTypeMap.insert("大雪",":/res/type/DaXue.png");

mTypeMap.insert("大雨",":/res/type/DaYu.png"); mTypeMap.insert("冻雨",":/res/type/DongYu.png"); mTypeMap.insert("多云",":/res/type/DuoYun.png"); mTypeMap.insert("浮沉",":/res/type/FuChen.png"); mTypeMap.insert("雷阵雨",":/res/type/LeiZhenYu.png");

mTypeMap.insert("雷阵雨伴有冰雹",":/res/type/LeiZhenYuBanYouBingBao.png"); mTypeMap.insert("霾",":/res/type/Mai.png");

mTypeMap.insert("强沙尘暴",":/res/type/QiangShaChenBao.png"); mTypeMap.insert("晴",":/res/type/Qing.png"); mTypeMap.insert("沙尘暴",":/res/type/ShaChenBao.png"); mTypeMap.insert("特大暴雨",":/res/type/TeDaBaoYu.png");

mTypeMap.insert("undefined",":/res/type/undefined.png");

mTypeMap.insert("雾",":/res/type/Wu.png");  mTypeMap.insert("小到中雪",":/res/type/XiaoDaoZhongXue.png"); mTypeMap.insert("小到中雨",":/res/type/XiaoDaoZhongYu.png"); mTypeMap.insert("小雪",":/res/type/XiaoXue.png"); mTypeMap.insert("小雨",":/res/type/XiaoYu.png"); mTypeMap.insert("雪",":/res/type/Xue.png"); mTypeMap.insert("扬沙",":/res/type/YangSha.png"); mTypeMap.insert("阴",":/res/type/Yin.png"); mTypeMap.insert("雨",":/res/type/Yu.png"); mTypeMap.insert("雨夹雪",":/res/type/YuJiaXue.png"); mTypeMap.insert("阵雪",":/res/type/ZhenXue.png"); mTypeMap.insert("阵雨",":/res/type/ZhenYu.png"); mTypeMap.insert("中到大雪",":/res/type/ZhongDaoDaXue.png"); mTypeMap.insert("中到大雨",":/res/type/ZhongDaoDaYu.png"); mTypeMap.insert("中雪",":/res/type/ZhongXue.png"); mTypeMap.insert("中雨",":/res/type/ZhongYu.png");

P8  Ubuntu搭建QT开发环境

8.1 安装Ubutnu22

8.1.1下载和安装Vmware

使用我们提供的安装包或者使用如下地址进行下载

官方下载网址: https://download3.vmware.com/software/WKST-1700-WIN/VMware-workstation-fu ll-17.0.0-20800274.exe

支持正版

如果你的电脑已经安装低版本的VMware,千万不要卸载,直接覆盖安装,更新到17的版本

8.1.2下载和安装Ubuntu22

使用我们提供的镜像

安装的过程看视频教程,安装之后打开如下图所示

8.1.3常用功能配置

先掌握如下命令

 查看命令所在目录

 查看当前目录下的所有文件和文件夹  创建文件夹

 复制拷贝文件

 复制拷贝文件夹

  删除文件 rm 文件名 ,如果删除文件夹 加 -rf选项

  特别有用!!善于使用tab键,能自动补全文件名或者命令名,不会造成输入错误

网络配置

  正常电脑就一台虚拟机,通过以上安装后,就能正常上网

 多虚拟机情况,桥接模式冲突,配置不稳定,选择NAT共享主机网络上网  获取IP地址通过命令 ip addr

共享文件配置

  windows需要传文件给虚拟机

 可以通过共享文件夹的方式  可以通过网络的方式

编译环境

默认情况,系统不带编译环境,通过以下命令安装环境

安装VMware Tool

 和windows系统的文本复制黏贴打通  共享文件夹

配置支持ubuntu远程登录

8.2 安装Ubuntu环境下的QT

8.2.1下载安装UbuntuQT

下载QT通过如下命令

wget     https://download.qt.io/archive/qt/5.12/5.12.9/qt-opensource-linux-x64-5.12.9.run

但是速度非常慢

直接去官网下载或者提供我们使用的安装包,把它通过共享文件夹拉倒Ubuntu中使用

安装QT

通过如下命令启动QT的安装程序,在此之前和Windows一样,先让Ubuntu断网配置选择需要安装的项目如下,和Windows差不多

运行QT

通过如下命令: /opt/Qt5.12.9/Tools/QtCreator/bin/qtcreator.sh & 这里的“&”符号代表后台运行,不占用控制命令终端

8.2.2Ubuntu中文支持

配置Ubuntu的中文环境

配置apt下载的服务器源,选择阿里

更新源 sudo apt-get update

在setting设置那边打开Region&Language设置

选择语言安装,在窗口中选择Chinese(simplified)

勾选Chinese,重启

配置Ubuntu支持中文输入

安装拼音输入法

sudo apt-get install fcitx-sunpinyin

设置输入法

点击应用到整个系统,关闭,重启

拷贝我分享的so文件到QT相关路径,让QT支持输入中文!非常重要!!!用系统自带的不行。

P9 加餐课

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

范纹杉好好生活工作

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值