7.1 文件系统概览

本节介绍文件系统相关知识,先大致讲解一下 Windows、Unix/Linux 的文件系统概貌,然后介绍 Qt 中目录浏览类 QDir 和文件信息类 QFileInfo。通过 QDir 和 QFileInfo 类协作,能够浏览文件系统中可访问的目录和文件,并且这些操作对 Qt 程序运行时的内部资源系统也是通用的,操作 Qt 程序内部资源系统中的目录和文件与真实文件系统是很类似的,唯一区别是 Qt 程序运行时的内部资源系统是只读的。
QDir 和 QFileInfo 类内容比较多,不需要都记住,但本节最后的两个示例代码要学会,第一个示例获取程序运行时的环境相关的路径,并判断文件夹或文件是否存在;第二个综合示例展示文件系统的浏 览和文件信息显示,运用简单的列表控件列出系统中的目录和文件。

7.1.1 文件系统介绍

操作系统所有的程序都离不开文件系统,整个操作系统其实就是各种文件夹和文件组成的,各种文件功能不同。Windows 操作系统磁盘分区格式常见为 NTFS 和 FAT32,Linux 常见的分区格式是 Ext3、Ext4、BTRFS 等。磁盘分区格式是对物理磁盘的逻辑划分,不同的操作系统划分分区的方式不同,文件系统的根目录也有区别。我们本小节先介绍 Windows 的文件系统,然后介绍 Unix/Linux 的文件系统。
(1)Windows 文件系统
Windows 通过“我的电脑”或者“资源管理器”都可以看到文件系统划分情况:
winroots
Windows 系统通常一个磁盘分区对应一个盘符,比如 C:\ ,D:\ 等等,注意每一个盘符就是一个文件系统根!
“我的电脑”只是虚拟的称呼,它不是文件系统,真正的文件系统就是各个盘符管辖的分区范围,多个磁盘分区就是多个文件系统。
对于某一个盘符下的文件系统,里面可以存放各种文件夹和文件,以 Qt 开发环境的文件夹为例:
winfiles
在上图地址栏里面显示的是完整的文件夹路径:C:\Qt\Qt5.4.0 ,该文件夹里面有各种子文件夹和文件,子文件夹或文件都有各自的详细信息,一般有四个基本的信息:名称、大小、类型、修改日期,并且可以根据这四个基本信息对子文件夹和文 件进行排序。Qt 对文件系统的访问是通过 QDir 和 QFileInfo 的协同,QDir 负责切换目录、枚举子文件夹和文件,并且可以对子文件夹、文件进行排序和过滤;而 QFileInfo 用于获取单个子文件夹或单个文件的详细信息,比如四个基本信息以及其他信息,比如读写权限等等。接下来我们看看 Unix/Linux 文件系统的情况,并对比一下二者的区别。

(2)Unix/Linux 文件系统
这里示范的是 openSUSE 操作系统,磁盘分区是 Ext4 格式,打开文件管理器,进入根分区可以看到下图所示:
linuxroot
Unix/Linux 文件系统根是唯一的,只有 / 。文件系统根 / 对应一个磁盘分区,这是肯定的,但是其他磁盘分区跑哪里去了?对于 Unix/Linux ,其他所有磁盘分区都是挂载到某个文件夹,而没有其他文件系统根。比如还有一个磁盘分区挂载到了 /home 文件夹,就是说这个文件夹本身就是一个磁盘分区。Unix/Linux 的逻辑磁盘分区都是挂载到某个文件夹的,而不是一个磁盘分区对应一个文件系统根。
对于 Unix/Linux 新手而言,一定要注意权限问题,根分区里面绝大多数的文件夹、文件对于普通用户都是只读的,一般只有 /home/***/ 里面的普通用户目录是可写的,因此普通用户能写入的文件夹很少,遇到写文件问题一定要考虑是不是没有写入权限。通常 Unix/Linux 系统中只有 root 用户能够读写所有的文件夹和文件,可以在命令行用 su 命令切换到 root 用户,然后再进行写入操作。
我们示范安装 Qt5.4.0 是装到用户可写的 /home/***/ 目录里的,比如下面截图:
linuxfiles
Unix/Linux 系统的文件路径分隔符是 '/' ,而 Windows 系统的文件路径分隔符是 '\' 。
对于 Qt 而言,内部路径字符串统一用 Unix/Linux 系统风格,都是 '/' ,所以 Qt 程序代码里面建议都用 '/' 作为文件路径分隔符,Qt 在不同的操作系统里面自动会把 '/' 换成本地化的文件路径分隔符来访问本地文件系统,而不需要程序员操心。
Qt 类库对不同操作系统里面的文件系统的建模是一样的,都是利用 QDir 进行切换目录、枚举子文件夹和文件,并且可以对子文件夹、文件进行排序和过滤;而 QFileInfo 用于获取单个子文件夹或单个文件的详细信息,比如名称、大小、类型、修改日期等等。
我们对 Windows 和 Unix/Linux 的文件系统作一下简单的对比:

对比项 Windows Unix/Linux Qt类或函数
路径分隔符 '\' '/' QDir::​separator() 获取该分隔符
文件系统根 多个根,C:\,D:\ 等等 唯一的 / QDir::​drives() 枚举文件系统根
目录操作 命令行 cd 切换目录,dir 枚举 命令行 cd 切换目录,ls 枚举 QDir 负责切换目录,枚举文件夹和文件
文件信息 命令行 dir 文件名 命令行 ls 文件名 QFileInfo 负责查询单个文件夹或文件详细信息
磁盘逻辑分区 NTFS、FAT32 Ext3、Ext4、BTRFS等 QStorageInfo 可以查询分区的信息(Qt 5.4 以上版本才有这个类)

在对文件系统有了基本的认识之后,我们来详细学习一下 QDir 和 QFileInfo 类的内容,最后通过一个文件浏览的例子运用这些知识。QStorageInfo 类比较新,运用的也比较少,与文件操作几乎无关,我们放到 7.5 节顺便提一下。

7.1.2 目录浏览类 QDir

本小节内容主要来自 QDir 类的帮助文档,Qir 类用于访问文件系统中的目录结构和文件等。QDir 类的静态函数通常用于获取程序运行时系统相关的一些路径信息等,而对于具体某一个目录的操作,需要新建 QDir 对象来实现。
QDir 用于操作路径文件名,访问关于目录路径、文件等信息,操作真实的底层文件系统。同时,QDir 的功能对 Qt 程序运行时的内部资源系统也是通用的,内部资源系统以 ":/" 为根目录。
Qt 都是以 '/' 作为统一的路径分隔符,互联网上也是用 '/' 作为 URL 分隔符的。Qt 程序员应当总是使用 '/' 作为路径分隔符,而不用管操作系统原本的路径分隔符是什么。Qt 会自动把自己的路径文件名转换为底层文件系统可接受的形式,从而操作底层文件系统。
QDir 既可以接受相对文件路径,也可以接受绝对文件路径。相对文件路径通常不以文件系统根打头,而绝对路径总是以文件系统根打头,比如 C:/、D:/ 、/ 等等(从这里开始都使用 Qt 默认的路径分隔符)。相对路径的基准目录通常是应用程序的工作路径,比如 QtCreator 调试运行程序时,就以影子构建目录作为基准目录,其他情况通常是以可执行程序所在的目录为准。
绝对路径举例:
QDir("/home/user/Documents")
QDir("C:/Documents and Settings")
第一个绝对路径是 Unix/Linux 系统里的形式,第二个绝对路径是 Windows 里的形式。Qt 代码里面都是使用 '/' ,作为路径分隔符,底层文件系统的本地路径分隔符不用操心,交给 Qt 自己处理就行了。
相对路径举例:
QDir("images/landscape.png")
QDir 对象可以通过函数 isRelative() 判断自己包含的路径是否为相对路径,通过函数 isAbsolute() 判断自己包含的路径是否为绝对路径形式。另外可以通过 makeAbsolute() 函数
bool QDir::​makeAbsolute()
把自己的路径确切地转换为绝对路径,这样后续操作的意义更为明确。
下面将 QDir 类的接口函数按照功能进行大致的分类讲解:
(1)导航和目录操作
QDir 构造函数可以指定该对象的起始浏览目录:
QDir(const QDir & dir)
QDir(const QString & path = QString())
QDir(const QString & path, const QString & nameFilter, SortFlags sort = SortFlags( Name | IgnoreCase ), Filters filters = AllEntries)
第一个构造函数是复制另一个 QDir 对象。第二个是以路径字符串构造 QDir 对象,这也是最常用的构造函数,如果参数里字符串是空的,那么就以应用程序当前工作目录 "." 作为起始目录。第三个构造函数可以指定文件名过滤字符串 nameFilter、排序方式 sort ,以及文件类型过滤枚举 filters 。
构造好 QDir 对象之后,就可以获取该对象当前处于的目录:
QString QDir::​path() const    //可能是相对路径,也可能是绝对路径
QString QDir::​absolutePath() const    //一定返回绝对路径
如果要重新设置 QDir 对象的路径,那么可以通过如下函数设置:
void QDir::​setPath(const QString & path)
注意 ​setPath() 函数是不检查参数 path 是否存在,也不管是不是有 "./../.." 之类复杂的相对路径形式,所以调用该函数可能进入一个不存在的目录,如果进一步操作可能导致出错,需要在调用该函数之前用 ​exists() 判断参数里的路径是否存在:
bool QDir::​exists(const QString & name) const    //参数里既可以判断文件夹,也可以判断文件的存在性
bool QDir::​exists() const    //不带参数,判断 QDir 对象自身路径是否为存在的文件夹,只管文件夹!
第一个带参数的 ​exists(const QString & name) 函数,既可以判断文件夹的存在性,也可以判断文件的存在性。
第二个只针对 QDir 对象自身路径是否为实际文件夹进行判断,如果 QDir 对象自身路径是一个存在的文件,那么也会返回 false,只有是真实的文件夹 才返回 true。

QDir 对象可以用  dirName() 返回该层级目录的简短名字,不包含路径,通常就是绝对路径的最后一个子串:
QDir("Documents/Letters/Applications").dirName()   //返回 "Applications"
如果 QDir 构造函数里字符串为空,那么 dirName() 返回当前目录 "." :
QDir().dirName()                                 //返回  "."
除了 QDir 构造函数和 setPath() 函数,更常用的修改 QDir 对象当前目录位置的函数是 cd():
bool QDir::​cd(const QString & dirName)
cd() 函数比 setPath() 函数更智能,应该尽量用 cd() 函数,因为 cd() 函数会自动检查参数里的目录是否存在,如果参数里路径不存在,那么该函数返回 false,QDir 对象原本的路径不变;如果切换目录成功就返回 true,并进 入新目录。
QDir 对象有切换到父目录的快捷函数:
bool QDir::​cdUp()
切换成功就返回 true(有父目录);切换失败就返回 false(没父目录,原本已经到根了)。

QDir对象不仅能访问文件夹,还能在自己当前的目录里面进行创建、重命名和删除的操作:
bool QDir::​mkdir(const QString & dirName) const    //创建新目录
bool QDir::​rename(const QString & oldName, const QString & newName) //重命名旧的目录
bool QDir::​rmdir(const QString & dirName) const    //删除一个空目录
这些操作如果成功就返回 true,失败就返回 false。注意:
​mkdir() 参数里的文件路径不能是已经有的(创建已有的会返回失败),必须是新的;
​rename() 参数里,旧的目录名 oldName 必须存在,新的目录名 newName 不能与已有的重名;
rmdir() 函数只能删除空文件夹,如果文件夹里面有其他文件或子文件夹就不能删除。
操作失败的原因,还有可能是没有权限,因为 Unix/Linux 普通用户经常没有写权限,这些操作就容易失败。对于 Unix/Linux 普通用户,应该只操作自己有写入权限的 /home/***/ 路径里面的文件夹和文件。

对于递归创建新的长路径,QDir 提供了快捷函数:
bool QDir::​mkpath(const QString & dirPath) const
dirPath 可以是长路径,比如 "/home/suse132/Projects/a/b/c" ,对于路径上已有的文件夹"/home/suse132/",不会构造新的,而后半部不存在的 "Projects/a/b/c" ,该函数会逐级递归创建新文件夹,直到 "/home/suse132/Projects/a/b/c" 每一个层级文件夹都存在。如果创建成功或者 dirPath 已经存在,都会返回 true,创建失败就返回 false。
​mkpath() 函数的逆操作函数是:
bool QDir::​rmpath(const QString & dirPath) const
注意 ​rmpath() 只能删除空的文件夹,如果参数里是 "Projects/a/b/c",只要其中有一层的文件夹里面有其他子文件夹或文件,都会操作失败。 而 且不要以绝对路径为参数调用 rmpath() 函数,因为文件系统根总是存在的,不能删除,不要这么干!

QDir对象可以对自己目录里的一些属性进行判断,比如:
bool QDir::​exists(const QString & name) const    //子文件夹或文件是否存在
bool QDir::​isReadable() const    //QDir对象当前目录是否有读权限,Linux系统有些目录普通用户不能读取
bool QDir::​isAbsolute() const    //QDir对象当前目录是否为绝对路径
bool QDir::​isRelative() const    //QDir对象当前目录是否为相对路径
bool QDir::​isRoot() const    //QDir对象当前目录是否为文件系统根
一般 QDir 对象会对先前的文件系统状态有个缓存,如果在程序运行时文件系统改变了,可以通过如下函数刷新一下缓存,重新访问文件系统:
void QDir::​refresh() const

(2)文件和目录内容枚举
目录里面可以包含一大堆条目,每个条目可以是文件、子文件夹、符号链接(快捷方式)等,目录里面的条目计数为:
uint QDir::​count() const
如果要获取 QDir 对象当前目录所有子条目名称字符串列表,使用函数:
QStringList QDir::​entryList(Filters filters = NoFilter, SortFlags sort = NoSort) const
QStringList QDir::​entryList(const QStringList & nameFilters, Filters filters = NoFilter, SortFlags sort = NoSort) const
函数参数里的 filters 是指条目类型的过滤枚举类型,sort 是指返回的条目列表的排序方式;第二个 ​entryList() 函数里的 nameFilters 是指条目名称的过滤字符串列表 。这几个参数都有专门对应的设置函数,等会再讲。entryList() 函数返回的字符串列表打印出来就是得到的文件、子文件夹、符号链接(快捷方式)名字。
当前目录里的子条目当然不仅仅有名字,还有其他很多属性,比如文件类型、大小、修改日期、读写属性等等,单个文件或文件夹的详细信息是用 QFileInfo 对象表示的,从 QDir 对象的目录枚举出所有子条目详细信息的函数为:
QFileInfoList QDir::​entryInfoList(Filters filters = NoFilter, SortFlags sort = NoSort) const
QFileInfoList QDir::​entryInfoList(const QStringList & nameFilters, Filters filters = NoFilter, SortFlags sort = NoSort) const
​entryInfoList() 函数的参数同上面 ​entryList() 的参数,只是返回结果为 QFileInfo 的列表 QFileInfoList 。QFileInfoList 列表当作数组来用就行了,序号从 0 到 count()-1 ,每个条目对应一个文件或子文件夹或符号链接。
在不过滤的情况下,entryList() 和 entryInfoList() 返回的列表包含 "." 和 ".." 目录,一个点代表当前目录自己,两个点代表父目录。

注意 QDir 对象内部存储的当前目录,可以是真实存在的路径,也可以是虚假的不存在路径:
◆ 实际存在的当前目录通常用于枚举或访问真实子文件夹、文件等,可以用 ​exists(const QString & name) 函数判断子文件 夹或文件是否存在。
◆ 对于不存在的当前目录,QDir 对象也可以照常工作,但因为不存在,所以枚举子条目肯定是空的。如果 QDir 对象里面存储的是不存在的当前目录,那么通常用于构造路径字符串,用于下一步构建路径,比如用于 mkpath() 函数。

根据 QDir 对象里的当前目录,配合 filePath() 和 absoluteFilePath() 函数可以构造出文件的路径,这些路径可以是都不存在的,也可以是真实的,也是用 ​exists() 函数判断存在性,以下面代码为例:
    QDir directory("Documents/Letters");
    QString path = directory.filePath("contents.txt");
    QString absolutePath = directory.absoluteFilePath("contents.txt");
    qDebug()<<directory.exists();
    qDebug()<<path;
    qDebug()<<absolutePath;
如果程序的工作路径(QtCreator调试运行程序时为影子构建目录)为
D:\QtProjects\ch02\build-hellocreator-Desktop_Qt_5_4_0_MinGW_32bit-Debug
实际上在这个路径里,根本没有 "Documents/Letters" 这两级子目录,上面代码还会正常工作,它就是分别计算文件 "contents.txt" 的相对于 QDir 对象的相对路径,以及 "contents.txt" 文件的绝对路径,上面一段代码的结果就是:
false
"Documents/Letters/contents.txt"
"D:/QtProjects/ch02/build-hellocreator-Desktop_Qt_5_4_0_MinGW_32bit-Debug/Documents/Letters/contents.txt"
这些路径其实都不存在,filePath() 和 absoluteFilePath() 函数仅仅是拼接出文件的完整路径,而不管它存在或不存在。

在使用 QDir 对象时,一定要用 exists() 函数谨慎判断路径是否真实存在!

如果要删除一个已存在的文件,使用如下函数:
bool QDir::​remove(const QString & fileName)
删除成功就返回 true,如果没有该文件或者没有删除权限等,删除失败返回 false。
删除一个空目录用之前说过的 ​rmdir() 函数。

下面讲讲枚举子条目时的过滤和排序,除了可以直接在 ​entryList() 和 ​entryInfoList() 指定各个参数,还可以用专门的函数设置这两个函数用到的参数:
①名称字符串过滤设置
void QDir::​setNameFilters(const QStringList & nameFilters)
nameFilters 是针对子条目名称的过滤串,参考帮助文档主题  QRegExp wildcard matching 的内容,常用的就是 *,匹配任意个数字符,比如
    QStringList filters;
    filters << "*.cpp" << "*.cxx" << "*.cc";
    dir.setNameFilters(filters);
这段代码就是过滤后得到 "*.cpp" 、 "*.cxx" 、 "*.cc" 三个扩展名的源代码文件。
QDir 类另外提供了一个静态函数,用于判断某个文件名是否匹配某个名称过滤串:
bool QDir::​match(const QString & filter, const QString & fileName)   //静态函数

②类型权限等过滤设置
void QDir::​setFilter(Filters filters)
这个 filters 与条目名称无关,它判断的是条目类型,Filters 枚举常量很多,根据 QDir 的帮助文档列举如下:

Filters 枚举常量 数值 描述
QDir::Dirs 0x001 列出匹配名称过滤串的目录
QDir::AllDirs 0x400 列出所有目录,不管名称过滤串
QDir::Files 0x002 列出文件
QDir::Drives 0x004 列出磁盘分区盘符(Unix/Linux 忽略这个值)
QDir::NoSymLinks 0x008 不列举符号链接
QDir::AllEntries Dirs | Files | Drives 列出目录、文件、磁盘分区盘符、符号链接
QDir::NoDot 0x2000 不列举 "."
QDir::NoDotDot 0x4000 不列举 "..",当前的父目录
QDir::NoDotAndDotDot NoDot | NoDotDot 不列举 "." 和 ".."
QDir::NoFilter -1 条目类型不做任何过滤,默认是这个
空行 空行 上面的常量比较常用,下面的枚举常量基本与文件系统权限有关:
QDir::Readable 0x010 列出本应用程序可以读取的文件,注意 Readable 枚举值需要与 Dirs 或 Files 结合使用。
QDir::Writable 0x020 列出本应用程序可以写入的文件,注意 Writable 枚举值需要与 Dirs 或 Files 结合使用。
QDir::Executable 0x040 列出本应用程序具有执行权限的文件,注意 Executable 枚举值需要与 Dirs 或 Files 结合使用。
QDir::Modified 0x080 只列出曾经被修改过的文件(Unix系统忽略这个值)
QDir::Hidden 0x100 列出隐藏文件(Unix系统以 '.' 打头的文件名)。
QDir::System 0x200 列出系统文件(Unix系统中包含 FIFOs、套接字、设备文件等,Windows系统中包含 .lnk 文件)
QDir::CaseSensitive 0x800 名称过滤字符串中字母是大小写敏感的。

一般对于条目类型,使用默认的不过滤即可,如果确定要过滤为仅文件或仅文件夹,可以用上面的枚举常量进行筛选。QDir::Readable、 QDir::Writable 、QDir::Executable 是 Unix/Linux 系统里常见的权限划分,需要与 QDir::Dirs 或者 QDir::Files 做按位或 | ,结合使用。

③排序设置
void QDir::​setSorting(SortFlags sort)
这个参数决定 ​entryList() 和 ​entryInfoList() 返回列表的排序方式,SortFlags 排序的枚举常量如下:

SortFlags 枚举常量 数值 描述
QDir::Name 0x00 按名称排序
QDir::Time 0x01 按时间排序(修改时间)
QDir::Size 0x02 按大小排序
QDir::Type 0x80 按类型排序(扩展名)
QDir::Unsorted 0x03 不排序
QDir::NoSort -1 不排序,默认是这个值
QDir::DirsFirst 0x04 文件夹排在前面,文件排在后面
QDir::DirsLast 0x20 文件夹排在后面,文件排在前面
QDir::Reversed 0x08 逆序排列
QDir::IgnoreCase 0x10 排序时大小写不敏感
QDir::LocaleAware 0x40 根据当前本地化的设置进行恰当地排序(有些本地化语言从右往左读)

注意前四个排序常量只能四选一,不能同时运用。一般在前四个选一个作为排序标准,后面的枚举常量与前面四选一的做 | 运算,后面的枚举常量就可以同时生效。

无论是通过上面三个设置函数专门设置,还是直接在 ​entryList() 和 ​entryInfoList() 指定各个参数,都是可行的,效果是等价的。

(3)应用程序工作路径和其他特殊路径
这部分都是 QDir 类的静态函数,属于该应用程序自己的全局设置,这些静态函数返回 QDir 对象或 QString 字符串,列举如下:

返回QDir对象 返回QString 静态函数描述
current() currentPath() 应用程序的工作目录(这个可以更改)
home() homePath() 用户的主文件夹,或叫家目录
root() rootPath() 操作系统的根分区,Unix 是"/" ,Windows 一般是 "C:/"
temp() tempPath() 操作系统里默认的临时文件目录

其中应用程序自己的工作路径是可以改的:
bool QDir::​setCurrent(const QString & path)
其他的路径一般都是系统定死的,Qt 是不修改其他三个路径的。
应用程序的工作路径不等于可执行程序所在的目录,比如 QtCreator 调试运行程序时,工作路径是影子构建目录。获取可执行程序所在的目录使用另一个类的静态函数:
QString QCoreApplication::​applicationDirPath()    //静态函数
联合 QDir::​setCurrent() 和 QCoreApplication::​applicationDirPath() 就可以把程序的工作路径设置为与可执行文件相同的路径:
    QDir::setCurrent( QCoreApplication::applicationDirPath() );
    qDebug()<<QDir::currentPath();
把上面代码放到主界面的构造函数里就能在程序运行时自动修改工作路径。

如果要获取文件系统根的列表,使用 QDir::​drives() 静态函数,之前 Windows 和 Linux 文件系统截图都提到这个函数:
QFileInfoList QDir::​drives()    //静态函数
对于 Windows 系统,一般有多个文件系统根,如 C:/ ,D:/ 等等,对于 Unix 和 Linux 系统,只有一个根 / 。

(4)路径操作和字符串
相对路径中经常包含一些 "." 代表当前目录,".." 代表父目录,还有一些符号链接(快捷方式)指向其他真实文件。
Qt 提供了如下函数对这些进行处理规约,得到绝对权威路径:
QString QDir::​canonicalPath() const    //会自动判断路径是否真实存在
如果规约后的当前路径是不存在的,那么该函数返回空串,如果规约后的当前路径是真实存在的,就返回最简化的绝对权威路径。

另一个弱化版的静态规约函数是 cleanPath(),它不检查路径是否真实存在,也不管是不是符号链接(快捷方式),它只对 "." 、 ".."  "/" 等做规约处理,去掉冗余的点号和斜杠:
QString QDir::​cleanPath(const QString & path)    //静态函数

在特殊场合,可能需要将 Qt 统一的路径字符串形式与操作系统本地化的文件路径形式进行互相转换,QDir 提供一对静态函数实现这个转换:
QString QDir::​fromNativeSeparators(const QString & pathName)    //静态函数,从本地化路径串转为 Qt 风格路径串
QString QDir::​toNativeSeparators(const QString & pathName)      //静态函数,将 Qt 风格路径串转为本地化路径串

关于 QDir 类的内容介绍就到这,最后小节的文件浏览示例是用最简单的接口,也不排序和过滤,就按照默认的参数列举文件夹和文件,等到下一章学到表格控件时再做排序和过滤方面的功能。

7.1.3 文件信息类 QFileInfo

本小节内容来自 QFileInfo 类的帮助文档,QFileInfo 类提供了不依赖具体系统的文件(指文件和文件夹,Unix系统中文件夹也是算文件的,下 同)信息。
QFileInfo 提供关于文件的名称、在文件系统中的位置路径等信息,它访问权限信息、判断是不是文件夹或符号链接(快捷方式)等。文件的大小和最后修改/读取时间也是可以获取的。 QFileInfo 还能用于获取 Qt 程序运行时的内部资源系统文件信息。

QFileInfo 既可以根据相对路径,也可以根据绝对路径指向一个文件。绝对路径总是以文件系统根打头(/、C:/、D:/ 等)。相对路径直接以某个目录名或文件名打头,并指出相对于程序当前工作路径的位置。绝对路径例子如 "/tmp/quartz"。相对路径举例如 "src/fatlib",这是相对于程序工作路径的形式。QFileInfo 提供了如下函数判断当前文件路径是否为相对路径:
bool QFileInfo::​isRelative() const
也可以用如下函数,明确地把当前文件路径转为绝对路径:
bool QFileInfo::​makeAbsolute()
makeAbsolute() 函数如果是真的是把相对路径转为绝对路径,那么返回 true,如果原本就是绝对路径,那么返回 false。
QFileInfo::​makeAbsolute() 函数返回值与之前的 QDir::makeAbsolute() 返回值有区别:之前的 QDir::makeAbsolute() 实际测试总是返回 true,而 QFileInfo::​makeAbsolute() 如果判断出原本就是绝对路径,它会返回转换失败。

QFileInfo 可以在构造函数或者随后用 setFile() 函数指定要访问的文件,其构造函数如下:
QFileInfo()    //无参数,后面可以用 setFile() 函数设置文件名
QFileInfo(const QString & file)   //根据相对路径或绝对路径的字符串,访问文件
QFileInfo(const QFile & file)    //从一个 QFile对象提取该文件的信息
QFileInfo(const QDir & dir, const QString & file)  //根据路径对象和相对它的文件名,访问文件夹信息
QFileInfo(const QFileInfo & fileinfo)    //复制构造函数
与构造函数参数类似的,setFile() 函数重载有三个:
void QFileInfo::​setFile(const QString & file)   //根据相对路径或绝对路径访问文件信息
void QFileInfo::​setFile(const QFile & file)     //从已有 QFile 对象提取出文件信息
void QFileInfo::​setFile(const QDir & dir, const QString & file) //根据路径对象和相对它的文件名,访问文件夹信息

设置好需要访问的文件名之后,下面就是判断该文件有哪些信息了,首先要判断是否存在:
bool QFileInfo::​exists() const
存在性得到确认之后,才能进行下一步信息获取。常见获取文件信息的函数罗列如下:
(1)文件、目录、符号链接类型判断

函数 描述
bool ​isFile() 是否为文件(不是文件夹),符号链接指向文件也算
bool ​isDir() 是否为文件夹,符号链接指向文件夹也算
bool ​isSymLink() 是否为符号链接(快捷方式)
QString ​symLinkTarget() 如果是符号链接,就返回链接指向的原本文件夹或文件,否则返回空串
qint64 ​size() 返回文件大小,如果文件不存在或者无权限读取,那么返回 0

对于正常的文件和文件夹,上面函数意义比较清晰,比较特殊的是符号链接(快捷方式),对于 Unix 系统(含 Linux、Mac OS X),符号链接会由底层操作系统处理,符号链接的 size() 结果等同于链接指向的原本文件大小,如果用 QFile 打开符号链接,那么也是打开链接指向的原本文件。代码举例:
#ifdef Q_OS_UNIX

QFileInfo info1("/home/bob/bin/untabify");
info1.isSymLink();          // returns true
info1.absoluteFilePath();   // returns "/home/bob/bin/untabify"
info1.size();               // returns 56201
info1.symLinkTarget();      // returns "/opt/pretty++/bin/untabify"

QFileInfo info2(info1.symLinkTarget());
info2.isSymLink();          // returns false
info2.absoluteFilePath();   // returns "/opt/pretty++/bin/untabify"
info2.size();               // returns 56201

#endif
"/home/bob/bin/untabify" 是一个符号链接,链接指向的本体文件是 "/opt/pretty++/bin/untabify" ,符号链接的文件大小等同于原本文件的大小。这里可以另外学到一个知识,通过判断宏 Q_OS_UNIX,可以知道当前操作系统是不是 Unix 系列的。

对于 Windows 系统里的快捷方式,即 *.lnk 文件实体,size() 函数返回该实体 *.lnk 文件的大小,而不是链接指向的本体文件大小。如果用 QFile 打开 *.lnk 文件,那也是打开快捷方式文件自身,而不是打开链接指向的本体文件,这点与 Unix 系统不同,需要注意。代码举例:
#ifdef Q_OS_WIN

QFileInfo info1("C:\\Documents and Settings\\Bob\\untabify.lnk");
info1.isSymLink();          // returns true
info1.absoluteFilePath();   // returns "C:/Documents and Settings/Bob/untabify.lnk"
info1.size();               // returns 743
info1.symLinkTarget();      // returns "C:/Pretty++/untabify"

QFileInfo info2(info1.symLinkTarget());
info2.isSymLink();          // returns false
info2.absoluteFilePath();   // returns "C:/Pretty++/untabify"
info2.size();               // returns 63942

#endif
info1 构造函数里使用的是本地化路径分隔符,注意 C++ 里面字符串中 "\" 是转义字符,如果表示反斜杠自己,字符串中需要用 "\\" 表示一个反斜杠。

(2)路径文件名解析
获取 QFileInfo 对象里面的完整路径文件名,可以通过如下函数:
QString QFileInfo::​filePath() const            //含路径和文件名,可能是相对的,也可能是绝对的路径文件名
QString QFileInfo::​absoluteFilePath() const    //返回绝对路径文件名
QString QFileInfo::​canonicalFilePath() const   //返回权威路径文件名
canonicalFilePath() 会对多余的 "."、".."、"/" 做规约,如果是 Unix 符号链接就返回本体文件的绝对路径文件名。

如果只获取路径部分,而不带文件名,使用如下函数:(和刚才三个对比,函数名里有 "file" 字样的,返回结果才带有文件名)
QString QFileInfo::​absolutePath() const   //返回绝对路径目录,不含文件名
QString QFileInfo::​canonicalPath() const  //返回权威路径目录,不含文件名
canonicalPath() 是类似的做规约,但只返回路径部分,而不带文件名。

完整的路径文件名是可以拆解开来的,可以划分为路径和文件名自身:
QString QFileInfo::​path() const      //返回当前路径文件名前面的路径部分
QString QFileInfo::​fileName() const  //返回文件名部分
举例:
① 文件 "/tmp/archive.tar.gz" 为例,path() 返回 "/tmp" ,fileName() 返回 "archive.tar.gz" 。
② 如果 QFileInfo 对象里面的完整路径文件名是以分隔符 ('/') 结尾,比如 "/home/suse132/" ,那么 path() 返回 "/home/suse132" ,​fileName()返回的是空串。
③ 如果完整文件名是 "/home/suse132" ,那么 path() 返回 "/home" ,fileName() 返回 "suse132" 。

注意这两个函数是不检查文件或文件夹是否存在,它们就是单纯地拆解完整文件名为两个部分而已,它们将最后一个路径分隔符左边的归为路径 path(),右边的归为文件名 fileName()。如果末尾就是分隔符,那么文件名 fileName() 为空。

Qt 类库中涉及到返回文件夹路径(分 区根路径除外)的函数,返回的字符串一般不带拖尾的路径分隔符 '/' ,如果需要在文件夹路径字符串末尾添加分隔符,可以自己用代码添加一个。

对于去除路径的文件名,比如 "archive.tar.gz" ,还可以进一步进行拆解:
QString QFileInfo::​baseName() const        //去除所有扩展名,得到基本名,如 "archive"
QString QFileInfo::​completeSuffix() const  //全部的扩展名,如 "tar.gz"
也就是说 baseName() + completeSuffix() == 原文件名。

文件名还有另一种拆解方式:
QString QFileInfo::​suffix() const          //最后一个扩展名,如 "gz"
QString QFileInfo::​completeBaseName() const  //去除最后一个扩展名的前面部分,如 "archive.tar"
这里的 completeBaseName() + suffix() == 原文件名。

(3)访问权限判断
关于文件的创建时间、修改时间,读、写、执行权限,所属用户、组别以及详细权限信息等,可以用下表中的函数获取:

函数 描述
QDateTime ​created() 文件创建时间
QDateTime ​lastModified() 最后修改时间
QDateTime ​lastRead() 最后读取时间
bool isReadable() 用户对该文件是否有读权限
bool ​isWritable() 用户对该文件是否有写权限
bool isExecutable() 用户对该文件是否有执行权限
QString ​owner() 文件所属的用户
uint ownerId() 文件所属的用户ID
QString ​group() 文件所属的组别
uint ​groupId() 文件所属的组别ID

如果希望同时判断某个文件的多项权限信息,可以通过专门的权限判断函数:
bool QFileInfo::​permission(QFile::Permissions permissions) const
参数 permissions 可以是多项权限枚举标志的按位或运算结果,同时判断该文件是否同时具有这些权限:

枚举常量 数值 描述
QFileDevice::ReadOwner 0x4000 文件所属用户可读
QFileDevice::WriteOwner 0x2000 文件所属用户可写
QFileDevice::ExeOwner 0x1000 文件所属用户可执行
QFileDevice::ReadGroup 0x0040 文件所属组别可读
QFileDevice::WriteGroup 0x0020 文件所属组别可写
QFileDevice::ExeGroup 0x0010 文件所属组别可执行
QFileDevice::ReadOther 0x0004 其他任意用户可读
QFileDevice::WriteOther 0x0002 其他任意用户可写
QFileDevice::ExeOther 0x0001 其他任意用户可执行
QFileDevice::ReadUser 0x0400 当前用户可读
QFileDevice::WriteUser 0x0200 当前用户可写
QFileDevice::ExeUser 0x0100 当前用户可执行

说明一下,QFileInfo 类和 QFile 类都可以判断这些权限,而 QFileDevice 是 QFile 的基类。
表格中前面 9 个都是从 Unix 系统引入权限表示方法,对于 Unix、Linux、Mac OS X 系统这些权限比较重要,对于 Windows 基本不用检查这些权限。
最后 3 个权限 ReadUser、WriteUser、ExeUser 是与程序关系密切的,与系统平台无关,是 Qt 自创的,用于判断当前程序是否能对该文件有读、写、可执行权限。
如果要同时判断当前程序是否对某个文件有读、写、执行权限,可以用下面代码:
QFileInfo fi("/usr/bin/ping");
qDebug()<<fi.permission(QFileDevice::ReadUser
                  | QFileDevice::WriteUser
                  | QFileDevice::ExeUser);
Windows 系统一般判断读写权限(ReadUser | WriteUser)就行了,因为只有特定扩展名的文件才能运行。
而在 Unix 系统中,任意扩展名的文件都可以当可执行文件来运行。

如果要获取当前文件所具有的全部访问权限,可以用如下函数统一获取:
QFile::Permissions QFileInfo::​permissions() const
注意 permissions() 多了一个字母 s,而且没有参数。它的返回值就是文件具备的所有权限,返回值与上面表格列举的权限枚举常量做按位或运算,就可 以判断分别有哪些权限。

对于 Unix 系列的操作系统,上面权限判断默认都是启用的,对于 Windows 的 NTFS 分区,情况不一样。
对于 NTFS 分区的文件,因为性能原因 ,Qt 对文件所属关系(ownership)和访问权限(permissions)的检查默认没有开启。如果需要开启对 NTFS 分区文件的所属关系和权限检查,那么首先手动定义:
extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
开启权限检查就执行下面这句代码:
qt_ntfs_permission_lookup++;    // 开启权限检查
关闭权限检查就执行下面的:
qt_ntfs_permission_lookup--;    // 重新关闭权限检查

(4)性能问题
一些 QFileInfo 的函数需要查询真实的文件系统,比如 canonicalPath() 、absolutePath()是需要查询真实路径位置的,会查询文件系统。
而另外一些函数只访问 QFileInfo 对象自己保存的数据,比如 path() 函数并不会查询文件系统,它只把内部存的路径文件名拆分一下,返回路径部分。
也就是说,与 QDir 类似, QFileInfo 对象可以处理不存在的虚假路径文件名,只用于解析文件名等用途。

注意:为了优化运行效率,QFileInfo 对象会将上次查询的真实文件信息做缓存。如果上次查询后,真实文件被修改了,以前缓存的信息可能是旧的,没更新。
可以通过刷新函数消除旧的缓存信息:
void QFileInfo::​refresh()
如果不希望 QFileInfo 对象缓存以前查询的信息,可以通过如下函数设置:
void QFileInfo::​setCaching(bool enable)

7.1.4 路径查看和判断示例

本小节的例子是运用 QDir 类进行环境路径的查看,并可以判断文件夹和文件路径的存在性。下一小节的例子讲解 QDir 类更大的用途,即浏览文件系统,并用 QFileInfo 类查看文件详细信息。下面先开始本小节路径查看和判断示例的学习。

打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 paths,创建路径 D:\QtProjects\ch07,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
建好项目之后,打开窗体 widget.ui 文件,进入设计模式,按照下图排布,拖入五行控件:
ui
界面涉及的操作如下:
● 主界面窗体的尺寸修改为 600*300,这个窗口比较宽,是用于显示较长的路径。
● 第一行:标签控件文本为 "工作路径",单行编辑控件对象名为 lineEditWorkPath ,这两个控件按照水平布局器排列。
● 第二行:三个按压按钮,第一个对象名 pushButtonGetWorkPath,文本 "获取工作路径",第二个对象名为 pushButtonSetWorkPath,文本为 "设置工作路径",第三个对象名为 pushButtonEnvPaths,文本为 "显示环境路径" ,三个按钮按照水平布局器排列。
● 第三行:标签控件文本为 "测试路径",单行编辑控件对象名为 lineEditTestPath,第三行也是按照水平布局器排列。
● 第四行:三个按钮按钮,第一个对象名 pushButtonExist,文本 "测试存在性",第二个对象名 pushButtonIsRelative,文本 "测试相对性" ,第三个对象名 pushButtonShowAbsolute,文本 "显示绝对路径" ,这一行也是按照水平布局器排列。
● 第五行:标签文本为 "结果显示",右边的是 QPlainTextEdit 类型文本编辑器,对象名为 plainTextEditResult,这行也是水平布局器排布。
● 主布局器:设置好五行水平布局器之后,点击界面空白位置,不选任何子控件和布局器,点击上面工具栏垂直布局器按钮,这样得到的垂直布局器直接变成窗口的主布局 器。

界面控件和布局设置好之后,分六次,右击按钮,右键菜单选择 "转到槽..." ,为每个按钮都添加 clicked() 信号对应的槽函数:
slots

槽函数都添加完毕后,保存界面文件,回到 QtCreator 代码编辑模式,我们首先看看 widget.h 文件代码,这个文件不需要手动修改,都是自动生成 的代码:
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private slots:
    void on_pushButtonGetWorkPath_clicked();

    void on_pushButtonSetWorkPath_clicked();

    void on_pushButtonEnvPaths_clicked();

    void on_pushButtonExist_clicked();

    void on_pushButtonIsRelative_clicked();

    void on_pushButtonShowAbsolute_clicked();

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H
Widget 类里的六个槽函数对应就是六个按钮。
我们下面来编辑源代码文件 widget.cpp ,添加功能代码,首先是头文件包含和构造函数:
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QDir>         //目录浏览类
#include <QFileInfo>    //文件信息类
#include <QMessageBox>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    //设置结果文本框为只读
    ui->plainTextEditResult->setReadOnly(true);
}

Widget::~Widget()
{
    delete ui;
}
头文件包含新增了四个,调试类头文件和消息框头文件以前就用过,目录浏览类和文件信息类是本节的内容。
在 Widget 类构造函数就添加了一句代码,把结果显示框 ui->plainTextEditResult 设置为只读的,这样程序运行时用户不能修改结果,而在代码里是可以修改结果框内容的。
Widget 类剩下的代码就是六个槽函数的代码,我们逐个看看,首先是 "获取工作路径" 按钮的槽函数:
void Widget::on_pushButtonGetWorkPath_clicked()
{
    //获取程序当前工作路径
    QString strWorkPath = QDir::currentPath();
    //显示到单行编辑控件
    ui->lineEditWorkPath->setText( strWorkPath );
}
这个槽函数内容很简单,就是用 QDir::currentPath() 函数获取程序的工作路径,并显示到单行编辑控件 ui->lineEditWorkPath 。

第二个按钮 "设置工作路径" 槽函数代码如下:
void Widget::on_pushButtonSetWorkPath_clicked()
{
    //获取单行编辑器里的路径
    QString strNewPath = ui->lineEditWorkPath->text();
    //判断字符串是否为空
    if(strNewPath.length() < 1)
    {
        return;
    }
    //判断路径是否存在
    QDir dirNew(strNewPath);
    QString strResult;  //操作结果显示
    if( dirNew.exists() )
    {
        //存在该路径,设置为工作路径
        bool bRes = QDir::setCurrent( dirNew.path() );
        if( !bRes )
        {
            //设置工作路径失败
            strResult = tr("设置工作路径为 %1 失败。").arg(strNewPath);
            QMessageBox::warning(this, tr("设置错误"), strResult);
        }
        else
        {
            //设置成功
            strResult = tr("设置工作路径成功,新的路径为:\r\n%1").arg( QDir::currentPath() );
        }
    }
    else    //新路径不存在
    {
        strResult = tr("设置工作路径为 %1 失败,该路径不存在!").arg(strNewPath);
        QMessageBox::warning(this, tr("路径不存在"), strResult);
    }
    //显示结果信息
    ui->plainTextEditResult->setPlainText(strResult);
}
该槽函数代码首先获取工作路径编辑控件 ui->lineEditWorkPath 里面的路径存到 strNewPath ,并判断,如果文本为空就直接 返回不干活,如果有文本进行下面操作:
以 strNewPath 路径定义 QDir 对象 dirNew;
调用 dirNew.exists() 判断该文件夹是否存在,如果存在该路径,则进行程序的工作路径设置 QDir::setCurrent()。
代码中对于可能出错的情况进行了消息框提示,并显示错误信息到结果文本框;
如果设置成功,就不弹窗,显示成功的信息到结果文本框。
注意:如果修改了应用程序工作路径,不会影响绝对路径的判断,但会改变相对路径的基准目 录,会影响相对路径的判断!

第三个按钮 "显示环境路径" 对应槽函数代码如下:
void Widget::on_pushButtonEnvPaths_clicked()
{
    QString strWorkPath = QDir::currentPath();  //应用程序的工作目录
    QString strAppPath = QCoreApplication::applicationDirPath();//可执行程序目录
    QString strHomePath = QDir::homePath();     //用户的主文件夹
    QString strRootPath = QDir::rootPath();     //应用程序所在文件系统的根分区
    QString strTempPath = QDir::tempPath();     //操作系统里默认的临时文件目录

    //构造结果串
    QString strResult;
    strResult += tr("工作路径:%1\r\n").arg(strWorkPath);
    strResult += tr("可执行程序目录:%1\r\n\r\n").arg(strAppPath);
    strResult += tr("用户主文件夹:%1\r\n").arg(strHomePath);
    strResult += tr("系统根目录:%1\r\n").arg(strRootPath);
    strResult += tr("临时目录:%1\r\n").arg(strTempPath);
    //显示
    ui->plainTextEditResult->setPlainText(strResult);
}
这个槽函数专门用静态函数获取程序和系统中的特定路径,QDir::currentPath() 是获取工作路径(目录),QCoreApplication::applicationDirPath() 是获取可执行程序(就是程序自己)所在目录,这两个目 录是与程序相关的。
剩下三个是与操作系统和用户相关的路径,之前都提到过,这里顺便显示一下。
另外说一个静态函数 QCoreApplication::​applicationFilePath(),它返回当前应用程序自己的绝对路径,比如
D:/QtProjects/ch07/build-paths-Desktop_Qt_5_4_0_MinGW_32bit-Debug/debug/paths.exe

第四个按钮 "测试存在性" 对应的槽函数代码如下:
void Widget::on_pushButtonExist_clicked()
{
    //获取测试路径
    QString strTestPath = ui->lineEditTestPath->text();
    //判断字符串是否为空
    if(strTestPath.length() < 1)
    {
        return;
    }
    //不带参数的 dirWork 默认就是程序工作路径!
    QDir dirWork;
    //结果字符串
    QString strResult;
    //判断存在性,不带参数的 dirWork.exists() 只能判断文件夹
    //这里既需要判断文件夹,也要判断文件,因此把 strTestPath 作为参数传入来判断
    if( dirWork.exists( strTestPath ) )
    {
        strResult = tr("路径 %1 是存在的。").arg(strTestPath);
    }
    else
    {
        strResult = tr("路径 %1 不存在。").arg(strTestPath);
    }
    //显示
    ui->plainTextEditResult->setPlainText(strResult);
}
该槽函数代码首先获取要测试的路径,存到 strTestPath 并判断字符串是否为空,是空串就直接返回。
我们这个按钮功能是既要测试文件夹的存在性,也要测试文件的存在性,需要用到的是带参数的 exists(const QString & name) 函数,不带参数的 exists() 只能判断文件夹。
因此我们直接定义一个 dirWork,默认就以程序的工作目录为基准,然后调用 dirWork.exists( strTestPath ) 判断路径的存在性,无论是相对路径、绝对路径,文件夹路径、文件路径,都可以正常判断。最后把判断结果显示到结果框里面。

第五个按钮 "测试相对性" 对应的槽函数代码如下:
void Widget::on_pushButtonIsRelative_clicked()
{
    //获取测试路径
    QString strTestPath = ui->lineEditTestPath->text();
    //判断字符串是否为空
    if(strTestPath.length() < 1)
    {
        return;
    }
    //构造目录浏览对象
    QDir dirTest(strTestPath);
    //结果字符串
    QString strResult;
    //判断相对性
    if( dirTest.isRelative() )
    {
        //是相对路径
        strResult = tr("路径 %1 是相对路径").arg(strTestPath);
    }
    else
    {
        //是绝对路径
        strResult = tr("路径 %1 是绝对路径").arg(strTestPath);
    }
    //显示
    ui->plainTextEditResult->setPlainText(strResult);
}
这个槽函数开始也是获取测试路径 strTestPath,并判断是否为空串。
然后是以 strTestPath 为参数构造了对象 dirTest,然后调用该对象的 isRelative() 判断需要测试的路径是相对路径还是绝对路径。绝对路径和相对路径判断的功能函数不会检查路径的存在性和真实性,读者可以在程序运行时可以测试一下。

第六个按钮 "显示绝对路径" 对应的槽函数代码如下:
void Widget::on_pushButtonShowAbsolute_clicked()
{
    //获取测试路径
    QString strTestPath = ui->lineEditTestPath->text();
    //判断字符串是否为空
    if(strTestPath.length() < 1)
    {
        return;
    }
    //构建目录浏览对象
    QDir dirTest(strTestPath);
    //结果字符串
    QString strResult;
    //获取绝对路径
    strResult = tr("测试路径 %1 的绝对路径为:%2")
            .arg(strTestPath).arg( dirTest.absolutePath() );
    //显示
    ui->plainTextEditResult->setPlainText(strResult);
}
该槽函数开头获取要测试的路径 strTestPath ,并判断是否为空串。
然后以 strTestPath 为参数定义 dirTest 对象,并调用 dirTest.absolutePath() 获取绝对路径,显示到结果字符串。
QDir::absolutePath() 函数也是不检查存在性的,它可以用于把虚假的相对路径转成绝对路径,用于将来的创建文件夹等操作。

读者需要注意一个问题,QDir 类和 QFileInfo 类有些名字一样的函数,但是工作特性可能不同!比如,QDir::absolutePath() 函数不检查路径存在性,而 QFileInfo::absolutePath() 是检查路径存在性的。不带参数的 QDir::exists() 只能判断文件夹存在性,而不带参数的 QFileInfo::​exists() 既可以判断文件夹存在性,也可以判断文件存在性。
具体情况一定要注意查帮助文档!

讲完这个例子的代码,我们生成运行例子看看:
run
点击 "获取工作路径" ,可以看到程序当前的工作路径为:
D:/QtProjects/ch07/build-paths-Desktop_Qt_5_4_0_MinGW_32bit-Debug
这是影子构建目录,而不是 exe 文件所在的目录,点击 "显示环境路径",可以看到:
工作路径:D:/QtProjects/ch07/build-paths-Desktop_Qt_5_4_0_MinGW_32bit-Debug
可执行程序目录: D:/QtProjects/ch07/build-paths-Desktop_Qt_5_4_0_MinGW_32bit-Debug/debug

用户主文件夹:C:/Documents and Settings/Administrator
系统根目录:C:/
临时目录:C:/Documents and Settings/Administrator/Local Settings/Temp
读者的操作系统和用户不一样,可能结果稍有差异。这里可以看到工作路径与可执行程序目录有区别,可执行程序在子文件夹 debug 里面。
下面我们测试一下 paths.exe 文件的存在性:
run2
这时可以获取 paths.exe 的绝对路径,即使不存在也是能获取绝对路径的:
run3
接下来我们把程序的工作路径改了,改为
D:/QtProjects/ch07/build-paths-Desktop_Qt_5_4_0_MinGW_32bit-Debug/debug
点击 "设置工作路径",看到成功的信息:
run4
工作路径修改了之后,会影响后续相对路径的判断,以及转换后的绝对路径:
run5
现在这个 paths.exe 又存在了,就是因为作为基准的工作路径变了。现在计算的绝对路径也变了:
run6
除非确定自己想干什么,否则一般不要随意修改程序的工作路径,因为牵一发而动全身,后面的代码判断都可能有问题。这个路径检查和判断的例子讲到这里,下一小节的例 子更重要,我们学习一下浏览文件系统、枚举目录内容、获取文件信息。

7.1.5 文件系统浏览示例

在开始这个例子之前,简单说明一下 QListWidget 列表控件(详细内容第 8 章再学),QListWidget 是用于罗列条目的控件,一般可以是字符串列表,一个字符串占一行,或者自己构建多个 QListWidgetItem 条目,逐个添加到列表控件。自己构造 QListWidgetItem 可以为条目添加图标:
QListWidgetItem(const QIcon & icon, const QString & text, QListWidget * parent = 0, int type = Type)
icon 是显示的图标,text 是显示的文本,parent 是父控件指针,type 可以是自定义的条目类型,数值 1000 以下是 Qt 保留的类型,我们可以从数值 1000 开始定义自己的类型,比如定义 2000 为 文件夹,2001 为文件。
新建好一个 QListWidgetItem 条目之后就可以添加到 QListWidget 列表控件:
void QListWidget::​addItem(QListWidgetItem * item)
对于浏览文件系统的情况,每个条目就是一个文件夹或文件,当用户选中的条目变化时,发出如下信号:
void QListWidget::​currentItemChanged(QListWidgetItem * current, QListWidgetItem * previous)
current 是当前选中条目,previous 是之前的条目,这两个参数指针有可能为 NULL,使用时一定要判断。
条目被单击时发出信号:
void QListWidget::​itemClicked(QListWidgetItem * item)
条目被双击时发出信号:
void QListWidget::​itemDoubleClicked(QListWidgetItem * item)
清除所有条目使用槽函数:
void QListWidget::​clear()
本小节运用到的列表控件函数、信号和槽大致就这些。
我们这一小节的例子会用列表控件和 QDir 浏览本地文件系统,当用户选中某个条目,就显示该条目(文件夹或文件)详细信息,如果双击一个文件夹,就进入该文件夹路径进行枚举,如果双击一个文件条目,那就调用 QDesktopServices::​openUrl() 打开该文件。下面我们开始这个例子的学习。

重新打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 fsbrowser,创建路径 D:\QtProjects\ch07,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
建好项目之后,不着急编程序,我们从系统的资源管理器进入项目文件夹 D:\QtProjects\ch07\fsbrowser,新建一个子文件夹 images,这个子文件夹用来放图标文件。项目用到三个图标文件,下载地址为:
https://lug.ustc.edu.cn/sites/qtguide/QtProjects/ch07/fsbrowser/images/
下载 driver.png、file.png、folder.png 三个文件,放到本地项目文件夹 images 子文件夹。
然后回到 QtCreator 里面的项目边栏,右击 fsbrowser 项目名称,右键菜单选择 "添加新文件",如下图所示:
addnew
在弹出的新建文件对话框,左边选择 "Qt" ,然后在中间选择 "Qt Resource File",如下图所示:
addnew2
点击 "Choose..." 按钮,来到新建资源文件界面,名称里面输入 icons ,如下图所示:
addnew3
点击 "下一步",剩下的步骤用默认的即可,新建 icons.qrc 完成之后,我们从 QtCreator 项目边栏打开该文件,右击该文件名:
addicons
右键菜单选择 "添加现有文件 ..." ,然后在弹出的对话框找到项目子文件夹 images 里面的三个图标文件,全部添加,然后就可以看到下图结果:
addicons2
这样资源文件就编辑好了,保存并关闭资源文件。我们下面编辑界面文件 widget.ui,在 QtCreator 设计模式,按照下图拖入控件:
ui
界面总共四行控件,具体操作如下:
● 主界面窗口大小 400*400,方便容纳所有控件。
● 第一行:单行编辑控件对象名为 lineEditDir,这个单行编辑控件就是地址栏,按压按钮控件对象名为 pushButtonEnter,文本为 "进入",这一行按照水平布局器进 行排列。
● 第二行,水平空白条对象名默认 horizontalSpacer,第一个按钮对象名 pushButtonDrivers,文本为 "获取分区根",第二个按钮对象名为 pushButtonParent,文本为 "进入父目录",这一行也按照水平布局器排列。
● 第三行是一个列表控件,默认对象名为 listWidget,列表控件用于显示当前路径里的文件夹和文件。
● 第四行是一个普通文本编辑器,对象名为 plainTextEditInfo,这个编辑器用于显示文件夹或文件条目信息。
● 主界面布局,头两行是水平布局器,三四行是控件,点击空白位置,不选中任何控件,点击上面工具栏垂直布局按钮,这样得到的垂直布局器自动称为主布局器。

编辑好界面和布局之后,我们首先分别右击三个按钮,右键菜单选择 "转到槽...",为按钮的 clicked() 信号添加槽函数:
ui
然后我们右击第三行的列表控件,右击菜单也是选择 "转到槽..." ,在弹出对话框选择信号 ​itemDoubleClicked(QListWidgetItem * ),这个信号的槽函数用于支持双击打开文件夹或文件,为该信号添加槽函数:
ui
然后我们再如法炮制,还是为列表控件添加信号槽函数,第二次信号选择 currentItemChanged(QListWidgetItem * , QListWidgetItem * ),这样为列表控件添加好两个信号对象的槽函数,第二个信号槽函数用于实时获取文件夹或文件条目的详细信息。槽函数添加完成后,保存界面文件,回到 QtCreator 编辑模式,我们开始编写实际的代码。

现在打开头文件 widget.h,这个例子需要编辑该头文件的内容,代码如下:
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QIcon>        //图标
#include <QListWidget>  //列表控件以及条目
#include <QDir>         //目录浏览
#include <QFileInfo>    //文件信息

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

    //条目类型枚举
    enum ITypes{ IDriver = 1999, IFolder = 2000, IFile = 2001 };

private slots:
    void on_pushButtonEnter_clicked();

    void on_pushButtonDrivers_clicked();

    void on_pushButtonParent_clicked();

    void on_listWidget_itemDoubleClicked(QListWidgetItem *item);

    void on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);

private:
    Ui::Widget *ui;

    //图标
    QIcon m_iconDriver; //分区根
    QIcon m_iconFolder; //文件夹
    QIcon m_iconFile;   //文件

    //当前目录
    QDir m_dirCur;

    //根据指定存在的目录,列举条目到列表控件
    void ShowItems(const QDir& dir);
    //根据 fi 获取文件信息,返回字符串
    QString GetFileInfo(const QFileInfo& fi);
    //根据 fi 获取文件夹信息,返回字符串
    QString GetFolderInfo(const QFileInfo& fi);
};

#endif // WIDGET_H
头文件代码里,首先添加了四个头文件包含,因为头文件用到这些类,所以提前把 <QIcon>、<QListWidget>、<QDir>、<QFileInfo> 包含了。
在类声明代码里,析构函数后我们添加了一个枚举类型声明,ITypes 有三个常量,IDriver 代表以后枚举列表条目对应的分区根条目,IFolder 是文件夹条目,IFile 是普通文件条目。
接下来五个槽函数是我们刚才通过界面添加的,头三个是三个按钮的槽函数,后两个是列表控件的槽函数。
在私有部分,我们新增了三个图标对象 m_iconDriver、m_iconFolder、m_dirCur,分别用于表示分区根、文件夹、文件条目的图标,用 于列表控件显示。
然后定义了一个 m_dirCur 对象,用于保存当前浏览的目录路径。
最后定义了三个函数,ShowItems() 函数根据指定的目录浏览对象,枚举里面的内容条目,并显示到列表控件。GetFileInfo() 函数是根据指定的文件信息对象 fi,获取该文件的信息,返回文件信息字符串。GetFolderInfo() 函数用于获取文件夹的信息,并返回结果字符串。与 QFileInfo 类关联紧密的函数就是这两个信息获取函数,而其他大部分的函数代码是配合 QDir 类对象浏览文件系统的。

头文件内容就以上那么多,下面来编辑源代码文件 widget.cpp ,先看看头文件包含和构造函数:
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMessageBox>  //消息框
#include <QDesktopServices> //打开本地文件或 URL
#include <QDateTime>    //日期时间

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    //设置结果信息框只读
    ui->plainTextEditInfo->setReadOnly(true);
    //图标
    m_iconDriver = QIcon(":/images/driver.png");
    m_iconFolder = QIcon(":/images/folder.png");
    m_iconFile = QIcon(":/images/file.png");
    //罗列分区根
    on_pushButtonDrivers_clicked();
}

Widget::~Widget()
{
    delete ui;
}
之前在 widget.h 已经包含多个头文件了,这里再包含几个: <QMessageBox> 用于弹出消息框、<QDesktopServices> 打开本地文件链接、<QDateTime> 打印文件日期时间字符串等。
构造函数里,先把结果消息编辑框 ui->plainTextEditInfo 设置成只读的;
然后根据资源系统的三个图片,对应生成三个图标对象 m_iconDriver、m_iconFolder、m_iconFile。
最后一句是手动调用枚举分区根的槽函数,窗口初始化时在列表控件显示分区根。这个槽函数等会讲解。

在析构函数后面,我们手动添加 Widget::ShowItems() 函数代码,这个函数根据参数里的目录对象,枚举出所有子条目,包括文件夹和文件,然后逐个新建列表控件条目,并添加到列表控件里面:
//根据目录列出条目到列表控件
void Widget::ShowItems(const QDir &dir)
{
    if( ! dir.exists())
    {
        //如果不存在就直接返回
        return;
    }
    //获取所有条目,目录排在前面
    QFileInfoList li = dir.entryInfoList(QDir::NoFilter, QDir::DirsFirst);
    //计数
    int nCount = li.count();
    //清空列表控件
    ui->listWidget->clear();
    //逐个添加
    for(int i=0; i<nCount; i++)
    {
        //不含路径的名字
        QString name = li[i].fileName();
        //判断是文件夹还是文件
        if(li[i].isDir())
        {
            //文件夹条目
            QListWidgetItem *itemFolder =
                    new QListWidgetItem(m_iconFolder, name, NULL, IFolder);
            //添加
            ui->listWidget->addItem(itemFolder);
        }
        else
        {
            //文件条目
            QListWidgetItem *itemFile =
                    new QListWidgetItem(m_iconFile, name, NULL, IFile);
            //添加
            ui->listWidget->addItem(itemFile);
        }
    }
}
ShowItems() 函数开头判断目录是否真实存在,只有目录存在才继续后续操作。
对于实际目录,利用 dir.entryInfoList() 枚举所有的文件夹和文件,参数 QDir::NoFilter 是不过滤,全部显示,参数 QDir::DirsFirst 是指把文件夹排在前面,文件排在后面,返回的 QFileInfoList 条目列表存到 li 对象。
下面先清空图形界面的列表控件 ui->listWidget,然后利用 for 循环,逐个枚举 li 列表里的内容:
li[i] 就是序号为 i 的条目信息对象,判断该条目是文件夹还是文件,如果是文件夹,那么新建图形界面列表控件条目时,图标用 m_iconFolder,自定义类型用 IFolder,然后添加条目到列表控件;
如果是文件条目,那么新建图形界面列表控件条目时,用 m_iconFile 图标,自定义类型为 IFile,然后添加条目到列表对象。
这个函数在目录浏览时比较常用,所以放到前面。根据 QDir 对象列举文件夹和文件的代码主要在这个函数里,一定要仔细学习这个函数,读者以后自己编代码时很可能会用到。

接下来是各个槽函数的代码,首先看 "进入" 按钮的代码,当用户在窗口的单行编辑控件里输入了路径之后,点击该按钮,就会尝试进入指定的路径,"进入" 按钮槽 函数代码如下:
void Widget::on_pushButtonEnter_clicked()
{
    //新目录
    QString strNewDir = ui->lineEditDir->text();
    QDir dirNew(strNewDir);
    //判断新目录是否存在
    if( dirNew.exists() )
    {
        //存在目录,使用权威路径,避免 Windows 系统进入的 "/"  bug
        m_dirCur = QDir( dirNew.canonicalPath() );
        //更新列表控件
        ShowItems(m_dirCur);
        //使用绝对路径显示
        ui->lineEditDir->setText(m_dirCur.absolutePath());
        //结果信息
        ui->plainTextEditInfo->setPlainText(tr("进入成功"));
    }
    else
    {
        //不存在目录
        QMessageBox::warning(this, tr("目录不存在"), tr("目录 %1 不存在").arg(strNewDir));
    }
}
该槽函数先获取单行编辑控件的文本,根据文件构建目录对象 dirNew,判断该目录是否存在,如果不存在就弹窗提示错误信息,如果目录路径存在,那么:
使用 dirNew.canonicalPath() 获取权威路径,并构造新的目录浏览对象给 m_dirCur 。注意这里用到了权威路径,因为用户在单行编辑控件里面可以输入各种乱七八糟的路径,实际测试时发现 Windows 系统可以识别 "/" 路径,是该应用程序所在的分区根,但是不识别实际存在的 "/Projects" 等子文件夹,比较矛盾,所以这里用权威路径构建新对象赋给 m_dirCur ,这种处理方式比较稳妥。
设置新的 m_dirCur 之后,调用 ShowItems(m_dirCur) 更新列表控件,然后用 m_dirCur 对象的绝对路径填充更新单行编辑控件(地址栏),并在结果信息编辑框显示 "进入成功" 。
程序在更新界面的单行编辑控件即地址栏时,始终用绝对路径显示,这样可以清晰地看到当前路径在哪里。

接下来是 "获取分区根" 按钮的代码,窗口初始化时就调用了它,填充分区根到列表控件,代码如下:
void Widget::on_pushButtonDrivers_clicked()
{
    //获取磁盘根分区
    QFileInfoList fil = QDir::drives();
    //手动添加本程序资源根
    fil.append( QFileInfo(":/") );
    //分区根计数
    int nCount = fil.count();
    //清空列表控件
    ui->listWidget->clear();
    //逐个添加条目
    for(int i=0; i<nCount; i++)
    {
        //分区根路径,全部用绝对路径显示
        QString strPath = fil[i].absolutePath();
        //新建条目
        QListWidgetItem *item =
                new QListWidgetItem(m_iconDriver, strPath, NULL, IDriver);
        //添加到列表控件
        ui->listWidget->addItem(item);
    }
    //当前目录设为系统根
    m_dirCur = QDir::root();
    //单行编辑控件文件
    ui->lineEditDir->setText( m_dirCur.absolutePath() );
    //结果显示
    ui->plainTextEditInfo->setPlainText(tr("已获取分区根"));
}
该槽函数先用 QDir::drives() 函数获取分区根列表,存到 fil ,然后另外添加了程序自己内嵌的资源系统根 ":/" ,QDir 和 QFileInfo 不仅支持本地文件系统的浏览和信息获取,对程序内嵌的资源系统是一样访问的,所以这里手动把资源根附加到列表里,程序运行时就可以浏览自己资源系统。
得到新的分区根列表 fil 后,清空旧的列表控件内容,使用 for 循环逐个枚举 fil 里面的条目,获取每个条目的绝对路径存到 strPath ,然后新建列表控件条目 item ,图标使用 m_iconDriver,自定义类型是 IDriver,然后添加到图形界面的列表控件。
填充好列表控件之后,把 m_dirCur 设置为操作系统的根,并显示 m_dirCur 绝对路径到地址栏,显示结果信息 "已获取分区根" 到结果信息编辑框。
获取分区根之后,列表控件最后一个条目就是程序自身资源系统的根路径,等程序运行时可以测试一下。

第三个按钮是 "进入父目录" ,点击这个按钮之后,会根据 m_dirCur 当前目录路径,尝试进入它的父目录,槽函数代码如下:
void Widget::on_pushButtonParent_clicked()
{
    if( m_dirCur.cdUp() )
    {
        //单行编辑控件更新,显示绝对路径
        ui->lineEditDir->setText( m_dirCur.absolutePath() );
        //列表更新
        ShowItems(m_dirCur);
        ui->plainTextEditInfo->setPlainText(tr("进入父目录成功"));
    }
    else
    {
        ui->plainTextEditInfo->setPlainText(tr("已到根目录"));
    }
}
槽函数里 m_dirCur.cdUp() 就是尝试切换到父目录,如果切换成功,就更新显示地址栏,调用 ShowItems(m_dirCur) 更新列表控件,并显示结果信息到 ui->plainTextEditInfo 。如果切换父目录失败,那么只显示信息 "已到根目录" 到结果信息编辑框,不需要其他操作。

三个按钮的槽函数代码讲完了,剩下是列表控件内部操作代码,主要是双击条目时,判断如果是文件夹就进入该文件夹,如果是本地文件,就调用系统浏览器打开该文件。列 表控件内第二个操作是如果当前选中的条目变化,就实时显示该条目信息到下面的结果信息编辑框里。
先来看看双击条目操作对应的槽函数代码:
void Widget::on_listWidget_itemDoubleClicked(QListWidgetItem *item)
{
    //判断 item 的类型
    int theType = item->type();

    if( IDriver == theType )    //如果是分区根
    {
        //根路径
        QString strFullPath = item->text();
        //设置当前目录
        m_dirCur = QDir( strFullPath );
        //更新单行编辑控件,显示绝对路径
        ui->lineEditDir->setText( m_dirCur.absolutePath() );
        //更新列表控件
        ShowItems(m_dirCur);
    }
    else if( IFolder == theType )   //如果是文件夹
    {
        //先对 .  .. 做排除
        QString strName = item->text();
        if( tr(".") == strName)
        {
            //不需要处理,还是当前目录
            return;
        }
        else if ( tr("..") == strName )
        {
            //切换父目录
            on_pushButtonParent_clicked();
            //返回
            return;
        }
        //正常的子文件夹处理
        //完整路径
        QString strFullPath = m_dirCur.absolutePath() + tr("/") + strName;
        strFullPath = QDir::cleanPath(strFullPath); //清理多余的斜杠
        //切换路径
        m_dirCur = QDir(strFullPath);
        //更新单行编辑控件,显示绝对路径
        ui->lineEditDir->setText(m_dirCur.absolutePath());
        //更新列表控件
        ShowItems(m_dirCur);
    }
    else    //普通文件
    {
        //拼接出路径字符串
        QString strFilePath = m_dirCur.absolutePath() + tr("/")  + item->text();
        strFilePath = QDir::cleanPath(strFilePath); //清理多余的斜杠
        //如果不是内嵌资源文件,是本地文件系统的
        if( ! strFilePath.startsWith( tr(":") ))
        {
            //调用系统浏览器打开
            QDesktopServices::openUrl( QUrl::fromLocalFile(strFilePath) );
        }
    }
}
槽函数开始时候先获取被双击条目的类型,这个类型是我们在创建列表控件条目时指定的三种,即分区根、文件夹和文件:
① 如果是分区根 IDriver 类型条目:获取根的完整路径 strFullPath ,作为参数构造 QDir 对象赋给 m_dirCur,更新地址栏,并列出该分区根的条目到列表控件。
② 如果是文件夹 IFolder 类型条目,判断文件夹名 strName ,文件夹 "." 代表自己,双击不需要切换,文件夹 ".." 代表父目录,调用进入父目录按钮的槽函数 on_pushButtonParent_clicked() ,然后对于其他普通的文件夹,我们将当前目录绝对路径 m_dirCur.absolutePath() 与 "/" 、strName 拼接起来,得到该子文件夹的完整路径,并对路径进行规约,清除可能冗余的斜杠,构造新的目录对象 QDir(strFullPath),赋给 m_dirCur,这样切换当前目录到该子文件夹里面,然后更新地址栏和列表控件的条目,正式进入新文件夹的浏览。
③ 剩下的就是普通文件类型,一样拼接出该文件的完整路径 strFilePath ,并对路径做规约处理 ,清理多余的斜杠,如果完整文件名不是资源系统 ":" 打头的文件,那就调用系统浏览器打开该本地文件 QDesktopServices::openUrl() 。
这个槽函数就模拟系统里面的资源管理器,双击条目是打开文件夹或打开文件。

接下来是获取当前列表控件条目的信息的函数,这些信息自动显示窗口最下面的结果信息编辑框里面,当列表控件当前选中条目变化时,下面槽函数被执行:
void Widget::on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
{
    //如果当前条目为空则返回
    if( NULL == current )
    {
        return;
    }
    //条目信息对象
    QFileInfo fi;
    //记录信息字符串
    QString strResult;
    //条目类型
    int theType = current->type();
    //条目名称
    QString strName = current->text();
    //判断类型
    if( IDriver == theType )    //分区根
    {
        //分区根路径
        fi = QFileInfo( strName );
        if( strName.startsWith( tr(":") ) )
        {
            strResult += tr("资源根 %1").arg(strName); //显示绝对路径
        }
        else
        {
            strResult += tr("分区根 %1").arg(strName); //显示绝对路径
        }
    }
    else if( IFolder == theType )   //文件夹
    {
        //完整路径
        QString strFullPath = m_dirCur.absolutePath() + tr("/") + strName;
        strFullPath = QDir::cleanPath(strFullPath); //清理多余的斜杠
        strResult += tr("文件夹 %1\r\n").arg(strFullPath); //显示绝对路径
        //获取信息,添加到结果串
        fi = QFileInfo(strFullPath);        
        strResult += GetFolderInfo(fi);
    }
    else    //文件
    {
        //完整文件名
        QString strFilePath = m_dirCur.absolutePath() + tr("/") + strName;
        strFilePath = QDir::cleanPath(strFilePath); //清理多余的斜杠
        strResult += tr("文件 %1\r\n").arg(strFilePath); //显示绝对路径
        //获取文件信息,添加到结果串
        fi = QFileInfo(strFilePath);
        strResult += GetFileInfo(fi);
    }
    //显示结果信息
    ui->plainTextEditInfo->setPlainText(strResult);
}
注意这个槽函数开始一定要判断用到的指针是否为空值,因为列表控件的 clear() 函数也会触发 currentItemChanged() ,这时候参数的指针是 NULL,所以必须先判断再使用。
槽函数定义了条目信息对象  fi 和结果信息字符串 strResult,获取条目名称存到 strName 。
接下来根据条目类型 theType 进行判断:
① 如果是分区根类型 IDriver ,简单判断路径是否以 ":" 打头,以冒号打头的是资源根,不以冒号打头的是普通文件系统的根,构造对应的结果信息串 strResult 。
② 如果是文件夹类型 IFolder ,拼接出子文件夹的 strFullPath ,并做规约处理,把文件夹完整路径添加到结果串,然后构造 fi 对象,使用函数 GetFolderInfo() 获取文件夹的信息字符串,添加到结果串。
③ 剩下的是文件类型,也是拼接出完整路径 strFilePath ,并对路径做规约处理,添加文件完整路径到结果信息串,然后构造 fi 对象,调用函数 GetFileInfo()  获取文件的信息字符串,添加到结果串。
三种不同类型的条目会得到不同的结果信息串,最后是显示到窗口下面的结果信息编辑框里。

对于文件夹和文件信息的获取,使用了不同函数: GetFolderInfo() 和 GetFileInfo() ,这两个函数专门和 QFileInfo 对象打交道,文件夹和文件的详细信息一般有些差异,因此各自用自己的函数来获取信息字符串。
获取文件夹信息的函数如下:
//获取文件夹信息字符串
QString Widget::GetFolderInfo(const QFileInfo &fi)
{
    QString strResult;  //用于返回的串
    //判断文件夹信息
    if(fi.isReadable()) //是否可读
    {
        strResult += tr("可读:是\r\n");
    }
    else
    {
        strResult += tr("可读:否\r\n");
    }
    if(fi.isWritable()) //是否可写
    {
        strResult += tr("可写:是\r\n");
    }
    else
    {
        strResult += tr("可写:否\r\n");
    }
    //时间
    QDateTime dtCreate = fi.created();
    QDateTime dtModify = fi.lastModified();
    strResult += tr("创建时间:%1\r\n").arg(dtCreate.toString("yyyy-MM-dd HH:mm:ss"));
    strResult += tr("修改时间:%1\r\n").arg(dtModify.toString("yyyy-MM-dd HH:mm:ss"));

    //返回
    return strResult;
}
文件夹信息相对简单一些,判断一下可读、可写的属性,获取创建时间、修改时间,把信息都添加到返回的结果串 strResult 即可。QDateTime 对象的 toString() 函数是把日期时间转成字符串形式,里面的参数是日期时间格式:yyyy 代表四位数年份,大写 MM 是月份,dd 是日子,大写 HH 是 24 小时格式钟点,小写 mm 是分钟,ss 是秒钟。

最后的是获取文件详细信息的函数:
//返回文件信息字符串
QString Widget::GetFileInfo(const QFileInfo &fi)
{
    QString strResult;  //用于返回的串
    //判断文件信息
    if(fi.isReadable())
    {
        strResult += tr("可读:是\r\n");
    }
    else
    {
        strResult += tr("可读:否\r\n");
    }
    if(fi.isWritable())
    {
        strResult += tr("可写:是\r\n");
    }
    else
    {
        strResult += tr("可写:否\r\n");
    }
    if(fi.isExecutable())
    {
        strResult += tr("可执行:是\r\n");
    }
    else
    {
        strResult += tr("可执行:否\r\n");
    }
    //类型
    strResult += tr("类型:%1\r\n").arg(fi.suffix());
    //大小
    strResult += tr("大小:%1 B\r\n").arg(fi.size());
    //时间
    QDateTime dtCreate = fi.created();
    QDateTime dtModify = fi.lastModified();
    strResult += tr("创建时间:%1\r\n").arg(dtCreate.toString("yyyy-MM-dd HH:mm:ss"));
    strResult += tr("修改时间:%1\r\n").arg(dtModify.toString("yyyy-MM-dd HH:mm:ss"));

    //返回
    return strResult;
}
对于文件的信息,这里判断了可读、可写、可执行三个权限属性,然后获取文件扩展名作为文件类型信息,获取文件的大小信息,最后是文件的创建时间和修改时间,都添加 到返回的结果串 strResult 里面。
GetFolderInfo() 和 GetFileInfo() 没有对文件夹或文件做存在性判断,因为所有文件夹和文件都是从分区根开始浏览的,本来就都是存在的,所以没必要判断。如果不是从文件系统浏览获取的文件夹和文件,最好对 QFileInfo 对象做存在性判断。

例子代码就是这些,我们生成运行例子,我们进入 C:/Qt/Qt5.4.0 文件夹,可以看到:
run
文件夹排在前面,列表后面才是文件。我们也可以点击 "获取根分区" 按钮, 然后双击进入资源根 ":/" ,进入资源系统的文件夹 ":/images" 进行浏览:
rc
例子程序对自己内嵌的资源系统浏览也是一样的,区别是所有内嵌资源文件为只读的,而且没有时间信息 ,因为毕竟不是本地文件系统,只是可执行程序内部的资源节。

本节关于文件系统的内容介绍到这,现在只是浏览文件系统,并没有读写实际的文件,我们从下一节开始真正的读写文件操作,从 QFile 类开始。



prev
contents
next