QT入门第十天:文件操作(下)文件信息与目录操作 | 零基础学QT

QT入门第十天:文件操作(下)文件信息与目录操作 | 零基础学QT

前言

昨天我们学习了文件操作的上篇:QFile文件读写、QTextStream文本流、QDataStream数据流。

今天我们继续学习文件操作的下篇,内容包括:

  • QFileInfo:获取文件的详细信息(大小、时间、权限等)
  • QDir:目录操作(创建、删除、遍历目录)
  • QTemporaryFile:临时文件
  • QFileSystemWatcher:文件监控
  • 综合实战:简易文件浏览器

学完这两篇,QT的文件操作你就基本掌握了!

一、QFileInfo 文件信息

1.1 什么是QFileInfo

QFileInfo是用来获取文件信息的类。它可以告诉你关于一个文件的各种信息:

  • 文件叫什么名字
  • 文件在哪个目录
  • 文件有多大
  • 文件什么时候创建的、什么时候修改的
  • 是文件还是目录
  • 有没有读写权限
  • 等等...

💡 理解:QFile就像一个文件操作工具(打开、读写),QFileInfo就像一个文件信息查询工具(查属性)。

1.2 基本用法

cpp 复制代码
#include <QFileInfo>
#include <QDebug>

// 创建QFileInfo对象
QFileInfo info("test.txt");

// 获取各种信息
qDebug() << "文件名:" << info.fileName();
qDebug() << "文件路径:" << info.filePath();
qDebug() << "绝对路径:" << info.absoluteFilePath();
qDebug() << "文件大小:" << info.size() << "字节";
qDebug() << "是否存在:" << info.exists();
qDebug() << "是文件吗:" << info.isFile();
qDebug() << "是目录吗:" << info.isDir();

1.3 文件名称相关

cpp 复制代码
QFileInfo info("C:/Users/xxx/Documents/report.txt");

qDebug() << info.fileName();          // "report.txt" (文件名+后缀)
qDebug() << info.baseName();          // "report" (文件名,不含后缀)
qDebug() << info.suffix();            // "txt" (后缀名)
qDebug() << info.completeSuffix();    // "txt" (完整后缀)

qDebug() << info.path();              // "C:/Users/xxx/Documents" (路径)
qDebug() << info.absolutePath();      // 绝对路径
qDebug() << info.absoluteFilePath();  // 绝对路径+文件名

💡 小技巧:用 baseName()suffix() 可以很方便地分离文件名和后缀。

1.4 文件时间

cpp 复制代码
QFileInfo info("test.txt");

// 创建时间
qDebug() << "创建时间:" << info.birthTime();

// 最后修改时间
qDebug() << "修改时间:" << info.lastModified();

// 最后访问时间
qDebug() << "访问时间:" << info.lastRead();

// 最后元数据修改时间
qDebug() << "元数据修改时间:" << info.metadataChangeTime();

这些返回的都是QDateTime对象,可以用toString()格式化:

cpp 复制代码
QDateTime time = info.lastModified();
qDebug() << time.toString("yyyy-MM-dd hh:mm:ss");
// 输出类似:2024-01-15 14:30:25

1.5 文件类型判断

cpp 复制代码
QFileInfo info("xxx");

info.isFile();       // 是普通文件吗?
info.isDir();        // 是目录吗?
info.isSymLink();    // 是符号链接吗?
info.isExecutable(); // 是可执行文件吗?
info.isReadable();   // 可读吗?
info.isWritable();   // 可写吗?
info.isHidden();     // 是隐藏文件吗?

1.6 权限判断

cpp 复制代码
QFileInfo info("test.txt");

// 检查所有者权限
if (info.permission(QFile::ReadOwner)) {
    qDebug() << "所有者可读";
}

// 也可以一次检查多个权限
if (info.permission(QFile::ReadOwner | QFile::WriteOwner)) {
    qDebug() << "所有者可读写";
}

权限类型有:

  • QFile::ReadOwner / WriteOwner / ExeOwner(所有者)
  • QFile::ReadUser / WriteUser / ExeUser(用户)
  • QFile::ReadGroup / WriteGroup / ExeGroup(组)
  • QFile::ReadOther / WriteOther / ExeOther(其他)

1.7 刷新信息

如果文件在程序运行过程中被修改了,QFileInfo的信息不会自动更新,需要手动刷新:

cpp 复制代码
QFileInfo info("test.txt");
// ... 文件被修改了 ...
info.refresh();  // 刷新信息

二、QDir 目录操作

2.1 什么是QDir

QDir是QT中用来操作目录的类,可以:

  • 查看目录里有什么文件
  • 创建和删除目录
  • 重命名目录
  • 过滤和排序文件
  • 获取各种系统目录

💡 理解:QDir就像一个文件夹操作工具,可以浏览文件夹、创建文件夹、删除文件夹。

2.2 基本用法

cpp 复制代码
#include <QDir>
#include <QDebug>

// 创建QDir对象,指向当前目录
QDir dir;

// 或者指定路径
QDir dir2("C:/Users/xxx/Documents");

// 检查目录是否存在
if (dir.exists()) {
    qDebug() << "目录存在";
}

// 获取当前路径
qDebug() << dir.currentPath();

2.3 遍历目录内容

获取目录下的所有文件和子目录:

cpp 复制代码
QDir dir("C:/Users/xxx/Documents");

// 获取所有条目(文件+目录)
QStringList entries = dir.entryList();

// 遍历输出
for (const QString &entry : entries) {
    qDebug() << entry;
}

2.4 过滤和排序

entryList()支持过滤和排序:

cpp 复制代码
QDir dir("C:/Users/xxx/Documents");

// 只显示文件,不显示目录,按名称排序
QStringList files = dir.entryList(
    QDir::Files,        // 过滤:只显示文件
    QDir::Name          // 排序:按名称
);

常用过滤标志

标志 说明
QDir::Files 只显示文件
QDir::Dirs 只显示目录
QDir::AllEntries 显示所有(文件+目录)
QDir::NoDotAndDotDot 不显示.和...
QDir::Hidden 显示隐藏文件
QDir::Readable 只显示可读的
QDir::Writable 只显示可写的

常用排序标志

标志 说明
QDir::Name 按名称排序
QDir::Time 按时间排序
QDir::Size 按大小排序
QDir::Type 按类型排序
QDir::Unsorted 不排序
QDir::Reversed 反向排序

多个标志用 | 连接:

cpp 复制代码
// 显示所有文件(不包括.和..),按大小从大到小排序
QStringList files = dir.entryList(
    QDir::Files | QDir::NoDotAndDotDot,
    QDir::Size | QDir::Reversed
);

2.5 名称过滤

还可以用通配符过滤文件名:

cpp 复制代码
QDir dir("C:/Users/xxx/Documents");

// 只显示txt文件
QStringList filters;
filters << "*.txt";
dir.setNameFilters(filters);

QStringList txtFiles = dir.entryList(QDir::Files);

也可以多个过滤条件:

cpp 复制代码
filters << "*.txt" << "*.doc" << "*.pdf";

2.6 创建目录

cpp 复制代码
QDir dir;

// 创建一个目录
if (dir.mkdir("newFolder")) {
    qDebug() << "创建成功";
} else {
    qDebug() << "创建失败(可能已存在)";
}

// 创建多级目录(比如 a/b/c,父目录不存在也会一起创建)
if (dir.mkpath("a/b/c")) {
    qDebug() << "多级目录创建成功";
}

💡 区别:

  • mkdir():只能创建一级目录,父目录必须存在
  • mkpath():可以创建多级目录,父目录不存在会自动创建

2.7 删除目录

cpp 复制代码
QDir dir;

// 删除一个空目录
if (dir.rmdir("emptyFolder")) {
    qDebug() << "删除成功";
}

// 删除多级空目录
if (dir.rmpath("a/b/c")) {
    qDebug() << "多级目录删除成功";
}

⚠️ 注意:rmdir()只能删除空目录!如果目录里有文件,删不掉。

如果要删除非空目录,需要先删除里面的所有文件,或者用更高级的方法。

2.8 重命名目录

cpp 复制代码
QDir dir;
dir.rename("oldName", "newName");

2.9 目录路径操作

cpp 复制代码
QDir dir("C:/Users/xxx/Documents");

// 切换到上级目录
dir.cdUp();  // 现在指向 C:/Users/xxx

// 切换到子目录
dir.cd("Documents");  // 又回到 C:/Users/xxx/Documents

// 判断是不是根目录
qDebug() << dir.isRoot();

// 目录名
qDebug() << dir.dirName();  // "Documents"

2.10 常用系统目录

QDir提供了一些静态方法获取常用目录:

cpp 复制代码
// 当前工作目录
qDebug() << QDir::currentPath();

// 用户主目录
qDebug() << QDir::homePath();

// 临时文件目录
qDebug() << QDir::tempPath();

// 根目录
qDebug() << QDir::rootPath();

这些都是静态方法,直接用类名调用就行。

2.11 计算目录大小

遍历目录下所有文件,计算总大小:

cpp 复制代码
qint64 dirSize(const QString &path) {
    QDir dir(path);
    qint64 size = 0;
    
    // 遍历所有文件
    QFileInfoList fileInfos = dir.entryInfoList(QDir::Files);
    for (const QFileInfo &info : fileInfos) {
        size += info.size();
    }
    
    // 递归遍历子目录
    QFileInfoList dirInfos = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
    for (const QFileInfo &info : dirInfos) {
        size += dirSize(info.absoluteFilePath());
    }
    
    return size;
}

三、QTemporaryFile 临时文件

3.1 什么是临时文件

临时文件就是用完就删的文件,常用于:

  • 临时保存数据
  • 程序间交换数据
  • 缓存

QT提供了QTemporaryFile类,非常方便:

  • 自动生成唯一的文件名(不会冲突)
  • 文件关闭后自动删除
  • 不用自己操心清理

3.2 基本用法

cpp 复制代码
#include <QTemporaryFile>
#include <QTextStream>
#include <QDebug>

// 创建临时文件
QTemporaryFile tempFile;

// 打开文件
if (tempFile.open()) {
    qDebug() << "临时文件路径:" << tempFile.fileName();
    
    // 写入内容
    QTextStream out(&tempFile);
    out << "这是临时文件的内容";
    
    // 关闭文件(关闭后会自动删除!)
    tempFile.close();
}

💡 注意:QTemporaryFile关闭后会自动删除文件。如果想保留文件,需要调用setAutoRemove(false)。

3.3 不自动删除

如果你想让临时文件保留下来:

cpp 复制代码
QTemporaryFile tempFile;
tempFile.setAutoRemove(false);  // 关闭后不自动删除

if (tempFile.open()) {
    // 写入...
    tempFile.close();
    // 文件还在,可以继续用
}

3.4 指定文件名模板

默认文件名是随机的,你也可以指定模板:

cpp 复制代码
// 模板:myapp_XXXXXX.txt
// XXXXXX会被替换成随机字符
QTemporaryFile tempFile("myapp_XXXXXX.txt");

四、QFileSystemWatcher 文件监控

4.1 什么是文件监控

QFileSystemWatcher可以监控文件或目录的变化:

  • 文件被修改了
  • 文件被删除了
  • 目录里新增了文件
  • 目录里删除了文件

当变化发生时,会发出信号通知你。

💡 应用场景:

  • 监控配置文件,修改后自动重新加载
  • 监控文件夹,有新文件自动处理
  • 文件被意外修改时提醒

4.2 基本用法

cpp 复制代码
#include <QFileSystemWatcher>
#include <QDebug>

// 创建监控对象
QFileSystemWatcher *watcher = new QFileSystemWatcher(this);

// 添加要监控的文件
watcher->addPath("config.ini");

// 添加要监控的目录
watcher->addPath("dataFolder");

// 连接信号:文件变化时触发
connect(watcher, &QFileSystemWatcher::fileChanged, 
        this, [=](const QString &path){
    qDebug() << "文件变化了:" << path;
    // 重新加载文件...
});

// 连接信号:目录变化时触发
connect(watcher, &QFileSystemWatcher::directoryChanged,
        this, [=](const QString &path){
    qDebug() << "目录变化了:" << path;
    // 重新扫描目录...
});

4.3 常用方法

cpp 复制代码
// 添加监控路径
watcher->addPath("file.txt");
watcher->addPath("folder");

// 批量添加
QStringList paths;
paths << "a.txt" << "b.txt";
watcher->addPaths(paths);

// 移除监控
watcher->removePath("file.txt");

// 查看监控了哪些文件
qDebug() << watcher->files();

// 查看监控了哪些目录
qDebug() << watcher->directories();

4.4 注意事项

  1. 文件被删除后监控会失效:如果监控的文件被删了,需要重新添加
  2. 性能考虑:不要监控太多文件,可能会影响性能
  3. 跨平台:不同平台的监控机制可能有差异

五、综合实战:简易文件浏览器

我们来做一个简易的文件浏览器,可以浏览目录、查看文件信息。

5.1 完整代码

cpp 复制代码
#include <QMainWindow>
#include <QListWidget>
#include <QLineEdit>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QFileInfo>
#include <QDir>
#include <QMessageBox>

class FileBrowser : public QMainWindow
{
    Q_OBJECT
public:
    FileBrowser(QWidget *parent = nullptr) : QMainWindow(parent) {
        setWindowTitle("简易文件浏览器");
        resize(800, 600);
        
        // 中心部件
        QWidget *central = new QWidget(this);
        setCentralWidget(central);
        
        QVBoxLayout *mainLayout = new QVBoxLayout(central);
        
        // 地址栏
        QHBoxLayout *pathLayout = new QHBoxLayout();
        m_pathEdit = new QLineEdit(this);
        m_backBtn = new QPushButton("←", this);
        m_backBtn->setFixedWidth(40);
        
        pathLayout->addWidget(m_backBtn);
        pathLayout->addWidget(m_pathEdit);
        mainLayout->addLayout(pathLayout);
        
        // 文件列表
        m_fileList = new QListWidget(this);
        mainLayout->addWidget(m_fileList);
        
        // 状态栏(显示文件信息)
        m_statusLabel = new QLabel("就绪", this);
        mainLayout->addWidget(m_statusLabel);
        
        // 连接信号槽
        connect(m_backBtn, &QPushButton::clicked, 
                this, &FileBrowser::goBack);
        connect(m_pathEdit, &QLineEdit::returnPressed,
                this, &FileBrowser::goToPath);
        connect(m_fileList, &QListWidget::itemDoubleClicked,
                this, &FileBrowser::onItemDoubleClicked);
        connect(m_fileList, &QListWidget::itemClicked,
                this, &FileBrowser::onItemClicked);
        
        // 初始显示用户主目录
        m_currentDir = QDir::homePath();
        updateList();
    }

private slots:
    // 返回上级
    void goBack() {
        QDir dir(m_currentDir);
        if (dir.cdUp()) {
            m_currentDir = dir.absolutePath();
            updateList();
        }
    }
    
    // 跳转到指定路径
    void goToPath() {
        QString path = m_pathEdit->text();
        QDir dir(path);
        if (dir.exists()) {
            m_currentDir = dir.absolutePath();
            updateList();
        } else {
            QMessageBox::warning(this, "错误", "路径不存在!");
        }
    }
    
    // 双击条目
    void onItemDoubleClicked(QListWidgetItem *item) {
        QString name = item->text();
        if (name == "..") {
            goBack();
            return;
        }
        
        QString fullPath = m_currentDir + "/" + name;
        QFileInfo info(fullPath);
        
        if (info.isDir()) {
            // 是目录,进入
            m_currentDir = fullPath;
            updateList();
        } else {
            // 是文件,显示信息
            QMessageBox::information(this, "文件信息",
                QString("文件名:%1\n大小:%2 字节\n修改时间:%3")
                    .arg(info.fileName())
                    .arg(info.size())
                    .arg(info.lastModified().toString("yyyy-MM-dd hh:mm:ss"))
            );
        }
    }
    
    // 单击条目(显示文件大小)
    void onItemClicked(QListWidgetItem *item) {
        QString name = item->text();
        if (name == "." || name == "..") {
            m_statusLabel->setText(name);
            return;
        }
        
        QString fullPath = m_currentDir + "/" + name;
        QFileInfo info(fullPath);
        
        if (info.isFile()) {
            m_statusLabel->setText(
                QString("%1 - %2 字节")
                    .arg(name)
                    .arg(info.size())
            );
        } else {
            m_statusLabel->setText(name + " - 文件夹");
        }
    }

private:
    // 更新文件列表
    void updateList() {
        m_pathEdit->setText(m_currentDir);
        m_fileList->clear();
        
        QDir dir(m_currentDir);
        
        // 获取所有条目(文件+目录)
        QFileInfoList entries = dir.entryInfoList(
            QDir::AllEntries | QDir::NoDot,  // 不显示.
            QDir::DirsFirst | QDir::Name      // 目录在前,按名称排序
        );
        
        for (const QFileInfo &info : entries) {
            QListWidgetItem *item = new QListWidgetItem();
            
            if (info.isDir()) {
                item->setText("📁 " + info.fileName());
            } else {
                item->setText("📄 " + info.fileName());
            }
            
            m_fileList->addItem(item);
        }
        
        // 统计
        int fileCount = dir.entryList(QDir::Files).count();
        int dirCount = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot).count();
        m_statusLabel->setText(
            QString("%1 个文件,%2 个文件夹")
                .arg(fileCount)
                .arg(dirCount)
        );
    }

private:
    QLineEdit *m_pathEdit;
    QPushButton *m_backBtn;
    QListWidget *m_fileList;
    QLabel *m_statusLabel;
    QString m_currentDir;
};

5.2 功能说明

这个简易文件浏览器有这些功能:

  • ✅ 浏览目录(双击进入文件夹)
  • ✅ 返回上级目录
  • ✅ 地址栏跳转
  • ✅ 目录和文件用图标区分
  • ✅ 单击显示文件大小
  • ✅ 双击文件显示详细信息
  • ✅ 状态栏显示统计信息

虽然简单,但核心的文件浏览功能都有了!

六、今日总结

今天我们学习了文件操作的下篇,主要是文件信息和目录操作。

知识点汇总

用途 特点
QFileInfo 文件信息 查询文件属性(大小、时间、权限等)
QDir 目录操作 遍历、创建、删除目录
QTemporaryFile 临时文件 自动生成文件名,自动删除
QFileSystemWatcher 文件监控 监控文件/目录变化,信号通知

重要概念

  • QFileInfo:获取文件的各种信息(名称、大小、时间、权限、类型)
  • QDir:目录操作(遍历、过滤、排序、创建、删除)
  • entryList / entryInfoList:获取目录内容,支持过滤和排序
  • mkdir / mkpath:创建目录(mkpath支持多级)
  • rmdir / rmpath:删除目录(只能删空目录)
  • 临时文件:QTemporaryFile,自动命名,自动删除
  • 文件监控:QFileSystemWatcher,监控变化,信号通知

经验分享

  1. QFileInfo很实用:获取文件信息不用自己写代码,直接用QFileInfo
  2. entryInfoList比entryList更方便:直接得到QFileInfo列表,不用再一个个创建
  3. mkpath比mkdir好用:可以创建多级目录,不用操心父目录是否存在
  4. 删除目录要小心:rmdir只能删空目录,非空目录要先删里面的文件
  5. 临时文件很方便:QTemporaryFile自动管理,不用操心清理
  6. 文件监控慎用:不要监控太多文件,注意性能
  7. 路径分隔符用 /:QT里统一用 / 就行,会自动转换

七、明日预告

明天我们将学习QT数据库编程

内容包括:

  • QT的SQL模块
  • SQLite数据库入门
  • 数据库的增删改查
  • QSqlTableModel模型
  • 综合实战:学生管理系统

数据库是很多应用的核心,明天我们一起来学习!


📝 学习建议:文件操作是基本功,一定要练熟。

练习建议:

  • 把今天的文件浏览器代码敲一遍运行看看
  • 试试给文件浏览器加一个"新建文件夹"功能
  • 试试写一个程序,统计某个目录下所有文件的总大小
  • 试试用QFileSystemWatcher监控一个文件夹,有变化时打印信息

文件操作上下两篇都学完了,你已经掌握了QT文件操作的核心内容!明天我们继续学数据库,继续加油!💪