一、Qt文件系统概述:QFile、QDir、QFileInfo、QIODevice的继承关系与核心职责

做Qt开发这么多年,文件操作几乎是每个项目都绕不开的坎。我记得刚入行那会儿,接手一个老项目,里面文件读写代码写得那叫一个乱——有人用C标准库的fopen,有人用Qt的QFile,还有人自己封装了一层。后来我花了整整两天时间,把所有文件操作统一成了Qt的这套体系。嗯,今天就跟大家聊聊Qt文件系统的这几个核心类。

1.1 从QIODevice说起:一切文件操作的根基

先看这张继承关系图,你就能明白这几个类是怎么串起来的。

QIODevice QFileDevice QTcpSocket QBuffer QFile QDir QFileInfo QSaveFile 常用组合 Qt文件系统核心类继承关系图 QIODevice → QFileDevice → QFile | 独立类:QDir、QFileInfo、QSaveFile

说白了,QIODevice 是所有输入输出设备的抽象基类。它定义了读写的基本接口——read、write、open、close 这些。你想想看,不管是文件、网络套接字还是内存缓冲区,底层操作其实都差不多:打开、读写、关闭。QIODevice 就是把这些共性抽了出来。

核心职责一句话:QIODevice 提供统一的读写接口,所有具体设备类都继承自它。

1.2 QFile:最常用的文件操作类

QFile 继承自 QFileDevice,而 QFileDevice 又继承自 QIODevice。我个人的习惯是,只要涉及本地文件读写,首选 QFile。为什么?因为它把文件打开、读写、关闭这些操作封装得特别干净。

来看一个最简单的例子:

// 打开文件并读取全部内容
QFile file("config.ini");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
    qWarning() << "文件打开失败:" << file.errorString();
    return;
}

QByteArray data = file.readAll();
file.close();

嗯,这里要注意:open 函数一定要检查返回值。我在项目中遇到过好几次,因为路径写错或者权限不足,文件打开失败,结果程序直接崩溃。后来我养成了习惯——每次 open 之后必做检查。

小技巧:QFile 支持两种模式——文本模式(Text)和二进制模式。读文本文件时加上 QIODevice::Text 标志,Qt 会自动处理换行符的跨平台差异。Windows 上是 \r\n,Linux 上是 \n,Qt 帮你搞定。

1.3 QDir:目录操作的瑞士军刀

QDir 不继承 QIODevice,它是一个独立的工具类。专门用来处理目录路径、遍历文件、创建删除目录这些事。

我曾经接手过一个项目,里面有个功能需要扫描某个目录下所有 .log 文件。原代码写了一大堆平台相关的 API,Windows 用 FindFirstFile,Linux 用 opendir。我直接换成 QDir,十行代码搞定:

QDir dir("/var/log");
QStringList filters;
filters << "*.log";
dir.setNameFilters(filters);

QFileInfoList list = dir.entryInfoList();
for (const QFileInfo &info : list) {
    qDebug() << "找到日志文件:" << info.absoluteFilePath();
}

你想想看,跨平台代码最怕的就是这种系统调用。用 QDir 之后,Windows、Linux、macOS 上跑得一模一样,省心多了。

避坑指南:我曾经在遍历目录时忘了设置过滤器,结果把 "." 和 ".." 也遍历出来了。记得用 entryInfoList() 时加上 QDir::Files 或 QDir::Dirs 标志,或者用 setNameFilters 过滤掉特殊目录。

1.4 QFileInfo:文件信息的查询利器

QFileInfo 也是个独立类。它不负责读写文件内容,只负责查询文件的元信息——大小、修改时间、权限、后缀名等等。

说实话,这个类特别实用。比如你要判断一个文件是否存在,或者获取文件的后缀名来决定用什么方式打开:

QFileInfo info("document.pdf");
if (info.exists()) {
    qDebug() << "文件名:" << info.fileName();
    qDebug() << "后缀名:" << info.suffix();
    qDebug() << "文件大小:" << info.size() << "字节";
    qDebug() << "最后修改:" << info.lastModified().toString("yyyy-MM-dd hh:mm:ss");
}

为什么要把 QFileInfo 单独拎出来?因为很多时候你只是想知道文件的信息,并不想打开它。比如做一个文件管理器,你总不能在列表里把每个文件都 open 一遍吧?那性能就太差了。

1.5 四个类的核心职责对比

为了方便记忆,我整理了一张表:

类名 继承关系 核心职责 典型场景
QIODevice 基类 定义读写接口,提供缓冲区管理 所有IO操作的抽象层
QFile QIODevice → QFileDevice → QFile 文件的打开、读写、关闭 读写配置文件、日志文件、二进制数据
QDir 独立类(不继承QIODevice) 目录路径操作、文件遍历、目录创建删除 扫描目录、管理文件列表、路径拼接
QFileInfo 独立类(不继承QIODevice) 查询文件元信息(大小、时间、权限等) 文件属性查看、文件类型判断

1.6 实际项目中的组合用法

在实际项目中,这三个类经常配合使用。我举个例子:

// 遍历目录下所有 .txt 文件,读取内容并统计行数
QDir dir("C:/data");
QStringList filters;
filters << "*.txt";
dir.setNameFilters(filters);

QFileInfoList files = dir.entryInfoList(QDir::Files);
int totalLines = 0;

for (const QFileInfo &info : files) {
    QFile file(info.absoluteFilePath());
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        while (!file.atEnd()) {
            file.readLine();
            totalLines++;
        }
        file.close();
    }
}

qDebug() << "总行数:" << totalLines;

你看,QDir 负责找文件,QFileInfo 提供文件路径,QFile 负责读取内容。各司其职,配合得天衣无缝。

总结一下:QIODevice 是底层抽象,QFile 是文件读写主力,QDir 管目录操作,QFileInfo 查文件信息。把这四个类搞明白,Qt 文件操作你就掌握了八成。

嗯,这一章就到这里。记住我说的——文件打开一定要检查返回值,遍历目录记得过滤特殊项。这些都是我踩过的坑,你就不用再踩一遍了。

可可精品资料库