文章目录

1. 文件
文件操作是应用程序必不可少的部分。Qt 作为⼀个通用开发库,提供了跨平台的⽂件操作能⼒。 Qt 提供了很多关于文件的类,通过这些类能够对文件系统进行操作,如文件读写、文件信息获取、文件复制或重命名等
1.1 输入输出设备
在 Qt 中,⽂件读写的类为 QFile 。QFile 的⽗类为 QFileDevice ,QFileDevice 提供了⽂件交互操作的底层功能。 QFileDevice 的⽗类是 QIODevice,QIODevice 的⽗类为 QObject 。
QIODevice
是 Qt 中所有输⼊输出设备(input/output device,简称 I/O 设备)的基础类,I/O 设备就是能进⾏数据输⼊和输出的设备,例如⽂件是⼀种 I/O 设备,⽹络通信中的 socket 是 I/O 设备, 串口、蓝⽛等通信接口也是 I/O 设备,所以它们也是从 QIODevice 继承来的。
Qt 中主要的⼀些 I/O 设备类的继承关系如下图所示:
上图中各类的说明如下:
QFile
是⽤于⽂件操作和⽂件数据读写的类,使⽤ QFile 可以读写任意格式的⽂件。QSaveFile
是⽤于安全保存⽂件的类。使⽤ QSaveFile 保存⽂件时,它会先把数据写⼊⼀个临时⽂件,成功提交后才将数据写⼊最终的⽂件。如果保存过程中出现错误,临时⽂件⾥的数据不会被写⼊最终⽂件,这样就能确保最终⽂件中不会丢失数据或被写⼊部分数据。 在保存⽐较⼤的⽂件或复杂格式的⽂件时可以使⽤这个类,例如从⽹络上下载⽂件等。QTemporaryFile
是⽤于创建临时⽂件的类。使⽤函数 QTemporaryFile::open() 就能创建⼀个⽂件名唯⼀的临时⽂件,在 QTemporaryFile 对象被删除时,临时⽂件被⾃动删除。QTcpSocket
和QUdpSocket
是分别实现了 TCP 和 UDP 的类。QSerialPort
是实现了串⼝通信的类,通过这个类可以实现计算机与串⼝设备的通信。QBluetoothSocket
是⽤于蓝⽛通信的类。⼿机和平板计算机等移动设备有蓝⽛通信模块,笔记本电脑⼀般也有蓝⽛通信模块。通过QBluetoothSocket类,就可以编写蓝⽛通信程。如编程实现笔记本电脑与⼿机的蓝⽛通信。QProcess
类⽤于启动外部程序,并且可以给程序传递参数。QBuffer
以⼀个 QByteArray 对象作为数据缓冲区,将 QByteArray 对象当作⼀个 I/O 设备来读写。
1.2 文件读写类
在 Qt 中,文件的读写主要是通过 QFile 类来实现。在 QFile 类中提供了⼀些用来读写文件的方法。对于⽂件的操作主要有:
- 读数据:QFile 类中提供了多个⽅法⽤于读取⽂件内容;如 read()、readAll()、readLine()等。
- 写数据:QFile 类中提供了多个⽅法⽤于往⽂件中写内容;如 write()、writeData()等。
- 关闭⽂件:⽂件使⽤结束后必须⽤函数 close() 关闭⽂件。
访问⼀个设备之前,需要使用 open()函数打开该设备,而且必须指定正确的打开模式,QIODevice 中所有的打开模式由 QIODevice::OpenMode
枚举变量定义,其取值如下:
下面我们通过文件、菜单栏实现一个简单记事本
#include <QMenu>
#include <QMenuBar>
#include <QAction>
#include <QFileDialog>
#include <QFile>
#include <QDebug>
#include <QTextEdit>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QMenuBar* menuBar = new QMenuBar(this);
this->setMenuBar(menuBar);
QMenu* fileMenu = new QMenu("文件");
menuBar->addMenu(fileMenu);
QAction* openAction = new QAction("打开");
QAction* saveAction = new QAction("保存");
fileMenu->addAction(openAction);
fileMenu->addAction(saveAction);
QTextEdit* textEdit = ui->textEdit;
textEdit->setPlaceholderText("请在这里输入内容");
connect(openAction,&QAction::triggered,this,&MainWindow::openFileByDialog);
connect(saveAction,&QAction::triggered,this,&MainWindow::saveFileByDialog);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::openFileByDialog()
{
//通过QFileDialog获取文件
QString filePath = QFileDialog::getOpenFileName();
//通过路径,实例化文件对象
QFile file(filePath);
if(file.open(QIODevice::ReadOnly))
{
QByteArray textArray = file.readAll();
QString textStr = QString::fromUtf8(textArray);
ui->textEdit->setText(textStr);
}
else
{
qDebug() << "文件:" << filePath << " 打开失败";
}
}
void MainWindow::saveFileByDialog()
{
QString filePath = QFileDialog::getSaveFileName();
qDebug() << filePath;
QFile file(filePath);
if(file.open(QIODevice::WriteOnly))
{
QString textStr = ui->textEdit->toPlainText();
QByteArray textArray = textStr.toUtf8();
file.write(textArray);
}
else
{
qDebug() << "文件:" << filePath << " 打开失败";
}
}
void MainWindow::resizeEvent(QResizeEvent* ev)
{
ui->textEdit->setGeometry(0,0,ev->size().width(),ev->size().height());
}
1.3 文件和目录信息类
QFileInfo 是 Qt 提供的⼀个⽤于获取⽂件和⽬录信息的类,如获取⽂件名、⽂件⼤⼩、⽂件修改⽇期等。
QFileInfo类中提供了很多的方法,常用的有:
isDir()
检查该⽂件是否是⽬录;isExecutable()
检查该⽂件是否是可执⾏⽂件;fileName()
获得⽂件名;completeBaseName()
获取完整的⽂件名;suffix()
获取⽂件后缀名;completeSuffix()
获取完整的⽂件后缀;size()
获取⽂件⼤⼩;isFile()
判断是否为⽂件;fileTime()
获取⽂件创建时间、修改时间、最近访问时间等;
2. 多线程
在 Qt 中,多线程的处理⼀般是通过 QThread类 来实现。
QThread 代表⼀个在应⽤程序中可以独⽴控制的线程,也可以和进程中的其他线程共享数据。
函数 | 功能描述 |
---|---|
run() | 线程的⼊⼝函数 |
start() | 通过调⽤ run() 开始执⾏线程。操作系统将根据优先级参数调度线程。如果线程已经在运⾏,这个函数什么也不做。 |
currentThread() | 返回⼀个指向管理当前执⾏线程的 QThread的指针 |
isRunning() | 如果线程正在运⾏则返回true;否则返回false。 |
sleep() / msleep() / usleep() | 使线程休眠,单位为秒 / 毫秒 / 微秒 |
wait() | 阻塞调用它的线程,直到目标线程(即 QThread 对象所代表的线程)结束运行。 这提供了与 POSIX pthread_join() 函数类似的功能。 |
terminate() | 终⽌线程的执⾏。线程可以⽴即终⽌,也可以不⽴即终⽌,这取决于操作系统的调度策略。在terminate() 之后使⽤ QThread::wait() 来确保 |
finished() | 当线程结束时会发出该信号,可以通过该信号来实现线程的清理⼯作 |
2.1 使用
创建线程的步骤:
- ⾃定义⼀个类,继承于 QThread,并且只有⼀个线程处理函数(和主线程不是同⼀个线程),这个线程处理函数主要就是重写⽗类中的 run() 函数。
- 线程处理函数⾥⾯写⼊需要执⾏的复杂数据处理;
- 启动线程不能直接调⽤ run() 函数,需要使⽤对象来调⽤ start() 函数实现线程启动;
- 线程处理函数执⾏结束后可以定义⼀个信号来告诉主线程;
- 最后关闭线程。
我们使用新线程,实现倒计时的案例
connect() 函数第五个参数为 Qt::ConnectionType,⽤于指定信号和槽的连接类型。同时影响信号的传递⽅式和槽函数的执⾏顺序,只有在多线程的时候才意义
Qt::ConnectionType 提供了以下五种⽅式:
客户端采用多线程的用途非常广泛,尤其是在需要同时处理多个任务、提高程序响应性或优化资源利用的场景中。以下是多线程在客户端程序中常见的用途:
- 多线程可以显著提高客户端程序的响应性,尤其是在执行耗时操作时。通过将耗时任务分配到单独的线程中,主线程可以继续处理用户界面(UI)事件,从而避免界面卡顿。
- 文件下载:在下载大文件时,可以将下载任务放在一个单独的线程中,主线程继续响应用户的其他操作(如浏览文件列表、取消下载等)
- 多线程可以用于更高效地管理资源,例如网络连接、数据库访问等。通过在多个线程中共享资源,可以提高资源的利用率。
- 在客户端中,可以使用多个线程同时发起多个网络请求,提高数据获取的速度
- 多线程可以用于实现异步操作,避免阻塞主线程。通过将异步任务放在单独的线程中,主线程可以继续执行其他任务。
- 异步文件读写:在读写大文件时,可以使用单独的线程进行文件操作,主线程继续响应用户操作。
- 异步网络通信:在进行网络通信时,可以使用单独的线程处理网络数据的发送和接收,主线程继续处理用户界面。
- 多线程可以用于模拟复杂的系统或生成动画,主线程负责用户界面的渲染,后台线程负责计算和更新数据。
- 在游戏客户端中,主线程负责渲染游戏画面,后台线程负责游戏逻辑的计算
2.2 线程安全
实现线程互斥和同步常⽤的类有:
- 互斥锁:QMutex、QMutexLocker
- 条件变量:QWaitCondition
- 信号量:QSemaphore
- 读写锁:QReadLocker、QWriteLocker、QReadWriteLock
2.2.1 互斥锁
互斥锁是⼀种保护和防⽌多个线程同时访问同⼀对象实例的⽅法。
- 在 Qt 中,互斥锁主要是通过
QMutex
类来处理。
- 特点:QMutex 是 Qt 框架提供的互斥锁类,⽤于保护共享资源的访问,实现线程间的互斥操作。
- ⽤途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。
QMutex mutex;
mutex.lock(); //上锁
//访问共享资源
//...
mutex.unlock(); //解锁
- QMutexLocker
- 特点:QMutexLocker 是 QMutex 的辅助类,使⽤ RAII⽅式对互斥锁进⾏上锁和解锁操作。
- ⽤途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。
QMutex mutex;
{
QMutexLocker locker(&mutex); //在作⽤域内⾃动上锁
//访问共享资源
//..
} //在作⽤域结束时⾃动解锁
- QReadWriteLocker、QReadLocker、QWriteLocker
特点:
- QReadWriteLock 是读写锁类,⽤于控制读和写的并发访问。
- QReadLocker ⽤于读操作上锁,允许多个线程同时读取共享资源。
- QWriteLocker ⽤于写操作上锁,只允许⼀个线程写⼊共享资源。
⽤途:在某些情况下,多个线程可以同时读取共享数据,但只有⼀个线程能够进⾏写操作。读写锁提供了更⾼效的并发访问⽅式。
2.2.2 条件变量
在多线程编程中,假设除了等待操作系统正在执⾏的线程之外,某个线程还必须等待某些条件满⾜才能执⾏,这时就会出现问题。
这种情况下,线程会很⾃然地使⽤锁的机制来阻塞其他线程,因为这只是线程的轮流使⽤,并且该线程等待某些特定条件,⼈们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进⼊了睡眠状态,这样其他线程就可以继续运⾏。
当条件满足时,等待条件的线程将被另⼀个线程唤醒。
在 Qt 中,专门提供了 QWaitCondition
类 来解决像上述这样的问题。
- 特点:QWaitCondition 是 Qt 框架提供的条件变量类,⽤于线程之间的消息通信和同步。
- ⽤途:在某个条件满⾜时等待或唤醒线程,⽤于线程的同步和协调
QMutex mutex;
QWaitCondition condition;
//....
//在等待线程中
mutex.lock();
//检查条件是否满⾜,若不满⾜则等待
while (!conditionFullfilled())
{
condition.wait(&mutex); //等待条件满⾜并释放锁
}
//条件满⾜后继续执⾏
//...
mutex.unlock();
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();
2.2.3 信号量
有时在多线程编程中,需要确保多个线程可以相应的访问⼀个数量有限的相同资源。
例如,运⾏程序的设备可能是⾮常有限的内存,因此我们更希望需要⼤量内存的线程将这⼀事实考虑在内,并根据可⽤的内存数量进⾏相关操作,多线程编程中类似问题通常⽤信号量来处理。
信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,⽽且可以跟踪可⽤资源的数量。
- 特点:QSemaphore 是 Qt 框架提供的计数信号量类,⽤于控制同时访问共享资源的线程数量。
- ⽤途:限制并发线程数量,⽤于解决⼀些资源有限的问题。
QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中进⾏类似操作
3. 网络
和多线程类似,Qt 为了支持跨平台,对网络编程的 API 也进行了重新封装。
注意:在进⾏⽹络编程之前,需要在项目中的 .pro ⽂件中添加 network 模块。添加之后要⼿动编译⼀下项⽬,使 Qt Creator 能够加载对应模块的头⽂件.
3.1 UDP Socket
主要的类有两个. QUdpSocket 和 QNetworkDatagram
- QUdpSocket 表⽰⼀个 UDP 的 socket ⽂件
名称 | 类型 | 说明 | 对标原生 API |
---|---|---|---|
bind(const QHostAddress&, quint16) | 方法 | 绑定指定的端⼝号 | bind |
receiveDatagram() | 方法 | 返回 QNetworkDatagram . 读取⼀个 UDP 数据报. | recvfrom |
writeDatagram(const QNetworkDatagram&) | 方法 | 发送⼀个 UDP 数据报 | sendto |
readyRead | 信号 | 在收到数据并准备就绪后触发. | ⽆ (类似于 IO 多路复⽤的通知机制) |
- QNetworkDatagram 表示⼀个 UDP 数据报
名称 | 类型 | 说明 | 对标原生 API |
---|---|---|---|
QNetworkDatagram(const QByteArray&, const QHostAddress& , quint16 ) | 构造函数 | 通过 QByteArray , ⽬标 IP 地址, 目标端⼝号 构造⼀个 UDP 数据报. 通常⽤于发送数据时. | 无 |
data() | 方法 | 获取数据报内部持有的数据. 返回 QByteArray | ⽆ |
senderAddress() | 方法 | 获取数据报中包含的对端的 IP 地址. | ⽆, recvfrom 包含了该功能. |
senderPort() | ⽅法 | 获取数据报中包含的对端的端⼝号. | ⽆, recvfrom 包含了该功能. |
下面我们就来使用这些API,实现一个带有客户端、服务端的回显程序
- 回显服务器
- 回显客户端
效果:
3.2 TCP Socket
核心类是两个: QTcpServer 和 QTcpSocket
- QTcpServer ⽤于监听端口,和获取客⼾端连接.
名称 | 类型 | 说明 | 对标原生 API |
---|---|---|---|
listen(const QHostAddress&, quint16 port) | 方法 | 绑定指定的地址和端⼝号, 并开始监听. | bind 和 listen |
nextPendingConnection() | 方法 | 从系统中获取到⼀个已经建⽴好的 tcp 连接. 返回⼀个 QTcpSocket , 表⽰这个客⼾端的连接。通过这个 socket 对象完成和客⼾端之间的通信. | accept |
newConnection | 信号 | 有新的客⼾端建⽴连接好之后触发 | ⽆ (但是类似于 IO 多路复⽤中的通知机制) |
- QTcpSocket 用户客户端和服务器之间的数据交互
名称 | 类型 | 说明 | 对标原生 API |
---|---|---|---|
readAll() | 方法 | 读取当前接收缓冲区中的所有数据. 返回 QByteArray 对象. | read |
write(const QByteArray& ) | 方法 | 把数据写⼊ socket 中. | write |
deleteLater | 方法 | 暂时把 socket 对象标记为⽆效. Qt 会在下个事件循环中析构释放该对象. | ⽆ (但是类似于 “半⾃动化的垃圾回收”) |
readyRead | 信号 | 有数据到达并准备就绪时触发 | ⽆ (类似于 IO 多路复⽤的通知机制) |
disconnected | 信号 | 连接断开时触发 | ⽆ (类似于 IO 多路复⽤的通知机制) |
在这里,我们依旧实现一个客户端,服务器的回显程序
先启动服务器,再启动客⼾端(可以启动多个),最终执⾏效果:
由于我们使用信号槽处理同⼀个客⼾端的多个请求,不涉及到循环,也就不会使客⼾端之间相互影响了
3.3 HTTP Client
进行 Qt 开发时,和服务器之间的通信很多时候也会⽤到 HTTP 协议
- 通过 HTTP 从服务器获取数据.
- 通过 HTTP 向服务器提交数据.
关键类主要是三个. 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) | 发设置请求头. |
其中的 QNetworkRequest::KnownHeaders 是⼀个枚举类型, 常⽤取值:
名称 | 说明 |
---|---|
ContentTypeHeader | 描述 body 的类型 |
ContentLengthHeader | 描述 body 的⻓度. |
LocationHeader | ⽤于重定向报⽂中指定重定向地址. (响应中使⽤, 请求⽤不到) |
CookieHeader | 设置 cookie |
UserAgentHeader | 设置 User-Agente |
- QNetworkReply 表⽰⼀个 HTTP 响应,这个类同时也是 QIODevice 的⼦类
名称 | 说明 |
---|---|
error() | 获取出错状态 |
errorString() | 获取出错原因的⽂本 |
readAll() | 读取响应 body |
header(QNetworkRequest::KnownHeaders header) | 读取响应指定 header 的值 |
此外,QNetworkReply 还有⼀个重要的信号 finished
会在客⼾端收到完整的响应数据之后触发
下面,我们实现一个给服务器发送⼀个 GET 请求的示例:
发送 POST 请求代码也是类似,使⽤ manager->post() 即可,在参数中传递body
4. 音视频
4.1 音频
在 Qt 中,音频主要是通过 QSound 类来实现。但是需要注意的是 QSound 类只⽀持播放 wav 格式的⾳频⽂件。也就是说如果想要添加音频效果,那么首先需要将非 wav格式 的⾳频文件转换为 wav 格式
注意:使用 QSound 类时,需要添加模块:multimedia.
QSound类的核心API就是:play() ,开始或继续播放当前源
#include <QSound> //添加⾳频头⽂件
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//实例化对象
QSound *sound = new QSound(":/1.wav",this);
connect(ui->btn,&QPushButton::clicked,[=](){
sound->play(); //播放
});
}
Widget::~Widget()
{
delete ui;
}
4.2 视频
在 Qt 中,视频播放的功能主要是通过 QMediaPlayer
类 和 QVideoWidget
类来实现。
在使⽤这两个类时要添加对应的模块 multimedia 和 multimediawidgets
名称 | 说明 |
---|---|
setMedia() | 设置当前媒体源 |
setVideoOutput() | 将QVideoWidget视频输出附加到媒体播放器。 如果媒体播放器已经附加了视频输出,将更换⼀个新的。 |