Note
本篇讲解 Qt 的文件与 IO 体系:QFile 基本读写、QTextStream 与 QDataStream 的区别与选择、QFileInfo/QDir 遍历目录、QSettings 保存应用配置。适合需要在程序里读写数据、保存设置的初学者。
QFile:文件的打开与读写
QFile 是 Qt 文件操作的基石,它继承自 QIODevice——一个抽象的”输入输出设备”基类(QBuffer、QTcpSocket 也继承它,所以它们的读写接口几乎一样)。使用流程固定为三步:setFileName → open → 读写 → close。
#include <QFile>
#include <QByteArray>
#include <QDebug>
void writeDemo()
{
QFile file("test.txt");
// 打开失败时不要继续往下走:路径不存在、无权限都会失败
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "打开失败:" << file.errorString();
return;
}
file.write("第一行内容\n"); // write 不会自动加换行,要自己拼 "\n"
file.write("第二行内容\n");
file.close(); // 栈对象析构时也会自动关闭
}
void readDemo()
{
QFile file("test.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
// 方式一:一次性读完
QByteArray all = file.readAll();
// 方式二:逐行读(seek 回到开头后)
file.seek(0); // 移动文件游标到开头
while (!file.atEnd()) {
QByteArray line = file.readLine(); // 读一行,含行尾 '\n'
qDebug() << line.trimmed(); // trimmed() 去掉两端空白
}
}打开模式是位标志,可以组合:
| 模式 | 含义 |
|---|---|
| QIODevice::ReadOnly | 只读 |
| QIODevice::WriteOnly | 只写(会清空原内容) |
| QIODevice::Append | 追加到末尾 |
| QIODevice::Truncate | 清空原内容(常与 WriteOnly 连用) |
| QIODevice::Text | 自动转换换行符(Windows 的 \r\n 与 \n 之间) |
Warning
两个高频坑:一是 open() 返回值必须判断,文件不存在时以 ReadOnly 打开会直接失败;二是 QIODevice::Text 模式下读写的内容经过了换行符转换,不是原始字节——处理二进制文件(图片、自定义格式)时千万不要加 Text 标志。
QTextStream 与 QDataStream
QFile 自带的 read/write 只面向原始字节,处理”数据类型”很不方便。Qt 用两个流类解决了这个问题,但它们定位完全不同:
| 对比项 | QTextStream | QDataStream |
|---|---|---|
| 定位 | 读写人类可读的文本 | 读写二进制串行化数据 |
| 支持类型 | 字符串为主,数字也可格式化 | C++ 基本类型 + 大部分 Qt 类型(QString、QColor、QRect…) |
| 可读性 | 记事本打开能看懂 | 打开是乱码 |
| 典型场景 | 日志、CSV、配置文本 | 自定义二进制格式、网络协议包 |
选型口诀:给人看的用 QTextStream,给程序(或其他机器)看的用 QDataStream。
#include <QTextStream>
#include <QDataStream>
#include <QFile>
void textStreamDemo()
{
QFile file("log.txt");
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream out(&file); // 流绑定到文件
out << "姓名:张三,年龄:" << 25 << endl; // 像 cout 一样用 <<
file.close();
}
void dataStreamDemo()
{
// 写入:结构化数据打包成二进制
QFile out1("person.ds");
out1.open(QIODevice::WriteOnly);
QDataStream dsOut(&out1);
dsOut.setVersion(QDataStream::Qt_5_15); // 固定版本,保证以后读得回来
dsOut << QString("张三") << qint32(25) << 65.5; // 顺序写入
out1.close();
// 读取:必须按**完全相同的顺序和类型**读出
QFile in1("person.ds");
in1.open(QIODevice::ReadOnly);
QDataStream dsIn(&in1);
dsIn.setVersion(QDataStream::Qt_5_15);
QString name; qint32 age; double weight;
dsIn >> name >> age >> weight; // 顺序乱了、类型错了都会读坏
in1.close();
}三个必须记住的规则:
- QDataStream 写入格式与操作系统、CPU 字节序无关(默认按网络字节序),同一份文件跨平台读取结果一致。
- 整型尽量用
qint8/quint16/qint32/qint64这类定长类型,避免 long 在 32/64 位系统上长度不同导致读写错位。 - 读取时先写入的先读出,顺序不可打乱;可以用
dsIn.status() != QDataStream::Ok检查是否读坏,出错就停止读取。
Success
QDataStream 的 setVersion 一定要在写之前、读之前都设置,并且两端一致。否则 Qt 升级后,旧文件里的 QString 等类型的编码规则可能变化,读出来就是错的。
QFileInfo 与 QDir:文件属性与目录遍历
QFile 偏重”读写内容”,而”这个文件多大、什么时候改的、目录里都有什么”要靠另外两个类:
#include <QFileInfo>
#include <QDir>
#include <QDebug>
void infoDemo(const QString &path)
{
QFileInfo info(path);
qDebug() << "是否存在:" << info.exists();
qDebug() << "文件大小:" << info.size() << "字节";
qDebug() << "是目录吗:" << info.isDir();
qDebug() << "绝对路径:" << info.absoluteFilePath();
qDebug() << "后缀名:" << info.suffix();
}
void dirDemo()
{
QDir dir("D:/Project");
// 遍历当前目录(不递归子目录)
for (const QFileInfo &fi : dir.entryInfoList(QDir::Files | QDir::Dirs, QDir::Name)) {
if (fi.fileName() == "." || fi.fileName() == "..") // 跳过特殊目录
continue;
qDebug() << (fi.isDir() ? "[目录]" : "[文件]") << fi.fileName();
}
// 递归遍历所有子目录,找 .cpp 文件
QDirIterator it("D:/Project", QStringList() << "*.cpp",
QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext())
qDebug() << it.next();
}QDir 还能直接做文件管理:mkdir() 建目录、rmdir() 删空目录、remove() 删文件、rename() 重命名。QDir::currentPath() 和 QDir::homePath() 常用来定位”当前工作目录”和”用户主目录”。
Warning
Qt 里写路径统一用正斜杠
/("D:/Project"),Windows 的反斜杠写法在字符串里会被当成转义字符。QDir 也提供了toNativeSeparators()在需要展示给用户时转换成本地风格。
QSettings:保存应用配置
窗口大小、上次打开的目录、用户偏好——这些需要在下次启动时恢复的数据,都交给 QSettings。它屏蔽了平台差异:Windows 上默认写注册表,Linux/macOS 上默认写 ini 文件,而你的代码完全不用关心区别。
#include <QSettings>
#include <QApplication>
void saveSettings()
{
QSettings settings("MyCompany", "MyEditor"); // (组织名, 应用名)
settings.setValue("window/size", QSize(800, 600));
settings.setValue("window/maximized", false);
settings.setValue("editor/fontSize", 14);
settings.setValue("recent/files", QStringList() << "a.txt" << "b.txt");
}
void loadSettings()
{
QSettings settings("MyCompany", "MyEditor");
// 读的时候提供默认值:键不存在时用默认值兜底
QSize size = settings.value("window/size", QSize(800, 600)).toSize();
bool maximized = settings.value("window/maximized", false).toBool();
int fontSize = settings.value("editor/fontSize", 12).toInt();
QStringList files = settings.value("recent/files").toStringList();
}要点:
- 键名用
组/子键的斜杠形式,自动形成层级(window/size 表示 window 组下的 size)。 - value() 返回 QVariant,必须用
toSize()/toInt()/toStringList()等函数转回具体类型。 - QSettings 写入有缓存,一般程序退出时自动落盘;若想立即写盘可调用
sync()。
如果想要一个可以拷贝到别的机器的 ini 文件,显式指定路径即可:
QSettings iniSettings("config.ini", QSettings::IniFormat);Success
注册表适合”跟机器走”的配置,ini 文件适合”跟程序走”的配置(绿色软件常用)。密码、敏感数据不要存进 QSettings——它不加密,ini 文件用户能直接打开看。
自测:三个问题检验掌握程度
自测:三个问题检验掌握程度
- QIODevice::Text 模式会带来什么副作用?读二进制文件时应该注意什么?
- QTextStream 和 QDataStream 怎么选?用 QDataStream 读文件时如果读取顺序和写入顺序不一致会发生什么?
- QSettings 在 Windows 和 Linux 上分别把配置存到哪里?想让配置跟着程序目录走该怎么做?
(答案都在上文)