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 用两个流类解决了这个问题,但它们定位完全不同:

对比项QTextStreamQDataStream
定位读写人类可读的文本读写二进制串行化数据
支持类型字符串为主,数字也可格式化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 文件用户能直接打开看。

自测:三个问题检验掌握程度