QT入门第九天:文件操作(上)QFile与文本流 | 零基础学QT
前言
前八天我们学习了环境搭建、信号与槽、常用控件、布局管理器、对话框、菜单栏工具栏状态栏、事件处理、绘图。今天我们来学习QT的文件操作。
文件操作是编程的基本功。不管什么程序,几乎都需要和文件打交道:
- 保存用户设置
- 读取配置文件
- 保存和加载数据
- 日志记录
- 导入导出文件
QT提供了一套完整的文件操作类,用起来非常方便。由于文件操作内容比较多,我们分成两天来学:
- 今天(上):QFile文件读写、QTextStream文本流、QDataStream数据流
- 明天(下):QFileInfo文件信息、QDir目录操作、临时文件、文件监控
一、QFile 基础
1.1 什么是QFile
QFile是QT中用来操作文件的类,可以读写文本文件和二进制文件。它继承自QIODevice,是QT所有IO类的基础。
💡 理解:QFile就像一个文件操作工具,用它可以打开文件、读内容、写内容、关闭文件。
1.2 基本用法
cpp
#include <QFile>
#include <QDebug>
// 1. 创建QFile对象,指定文件路径
QFile file("test.txt");
// 2. 打开文件(只读方式)
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "文件打开成功";
// 3. 读写操作...
// 4. 关闭文件
file.close();
} else {
qDebug() << "文件打开失败:" << file.errorString();
}
1.3 打开模式
open()函数需要指定打开模式,常用的有:
| 模式 | 说明 |
|---|---|
| QIODevice::ReadOnly | 只读 |
| QIODevice::WriteOnly | 只写(会清空原有内容) |
| QIODevice::ReadWrite | 读写 |
| QIODevice::Append | 追加(在文件末尾写入) |
| QIODevice::Truncate | 截断(清空文件内容) |
| QIODevice::Text | 文本模式(换行符会自动转换) |
| QIODevice::Unbuffered | 不缓存 |
💡 多个模式用
|连接,比如ReadOnly | Text表示以只读文本模式打开。
1.4 检查文件是否存在
cpp
if (QFile::exists("test.txt")) {
qDebug() << "文件存在";
} else {
qDebug() << "文件不存在";
}
1.5 删除文件
cpp
QFile::remove("test.txt"); // 删除文件,成功返回true
1.6 重命名文件
cpp
QFile::rename("old.txt", "new.txt"); // 重命名
二、读写文本文件
2.1 读取全部内容
cpp
QFile file("test.txt");
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
// readAll()读取全部内容,返回QByteArray
QByteArray data = file.readAll();
// 转成QString
QString content = QString::fromUtf8(data);
qDebug() << "文件内容:" << content;
file.close();
}
2.2 逐行读取
大文件不适合一次性全部读入内存,可以逐行读取:
cpp
QFile file("test.txt");
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
int lineNum = 0;
while (!file.atEnd()) {
QByteArray line = file.readLine(); // 读一行
lineNum++;
qDebug() << "第" << lineNum << "行:" << line.trimmed();
}
file.close();
}
💡
readLine()会把换行符也读进来,用trimmed()可以去掉首尾的空白字符。
2.3 写入文件
cpp
QFile file("output.txt");
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
// write()写入内容
file.write("Hello QT!\n");
file.write("这是一行中文\n");
// 也可以用QByteArray
QByteArray data = "第三行内容\n";
file.write(data);
file.close();
qDebug() << "写入成功";
}
⚠️ 注意:WriteOnly模式会清空文件原来的内容!如果想保留原来的内容,用Append模式。
2.4 追加写入
在文件末尾添加内容,不覆盖原有内容:
cpp
QFile file("log.txt");
if (file.open(QIODevice::Append | QIODevice::Text)) {
file.write("这是追加的一行\n");
file.close();
}
2.5 获取文件大小
cpp
QFile file("test.txt");
if (file.open(QIODevice::ReadOnly)) {
qint64 size = file.size(); // 文件大小,单位字节
qDebug() << "文件大小:" << size << "字节";
file.close();
}
三、QTextStream 文本流
直接用QFile的read和write操作文本有点麻烦,QT提供了QTextStream文本流,用起来更方便。
3.1 什么是QTextStream
QTextStream是一个文本流类,它封装了QFile,提供了更友好的接口:
- 可以用
<<和>>操作符,就像cout/cin一样 - 自动处理编码
- 支持格式化输出
💡 理解:QTextStream就像给文件套了一层"文本壳",让读写文本更简单。
3.2 用QTextStream读取
cpp
#include <QTextStream>
QFile file("test.txt");
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
// 设置编码(QT6默认是UTF-8,一般不用设置)
in.setEncoding(QStringConverter::Utf8);
// 读取全部内容
QString content = in.readAll();
qDebug() << content;
// 或者逐行读取
// while (!in.atEnd()) {
// QString line = in.readLine();
// qDebug() << line;
// }
file.close();
}
3.3 用QTextStream写入
cpp
QFile file("output.txt");
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
// 用 << 操作符写入,就像 cout 一样
out << "Hello QT!" << Qt::endl;
out << "数字:" << 123 << Qt::endl;
out << "浮点数:" << 3.14 << Qt::endl;
out << "中文也没问题" << Qt::endl;
file.close();
qDebug() << "写入成功";
}
3.4 格式化输出
QTextStream支持各种格式化:
cpp
QTextStream out(&file);
// 设置字段宽度
out << qSetFieldWidth(10) << left << "姓名" << "年龄" << Qt::endl;
out << qSetFieldWidth(10) << left << "张三" << 25 << Qt::endl;
// 设置数字进制
out << hex << 255 << Qt::endl; // 十六进制:ff
out << oct << 255 << Qt::endl; // 八进制:377
out << dec << 255 << Qt::endl; // 十进制:255
// 设置浮点数精度
out << qSetRealNumberPrecision(2) << 3.1415926 << Qt::endl; // 3.14
3.5 为什么推荐用QTextStream
- 更方便 :
<<操作符用起来很顺手 - 自动编码:不用自己处理编码转换
- 格式化:支持各种格式化输出
- 类型安全:各种类型都能直接写
💡 建议:读写文本文件优先用QTextStream,比直接用QFile方便多了。
四、QDataStream 数据流
4.1 什么是QDataStream
QDataStream是二进制数据流,用来读写二进制数据。和QTextStream不同:
- QTextStream:读写文本,人能看懂
- QDataStream:读写二进制,人看不懂,但效率高、体积小
💡 什么时候用QDataStream?
- 需要保存复杂的数据结构
- 对文件大小有要求
- 数据不需要人看懂
- 序列化对象
4.2 写入二进制数据
cpp
#include <QDataStream>
QFile file("data.dat");
if (file.open(QIODevice::WriteOnly)) {
QDataStream out(&file);
// 设置版本(重要!保证读写版本一致)
out.setVersion(QDataStream::Qt_6_0);
// 写入各种类型的数据
out << QString("张三");
out << 25; // int
out << 175.5; // double
out << true; // bool
// 写入QByteArray
QByteArray bytes = "二进制数据";
out << bytes;
// 写入QStringList
QStringList list;
list << "苹果" << "香蕉" << "橙子";
out << list;
file.close();
qDebug() << "二进制写入成功";
}
4.3 读取二进制数据
cpp
QFile file("data.dat");
if (file.open(QIODevice::ReadOnly)) {
QDataStream in(&file);
// 版本要和写入时一致!
in.setVersion(QDataStream::Qt_6_0);
// 按写入的顺序读取
QString name;
int age;
double height;
bool isStudent;
QByteArray bytes;
QStringList list;
in >> name >> age >> height >> isStudent >> bytes >> list;
qDebug() << "姓名:" << name;
qDebug() << "年龄:" << age;
qDebug() << "身高:" << height;
qDebug() << "是学生:" << isStudent;
qDebug() << "列表:" << list;
file.close();
}
4.4 版本问题(重要!)
QDataStream有版本的概念,不同版本的格式可能不一样。读取和写入的版本必须一致,否则会出问题。
cpp
// 写入时设置版本
out.setVersion(QDataStream::Qt_6_0);
// 读取时也要用相同版本
in.setVersion(QDataStream::Qt_6_0);
⚠️ 重要提醒:一定要设置版本!不然不同QT版本读写可能不兼容。
4.5 支持的类型
QDataStream支持很多QT类型的序列化:
- 基本类型:int, double, bool, char等
- QString, QByteArray
- QStringList, QList, QVector, QMap, QHash
- QDate, QTime, QDateTime
- QPoint, QSize, QRect
- QColor, QFont, QPixmap
- 还有很多很多...
基本上QT的常用类型都支持直接用 << 和 >> 读写,非常方便。
五、文件操作的注意事项
5.1 错误处理
打开文件可能失败(比如文件不存在、没有权限),一定要检查返回值:
cpp
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "打开文件失败:" << file.errorString();
return;
}
errorString() 可以得到具体的错误信息,方便排查问题。
5.2 自动关闭
QFile的析构函数会自动关闭文件,所以有时候不用手动close():
cpp
void readFile() {
QFile file("test.txt");
if (file.open(QIODevice::ReadOnly)) {
// 读取...
}
// 函数结束时,file对象销毁,自动调用close()
}
💡 但建议还是手动close(),更清晰,也能及时释放资源。
5.3 大文件处理
如果文件很大(几百MB以上),不要一次性readAll(),会占很多内存。应该:
- 逐行读取(文本文件)
- 分块读取(二进制文件)
cpp
// 分块读取二进制
const qint64 chunkSize = 1024 * 1024; // 1MB一块
while (!file.atEnd()) {
QByteArray chunk = file.read(chunkSize);
// 处理这一块...
}
5.4 文件路径
文件路径有相对路径和绝对路径:
- 相对路径:相对于程序运行目录,比如
"test.txt" - 绝对路径:完整路径,比如
"C:/Users/xxx/Documents/test.txt"
💡 小技巧:可以用
QCoreApplication::applicationDirPath()获取程序所在目录。
六、综合实战:完善记事本的文件功能
我们来给之前的记事本加上真正的文件读写功能。
6.1 完整代码
cpp
#include <QMainWindow>
#include <QTextEdit>
#include <QMenuBar>
#include <QMenu>
#include <QAction>
#include <QFile>
#include <QTextStream>
#include <QFileDialog>
#include <QMessageBox>
class Notepad : public QMainWindow
{
Q_OBJECT
public:
Notepad(QWidget *parent = nullptr) : QMainWindow(parent) {
setWindowTitle("简易记事本");
resize(800, 600);
// 文本编辑区
m_textEdit = new QTextEdit(this);
setCentralWidget(m_textEdit);
// 创建菜单
createMenus();
// 连接信号槽
connect(m_openAction, &QAction::triggered, this, &Notepad::openFile);
connect(m_saveAction, &QAction::triggered, this, &Notepad::saveFile);
connect(m_saveAsAction, &QAction::triggered, this, &Notepad::saveFileAs);
connect(m_newAction, &QAction::triggered, this, &Notepad::newFile);
// 文本改变时标记为未保存
connect(m_textEdit, &QTextEdit::textChanged, this, [=](){
setWindowModified(true);
updateTitle();
});
updateTitle();
}
private slots:
// 新建文件
void newFile() {
if (maybeSave()) {
m_textEdit->clear();
m_currentFile.clear();
setWindowModified(false);
updateTitle();
}
}
// 打开文件
void openFile() {
if (maybeSave()) {
QString fileName = QFileDialog::getOpenFileName(
this, "打开文件", "",
"文本文件 (*.txt);;所有文件 (*.*)"
);
if (!fileName.isEmpty()) {
loadFile(fileName);
}
}
}
// 保存文件
bool saveFile() {
if (m_currentFile.isEmpty()) {
return saveFileAs();
}
return saveFileTo(m_currentFile);
}
// 另存为
bool saveFileAs() {
QString fileName = QFileDialog::getSaveFileName(
this, "另存为", "未命名.txt",
"文本文件 (*.txt);;所有文件 (*.*)"
);
if (fileName.isEmpty()) {
return false;
}
return saveFileTo(fileName);
}
private:
// 创建菜单
void createMenus() {
QMenu *fileMenu = menuBar()->addMenu("文件(&F)");
m_newAction = fileMenu->addAction("新建(&N)");
m_newAction->setShortcut(QKeySequence::New);
m_openAction = fileMenu->addAction("打开(&O)");
m_openAction->setShortcut(QKeySequence::Open);
m_saveAction = fileMenu->addAction("保存(&S)");
m_saveAction->setShortcut(QKeySequence::Save);
m_saveAsAction = fileMenu->addAction("另存为(&A)...");
fileMenu->addSeparator();
QAction *exitAction = fileMenu->addAction("退出(&X)");
exitAction->setShortcut(QKeySequence::Quit);
connect(exitAction, &QAction::triggered, this, &QWidget::close);
}
// 加载文件
void loadFile(const QString &fileName) {
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QMessageBox::warning(this, "错误",
"无法打开文件:" + file.errorString());
return;
}
QTextStream in(&file);
m_textEdit->setPlainText(in.readAll());
m_currentFile = fileName;
setWindowModified(false);
updateTitle();
file.close();
}
// 保存到文件
bool saveFileTo(const QString &fileName) {
QFile file(fileName);
if (!file.open(QFile::WriteOnly | QFile::Text)) {
QMessageBox::warning(this, "错误",
"无法保存文件:" + file.errorString());
return false;
}
QTextStream out(&file);
out << m_textEdit->toPlainText();
m_currentFile = fileName;
setWindowModified(false);
updateTitle();
file.close();
return true;
}
// 如果有未保存的修改,询问是否保存
bool maybeSave() {
if (!isWindowModified()) {
return true;
}
QMessageBox::StandardButton btn = QMessageBox::question(
this, "提示",
"文件已修改,是否保存?",
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel
);
if (btn == QMessageBox::Save) {
return saveFile();
} else if (btn == QMessageBox::Cancel) {
return false;
}
return true;
}
// 更新标题栏
void updateTitle() {
QString title;
if (m_currentFile.isEmpty()) {
title = "未命名";
} else {
QFileInfo info(m_currentFile);
title = info.fileName();
}
setWindowTitle(title + "[*] - 简易记事本");
}
// 关闭事件
void closeEvent(QCloseEvent *event) override {
if (maybeSave()) {
event->accept();
} else {
event->ignore();
}
}
private:
QTextEdit *m_textEdit;
QString m_currentFile;
QAction *m_newAction;
QAction *m_openAction;
QAction *m_saveAction;
QAction *m_saveAsAction;
};
6.2 功能说明
这个记事本有这些功能:
- ✅ 新建文件(Ctrl+N)
- ✅ 打开文件(Ctrl+O)
- ✅ 保存文件(Ctrl+S)
- ✅ 另存为
- ✅ 未保存提示(标题栏有*号标记)
- ✅ 关闭时询问是否保存
- ✅ 错误提示
是不是很像一个真正的记事本了?只用了QFile和QTextStream,就实现了完整的文件功能。
七、今日总结
今天我们学习了QT文件操作的上篇,主要是文件读写。
知识点汇总
| 类 | 用途 | 特点 |
|---|---|---|
| QFile | 文件操作基础类 | 直接读写字节,底层 |
| QTextStream | 文本流 | 读写文本,方便,支持格式化 |
| QDataStream | 数据流 | 读写二进制,效率高,支持序列化 |
重要概念
- ✅ 打开模式:ReadOnly, WriteOnly, Append, Text等
- ✅ 文本读写:QFile直接读写,或用QTextStream更方便
- ✅ 二进制读写:QDataStream,注意版本一致
- ✅ 错误处理:检查open返回值,用errorString看错误信息
- ✅ 自动关闭:QFile析构时自动关闭,但建议手动close
经验分享
- 文本文件优先用QTextStream:比直接用QFile方便,自动处理编码
- 二进制文件用QDataStream:支持很多QT类型,序列化很方便
- 一定要设置QDataStream版本:不然不同版本可能不兼容
- 大文件不要一次性读完:逐行或分块读取
- 记得检查错误:文件操作可能因为各种原因失败,一定要处理
- 相对路径注意运行目录:调试和发布时运行目录可能不一样
八、明日预告
明天我们将学习文件操作(下):文件信息与目录操作。
内容包括:
- QFileInfo:获取文件详细信息(大小、时间、权限等)
- QDir:目录操作(创建、删除、遍历目录)
- QTemporaryFile:临时文件
- QFileSystemWatcher:文件监控
- 综合实战:文件浏览器
文件操作下篇更精彩,明天见!
📝 学习建议:文件操作是基本功,一定要练熟。
练习建议:
- 把今天的记事本代码敲一遍运行看看
- 试试给记事本加一个"最近打开的文件"功能
- 试试写一个程序,统计一个文本文件有多少行、多少字
- 试试用QDataStream保存和加载一个自定义的结构体
文件读写掌握了,明天我们继续学文件信息和目录操作!明天见,继续加油!💪