当前位置: 首页 > news >正文

QT系统相关

程序是运行在操作系统上的,需要操作系统给我们提供支撑,Qt是跨平台的C++开发框架,Qt封装了操作系统API,所以需要学习Qt系统提供的API

1.Qt中的事件,是一个图形化界面中用户操作和程序交互的一个核心机制,Qt中的事件是对系统中的事件的一个封装。

2.Qt中的文件操作,C++标准库也提供了文件操作,但是Qt提供的文件操作更加丰富。

3.Qt中的多线程编程

4.Qt中的网络编程

5.Qt中的多媒体,例如视频和音频的播放

1.Qt中的事件

信号槽,用户进行的各种操作,就有可能会产生出信号,可以给莫个信号指定槽函数,当信号触发时,就能够自动的执行到对应的槽函数。

事件非常类似,用户进行的各种操作,也会产生事件,程序员同样可以给事件关联上处理函数(处理的逻辑),当事件触发的时候,就能够执行到对应的代码。

事件本身就是操作系统提供的机制,Qt也同样把操作系统事件机制进行了封装,拿到了Qt中,但是由于时间对应的代码编写起来不是很方便,Qt对于事件机制又进行了进一步的封装,就得到了信号槽。

实际Qt开发程序过程中,绝大部分和用户之间的交互都是通过“信号槽”来完成的,有些特殊情况下,信号槽不一定能够搞定,用户的某个动作行为,Qt没有提供对应的信号,此时就需要通过重写事件处理函数的形式,来手动处理事件的响应逻辑。常见的 Qt 事件如下:

常见事件描述:

鼠标事件鼠标左键、鼠标右键、鼠标滚轮,鼠标的移动,鼠标按键的按下和松开
键盘事件按键类型、按键按下、按键松开
定时器事件定时时间到达
进入离开事件鼠标的进入和离开
滚轮事件鼠标滚轮滚动
绘屏事件重绘屏幕的某些部分
显示隐藏事件窗口的显示和隐藏
移动事件窗口位置的变化
窗口事件是否为当前窗口
大小改变事件窗口大小改变
焦点事件键盘焦点移动
拖拽事件用鼠标进行拖拽

2.Qt中的文件操作

QIODevice 的父类为 QObject 。QIODevice 是 Qt 中所有输入输出设备(input/output device,简称 I/O 设备)的基础类,I/O 设备就是能进行数据输入和输出的设备,例如文件是一种 I/O 设备,网络通信中的 socket 是 I/O 设备, 串口、蓝牙等通信接口也是 I/O 设备,所以它们也是从 QIODevice 继承来的。Qt 中主要的一些 I/O 设备类的继承关系如下图所示:

C和C++都提供了自己的文件操作,但是在编写Qt程序的时候,更推荐使用Qt自己提供的这套文件操作,和QString等Qt内置的类可以很好的配合。

2.1文件读写

构造文件对象

打开文件

使用以下两种方法,需要文件路径和文件描述符,使用起来比较麻烦

在构造函数时指定了文件路径,就可以使用如下版本的open。

QIODevice::NotOpen没有打开设备
QIODevice::ReadOnly以只读方式打开设备
QIODevice::WriteOnly以只写方式打开设备
QIODevice::ReadWrite以读写方式打开设备
QIODevice::Append以追加方式打开设备,数据将写到文件末尾
QIODevice::Truncate每次打开文件后重写文件内容,原内容将被删除
QIODevice::Text在读文件时,行尾终止符会被转换为 ‘\n’;当写入文件时,行尾终止符会被转换为本地编码。如 Win32上为’\r\n’;
QIODevice::Unbuffered无缓冲形式打开文件,绕过设备中的任何缓冲区
QIODevice::NewOnly文件存在则打开失败,不存在则创建文件

读操作 read/readLine/readAll

写操作

关闭文件

代码示例:

QPlainTextEdit是一个功能强大、易于使用的纯文本编辑器/查看器。它使用与QTextEdit相同的技术和概念,但是为纯文本的处理进行了优化,因此更适合处理大型纯文本文档。QPlainTextEdit不提供富文本编辑功能,如字体、颜色、大小等的格式化,而是专注于纯文本的编辑和显示。

2.2文件和目录信息类

QFileInfo 是 Qt 提供的一个用于获取文件和目录信息的类,如获取文件名、文件大小、文件修改日期等。QFileInfo类中提供了很多的方法,常用的有:

  • isDir() 检查该文件是否是目录;
  • isExecutable() 检查该文件是否是可执行文件;
  • fileName() 获得文件名;
  • completeBaseName() 获取完整的文件名;
  • suffix() 获取文件后缀名;
  • completeSuffix() 获取完整的文件后缀;
  • size() 获取文件大小;
  • isFile() 判断是否为文件;
  • fileTime() 获取文件创建时间、修改时间、最近访问时间等;

3.Qt多线程

3.1线程的使用

QThread常用API

run()子类重写父类的run函数
start()这个函数是真正调用系统的API创建线程,新的线程创建出来之后自然就会执行run函数
currentThread()返回线程对象的指针
isRunning()如果线程正在运行则返回true,否则返回false
sleep()/msleep()/usleep()设置线程休眠时间,单位为秒 / 毫秒 / 微秒
wait()阻塞线程,直到满足以下任何一个条件:
与此 QThread 对象关联的线程已经完成执行(即当它从run()返回时)。如果线程已经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。已经过了几毫秒。如果时间是 ULONG_MAX(默认值),那么等待永远不会超时(线程必须从run()返回)。如果等待超时,此函数将返回 false。这提供了与 POSIX pthread_join() 函数类似的功能
terminate()终止线程的执行。线程可以立即终止,也可以不立即终止,这取决于操作系统的调度策略。在terminate() 之后使用 QThread::wait() 来确保
finished()当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作

代码示例:多线程版的计时器

在多线程中,由于对界面上控件的修改存在着线程安全的问题,Qt规定只能在主线中修改控件的状态。

在客户端中多线程的意义

在服务器开发中使用多线程的目的,是为了充分利用多核CPU的计算资源,尤其在服务器端一般是双路CPU(双路CPU是一个主板上面有两个CPU)。

在客户端多线程仍然非常有意义,但是侧重点就不同了。对于普通用户来说,“使用体验”是一个非常重要的话题。现如今普通用户的家用PC上也是多核CPU,但客户端上的程序一般不会使用多线程把CPU计算资源吃完,就算是做很重量级的计算,也不会把CPU计算资源利用完,一旦利用完了,此时系统会很卡,用户的体验会很差。

客户端的多线程最主要不是为了提高程序速度,相比之下,客户端中的多线程,主要是用于,通过多线程的方式,执行一些耗时的等待IO操作,避免主线程被卡死,避免对用户造成不好的体验。例如:

客户端经常会和服务器进行网络通信,比如客户端要上传/下载一个很大的文件,传输需要消耗很久,还有代码中持续不断的进行QFile.write,这种就是一个密集的IO操作,这种密集IO就会使程序被系统阻塞,然后被挂起。一旦程序都被挂起了,此时意味着,用户进行的各种操作,程序都无法响应。在生活中我们使用软件的过程中会遇到这样的场景,Windows提示你这个窗口不能响应,是否要强制结束,这种现象就是程序被挂起来了。

最好的做法,就是使用单独的线程,来处理这种密集的IO操作,主线程负责事件循环、负责处理用户的各项操作,如果被挂,挂起也是只挂起这个新的线程,主线程并不会受到影响,主线程仍然可以继续工作,继续响应用户的各种操作。

3.2线程安全问题

实现线程互斥和同步常用的类有:

  • 互斥锁:QMutex、QMutexLocker
  • 条件变量:QWaitCondition
  • 信号量:QSemaphore
  • 读写锁:QReadLocker、QWriteLocker、QReadWriteLock

3.2.1互斥锁

解决线程安全问题常用的就是加锁,就是把多线程要访问的公共资源,通过锁保护起来,借助这个锁把并发执行=》串行执行,在Linux中提供mutex互斥量来作为锁,到C++11开始把锁融入到标准库中std::mutex,把系统中的锁进行封装,Qt同样提供了锁,来对系统中的锁进行封装QMutex。

案例:对一个变量,通过两个线程循环对变量进行加加

多个线程进行加锁的对象,得是同一个锁对象。不同锁对象,就不会产生锁的互斥,也就无法把并发执行=》串行执行,也就无法解决上述问题。

在实际开发中,加锁之后,涉及到的逻辑可能很复杂,可能会忘记释放锁。使用QMutexLocker对互斥锁进行智能管理。

3.2.2读写锁

QReadWriteLocker、QReadLocker、QWriteLocker

特点:

  • QReadWriteLock 是读写锁类,用于控制读和写的并发访问。
  • QReadLocker 用于读操作上锁,只允许一个线程读取共享资源。
  • QWriteLocker 用于写操作上锁,只允许一个线程写入共享资源。

用途:在某些情况下,多个线程可以同时读取共享数据,但只有一个线程能够进行写操作。读写锁提供了更高效的并发访问方式。

QReadWriteLock rwLock;
//在读操作中使用读锁
{QReadLocker locker(&rwLock); //在作用域内自动上读锁//读取共享资源//...
} //在作用域结束时自动解读锁//在写操作中使用写锁
{QWriteLocker locker(&rwLock); //在作用域内自动上写锁//修改共享资源//...
}//在作用域结束时自动解写锁

3.2.3条件变量

在多线程编程中,假设除了等待操作系统正在执行的线程之外,某个线程还必须等待某些条件满足才能执行,这时就会出现问题。这种情况下,线程会很自然地使用锁的机制来阻塞其他线程,因为这只是线程的轮流使用,并且该线程等待某些特定条件,人们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进入了睡眠状态,这样其他线程就可以继续运行。当条件满足时,等待条件的线程将被另一个线程唤醒。在 Qt 中,专门提供了 QWaitCondition类 来解决像上述这样的问题。

特点:QWaitCondition 是 Qt 框架提供的条件变量类,用于线程之间的消息通信和同步。

用途:在某个条件满足时等待或唤醒线程,用于线程的同步和协调。

QMutex mutex;
QWaitCondition condition;
//在等待线程中
mutex.lock();
//检查条件是否满足,若不满足则等待
while (!conditionFullfilled())
{condition.wait(&mutex); //等待条件满足并释放锁
}
//条件满足后继续执行
//...
mutex.unlock();
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();

3.2.4 信号量

有时在多线程编程中,需要确保多个线程可以相应的访问一个数量有限的相同资源。例如,运行程序的设备可能是非常有限的内存,因此我们更希望需要大量内存的线程将这一事实考虑在内,并根据可用的内存数量进行相关操作,多线程编程中类似问题通常用信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,而且可以跟踪可用资源的数量。

特点:QSemaphore 是 Qt 框架提供的计数信号量类,用于控制同时访问共享资源的线程数量。

用途:限制并发线程数量,用于解决一些资源有限的问题。

QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另一个线程中进行类似操作

4.Qt中的网络

和多线程类似, Qt 为了支持跨平台, 对网络编程的 API 也进行了重新封装。接下来介绍 Qt 的网络相关的 API 的使用。实际 Qt 开发中进行网络编程, 也不一定使用 Qt 封装的网络 API, 也有一定可能使用的是系统原生 API 或者其他第三方框架的 API。

进行网络编程的时候,本质上是在编写应用层代码,需要传输层提供支持,而传输层操作系统已经实现好了,并给我们提供了相关接口。传输层最核心的协议有UDP和TCP,UDP无连接不可靠传输面向数据报全双工,TCP有连接可靠传输面向字节流全双工,操作系统给我们提供了两套Socket API。

在使用Qt中的网络API之前, 需要在项目中的.pro 文件中添加 network 模块.添加之后要手动编译一下项目, 使 Qt Creator 能够加载对应模块的头文件。之前学过的各种控件,各种内容,都包含在QtCore模块中(该模块默认就被添加了),与之对应的Qt还提供了其他模块,要想使用相关内容,就需要先添加对应的模块。

为什么Qt要分模块?

Qt框架本身是非常庞大的,如果把Qt中所有的功能放到一起,即使写一个简单的hello world程序,生成的可执行程序也非常庞大,这里包含了大量没有使用的功能。Qt还有一个典型的应用场景,就是应用在嵌入式的系统上,嵌入式系统的配置比较低,一个简单的程序,却非常的大,在嵌入式系统上可能无法运行。

Qt提供了静态库的版本,还提供了动态库的版本,如果使用的是静态库版本,那在.pro工程文件中添加的模块在编译时会被全都添加到.exe文件中。

4.1UDP

4.1.1 核心 API

主要的类有两个QUdpSocket 和 QNetworkDatagram(数据报,每次发送和接受都必须是一个完整的数据报)

QUdpSocket 表示一个 UDP 的 socket 文件

名称类型说明对标原生API
bind(
const QHostAddress&, quint16)
方法绑定指定的端口号bind
receiveDatagram()方法返回QNetworkDatagram读取一个 UDP 数据报recvfrom
writeDatagram(
const QNetworkDatagram&)
方法发送一个 UDP 数据报sendto
readyRead信号在收到数据并准备就绪后触发.无 (类似于 IO 多路复用的通知机制)

系统原生的API接口中recvfrom是阻塞IO,当客户端一直没有发送数据,recvfrom会一直阻塞,直到有请求。基于阻塞模型来实现的服务器并不是很好,一阻塞就会导致整个线程卡在这里,其他什么操作也做不了,如果利用上这一时间做一些其他的操作可能会更好,Qt就利用了信号和槽机制。

在QUdpSocket类中就提供了readRead信号,当socket收到请求的时候,就会触发readyRead ,在对应的槽函数中实现读取请求操作。

QNetworkDatagram 表示一个 UDP 数据报

名称类型说明对标原生 API
QNetworkDatagram
(const QByteArray&,
const QHostAddress& ,
quint16 )
构造函数通过 QByteArray , 目标 IP 地址,目标端口号 构造一个 UDP 数据报,通常用于发送数据时
data()方法获取数据报内部持有的数据,返回QByteArray
senderAddress()方法获取数据报中包含的对端的 IP 地址无,recvfrom 包含了该功能
senderPort()方法获取数据报中包含的对端的端口号无,recvfrom 包含了该功能

在构造QUdpSocket对象时可以指定一个父对象

4.1.2回显服务器

“根据请求处理响应” 是服务器开发中的最核心的步骤,一个商业服务器程序,这里的逻辑可能是几万行几十万行代码量级的。

此时,服务器程序编写完毕,但是直接运行还看不出效果. 还需要搭配客户端来使用。

4.1.3回显客户端

  1. 创建界面,含一个 QLineEdit , QPushButton , QListWidget
  • 先使用水平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的izePolicy 为 Expanding
  • 再使用垂直布局把 QListWidget 和上面的水平布局放好.
  • 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺寸比例根据个人喜好微调)

  1. 在 mainwindow.cpp 中, 先创建两个全局常量, 表示服务器的 IP 和 端口
// 提前定义好服务器的 IP 和 端口
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;
  1. 创建 QUdpSocket 成员

修改 mainwindow.h 定义成员

修改mainwindow.cpp, 初始化 socket

  1. 给发送按钮 slot 函数,实现发送请求

  1. 通过信号槽,来处理服务器的响应

最终执行效果

启动多个客户端都可以正常工作

4.2TCP

4.2.1 核心 API

核心类是两个: QTcpServer 和 QTcpSocket

QTcpServer 用于监听端口和获取客户端连接

名称类型说明对标原生 API
listen(const QHostAddress&, quint16 port)方法绑定指定的地址和端口号,并开始监听bind 和 listen
nextPendingConnection()方法从系统中获取到一个已经建立好的返回一个 QTcpSocket,表示这个tcp 连接,客户端的连接通过这个 socket 对象完成和客户端之间的通信accept
newConnection信号有新的客户端建立连接好之后触发无 (但是类似于 IO 多路复用中的通知机制)

QTcpSocket 用户客户端和服务器之间的数据交互

名称类型说明对标原生 API
readAll()方法读取当前接收缓冲区中的所有数据,返回 QByteArray 对象read
write(const QByteArray& )方法把数据写入 socket 中write
readyRead信号有数据到达并准备就绪时触发无 (但是类似于 IO 多路复用中的通知机制)
deleteLater方法暂时把 socket 对象标记为无效,Qt会在下个事件循环中析构释放该对象无 (但是类似于 “半自动化的垃圾回收”)
disconnected信号连接断开时触发无 (但是类似于 IO 多路复用中的通知机制)

QByteArray 用于表示一个字节数组,可以很方便的和 QString 进行相互转换

  • 使用 QString 的构造函数即可把 QByteArray 转成 QString
  • 使用 QString 的 toUtf8 函数即可把 QString 转成 QByteArray

4.2.2 TCP回显服务器(请求的内容是什么就回显什么内容)

4.2.3TCP回显客户端

先启动服务器, 再启动客户端(可以启动多个),最终执行效果

4.3HTTP

4.3.1 核心 API

关键类主要是三个,QNetworkAccessManager 、QNetworkRequest、QNetworkReply

QNetworkAccessManager 提供了 HTTP 的核心操作

方法说明
get(const QNetworkRequest& )发起一个 HTTP GET 请求,返回 QNetworkReply 对象
post(const QNetworkRequest&,
const QByteArray& )
发起一个 HTTP POST 请求,返回 QNetworkReply 对象

QNetworkRequest 表示一个 HTTP 请求(不含 body)

如果需要发送一个带有 body 的请求(比如 post),会在 QNetworkAccessManager 的 post 方法中通过单独的参数来传入 body。

方法说明
QNetworkRequest(const QUrl& )通过 URL 构造一个 HTTP 请求
setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value)设置请求头
  • QVariant表示一个可变类型,可以向value传输任何的值

其中的 QNetworkRequest::KnownHeaders 是一个枚举类型,常用取值:

取值说明
ContentTypeHeader描述 body 的类型
ContentLengthHeader描述 body 的长度
LocationHeader用于重定向报文中指定重定向地址. (响应中使用,请求用不到)
CookieHeader设置 cookie
UserAgentHeader设置 User-Agent

QNetworkReply 表示一个 HTTP 响应,这个类同时也是 QIODevice 的子类

方法说明
error()获取出错状态
errorString()获取出错原因的文本
readAll()读取响应 body
header(QNetworkRequest::KnownHeaders
header)
读取响应指定 header 的值

此外,QNetworkReply 还有一个重要的信号 finished 会在客户端收到完整的响应数据之后触发

4.3.2代码示例

给服务器发送一个GET请求

  1. 先来构建界面

将水平布局中的两个控件的垂直策略改为充满

此处建议使用 QPlainTextEdit 而不是 QTextEdit,主要因为 QTextEdit是支持HTML解析的,会对HTML进行解析和渲染的,如果得到的 HTTP 响应体积很大,就会导致界面渲染缓慢甚至被卡住。

  1. 编写代码

访问百度的网页

在实际开发中HTTP Client也不一定得到的非得是HTML,更多的是客户端和服务器开发约定好交互的数据格式。按照约定好的格式,客户端拿到之后,进行解析,并显示到界面上。

4.4 其他模块

Qt 中还提供了 FTP, DNS, SSL 等网络相关的组件工具,需要时可以翻阅官方文档学习相关 API 的使用

5.Qt中音视频

5.1播放声音

在 Qt 中,音频主要是通过 QSound 类来实现。但是需要注意的是 QSound 类只支持播放 wav 格式的音频文件。也就是说如果想要添加音频效果,那么首先需要将 非wav格式 的音频文件转换为 wav 格式。使用 QSound 类时,需要添加模块:multimedia。

play()开始或继续播放当前源。

代码示例:

5.2播放视频

5.2.1核心API

在 Qt 中,视频播放的功能主要是通过 QMediaPlayer类 和 QVideoWidget类 来实现。在使用这两个类时要添加对应的模块 multimedia 和 multimediawidgets。

setMedia()设置当前媒体源。
setVideoOutput()将QVideoWidget视频输出附加到媒体播放器。如果媒体播放器已经附加了视频输出,将更换一个新的。

首先在 .pro 文件中添加 multimedia 和 multimediawidgets 两个模块;如下图示:

http://www.dtcms.com/a/322995.html

相关文章:

  • gpt-oss 全量技术解读
  • Alibaba Cloud Linux 3 安装 git
  • 【Spring Boot启动流程底层源码详解】
  • kubectl get node k8s-node01 -o yaml | grep taint -B 5 -A 5
  • 如何理解SA_RESTART”被信号中断的系统调用自动重启“?
  • 腾讯COS云存储入门
  • 笔试——Day33
  • 基于遗传优化的稀疏线阵最优排布算法matlab仿真
  • Java面向对象编程(OOP)全面解析:从基础到实践
  • 关于城市农村创业的一点构想
  • 自动生成视频的AI大模型高效创作指南
  • mac安装node.js
  • 【GPT入门】第41课 Model Scope在线平台部署Llama3
  • Serper注册无反应
  • Numpy基础(通用函数)
  • 游游的数组染色
  • 洛谷 滑动窗口 /【模板】单调队列
  • 揭秘MyBatis核心类MappedStatement
  • Java异常:认识异常、异常的作用、自定义异常
  • ChatGPT 5的编程能力宣传言过其实
  • 97-基于Python的大众点评数据分析预测系统
  • 七、《Serverless架构:按毫秒计费的成本革命》--从新浪AI推理平台50%效能提升看无服务器本质
  • 数据结构——优先级队列(PriorityQueue):一文解决 Top K 问题!
  • 可视化大屏 SDK 数据结构设计:从拖拽组件到最终渲染的全链路
  • 2025-08-09 李沐深度学习13——经典卷积神经网络 (1)
  • 嵌入式知识日常问题记录及用法总结(一)
  • C++2024 年一级
  • Vue3 学习教程,从入门到精通,Vue 3 + Tailwind CSS 全面知识点与案例详解(31)
  • buuctf:inndy_echo、actf_2019_babystack
  • 花生4CL基因家族鉴定及对干旱与盐胁迫响应分析--文献精读157