CC++遍历⽬录下的所有⽂件(Windows篇,超详细)
注:
1. 本⽂讨论的是怎么⽤Windows API遍历⽬录下的所有⽂件。除Windows API,还有⼀种Windows/Linux通⽤的⽅式,使⽤<io.h>。
2. 本⽂部分翻译⾃MSDN,翻译可能不准确。
WIN32_FIND_DATA结构
遍历⽬录下的⽂件需要⽤到WIN32_FIND_DATA结构。实际上有两种结构:WIN32_FIND_DATAA和WIN32_FIND_DATAW。A和W分别代表ASCII和宽字符(Unicode)。定义UNICODE宏时,WIN32_FIND_DATA指WIN32_FIND_DATAW;否则指WIN32_FIND_DATAA。
下⾯是两个结构的定义(minwinbase.h,VS2015):
typedef struct _WIN32_FIND_DATAA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
_Field_z_ CHAR  cFileName[ MAX_PATH ];
_Field_z_ CHAR  cAlternateFileName[ 14 ];
#ifdef _MAC
DWORD dwFileType;
DWORD dwCreatorType;
WORD  wFinderFlags;
#endif
} WIN32_FIND_DATAA;
typedef struct _WIN32_FIND_DATAW {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
大学学生会自我介绍
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
_Field_z_ WCHAR  cFileName[ MAX_PATH ];
_Field_z_ WCHAR  cAlternateFileName[ 14 ];
江南百景图 苏州府怎么去#ifdef _MAC
DWORD dwFileType;
DWORD dwCreatorType;
WORD  wFinderFlags;
#endif
} WIN32_FIND_DATAW;
关于_MAC宏的部分可以忽略,这是有历史原因的——曾今Microsoft是Mac的最⼤开发者,为了⽅便
Windows上的应⽤移植到Mac上,就使⽤_MAC宏,如果是Mac操作系统_MAC就是有定义的。(根据)因为这⾥说的是Windows,就先把这个放⼀边。
下⾯是每个结构成员的含义:
dwFileAttributes
⼀个⽂件(或路径)的⽂件属性
⽂件属性常量:
FILE_ATTRIBUTE_ARCHIVE(0x20):⽂件或⽬录是档案⽂件或⽬录。应⽤程序使⽤这种属性标记⽂件,表⽰备份或移除。
FILE_ATTRIBUTE_COMPRESSED(0x800):⽂件或⽬录是压缩的。对于⼀个⽂件,其中的所有数据都是压缩的。对于⼀个⽬录,对于新创建的⽂件和⼦⽬录默认压缩。FILE_ATTRIBUTE_DEVICE(0x40):这个值保留给系统使⽤。
FILE_ATTRIBUTE_DIRECTORY(0x10):表⽰这是⼀个⽬录。
FILE_ATTRIBUTE_ENCRYPTED(0x10):⽂件或⽬录是加密的。对于⼀个⽂件,所有的数据流都被加密了。对于⼀个⽬录,对于新创建的⽂件和⼦⽬录默认加密。
FILE_ATTRIBUTE_HIDDEN(0x2):⽂件或⽬录是隐藏的。遍历⽂件夹时⼀般不包括它们。
FILE_ATTRIBUTE_INTEGRITY_STREAM(0x8000):路径或⽤户数据流被设置为integrity(只有ReFS volume⽀持)。遍历⽂件夹时⼀般不包括它们。Integrity设置在⽂件重命名之后依然保留。如果⼀个⽂件被复制,⽬标⽂件将会是integrity,不管源⽂件或⽬标路径是否是integrity。
FILE_ATTRIBUTE_NORMAL(0x80):⽂件没有任何其它属性。只能单独使⽤。
FILE_ATTRIBUTE_NOT_CONTEXT_INDEXED(0x2000):⽂件或⽬录不会被context indexing service标索引。
FILE_ATTRIBUTE_READONLY(0x1):⽂件为只读。程序可以读取该⽂件,但不能写⼊或删除。此属性不适⽤于⽬录。
……(太多了,有时间再全部列举)
顺带提⼀下位标记(bit flags)。
如你所见,所有的⽂件属性常量写成⼆进制都只有⼀位是1,剩下的都是0。对于dwFileAttributes,将其写成⼆进制的形式,它的⼀些位的值是有含义的——例如个位表⽰是否是
只读的(FILE_ATTRIBUTE_READONLY是1),16位表⽰是否是⽬录(FILE_ATTRIBUTE_DIRECTORY是0x10,即16),32位表⽰是否是档案⽂件/⽬录
(FILE_ATTRIBUTE_ARCHIVE是0x20,即32)……
那么怎么指定多个属性呢?因为每种属性写成⼆进制都只有⼀位是1,剩下的都是0,所以可以使⽤按位or运算符(|)指定多个属性,例如FILE_ATTRIBUTE_HIDDEN |
FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE,这样每种属性对应的位都为1,其余的为0。
柔顺⾄于判断是否具有某个属性,可以⽤按位and运算符(&)。例如对于属性attrib,判断是否是⽬录:attrib & FILE_ATTRIBUTE_DIRECTORY。如果不为0则是⽬录,为0则不
是。
ftCreationTime
FILETIME结构。指定⼀个⽂件或⽬录的创建时间。如果⽂件系统不⽀持创建时间,此成员为0。
ftLastAccessTime
FILETIME结构。对于⽂件,指定⽂件最后被读取、写⼊,或(对于可执⾏⽂件)被运⾏的时间。对于⽬录,指定⽬录的创建时间。如果⽂件系统不⽀持最后⼀次写⼊时间,此成
员为0。
ftLastWriteTime
FILETIME结构。对于⽂件,指定⽂件最后被写⼊、截短或重写的时间(例如调⽤WriteFile()或SetEndOfFile()时)。⽇期和时间在⽂件属性或描述符被改变时不会被更新。
nFileSizeHigh
DWORD。⽂件⼤⼩(以字节为单位)的⾼DWORD。除⾮⽂件⼤⼩⼤于MAXDWORD,否则值为0。⽂件⼤⼩等于(nFileSizeHigh * (MAXDWORD + 1)) + nFileSizeLow。
nFileSizeLow
DWORD。⽂件⼤⼩(以字节为单位)的低DWORD。
dwReserved0
DWORD。如果dwFileAttributes成员含有FILE_ATTRIBUTE_REPARSE_POINT属性,这个成员指定重新分析点标签(reparse point tag)。否则这个值是未定义的。
可能的值:
IO_REPARSE_TAG_CSV,IO_REPARSE_TAG_DEDUP,IO_REPARSE_TAG_DFS,IO_REPARSE_TAG_DFSR,IO_REPARSE_TAG_HSM,IO_REPARSE_TAG_HSM2,IO_REPARSE_TAG dwReserved1
DWORD。保留给将来使⽤。
cFileName
CHAR/WCHAR数组,⼤⼩为MAX_PATH。⽂件名。
cAlternateFileName
CHAR/WCHAR数组,⼤⼩为14。⽂件的别名。名称的格式为8.3⽂件名格式。
FILETIME结构
可以看到WIN32_FIND_DATA的ftCreationTime、ftLastAccessTime、ftLastWriteTime类型是FILETIME结构。那么FILETIME结构是怎样的呢?下⾯是MSDN上的定义:
typedef struct _FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME;
dwLowDateTime
⽂件时间的低DWORD。
dwHighDateTime
⽂件时间的⾼DWORD。
FILETIME结构表⽰的时间(距离Epoch的秒数)为dwHighDateTime * (MAXDWORD + 1) + dwLowDateTime。
FindFirstFile()/FindNextFile()/FindClose()函数
要查⽂件,需要使⽤FindFirstFile()、FindNextFile()和FindClose()函数。
FindFirstFile()函数
HANDLE WINAPI FindFirstFile(
_In_ LPCTSTR        lpFileName,
_Out_ LPWIN32_FIND_DATA    lpFindFileData
);
搜索第⼀个⽂件,创建并返回搜索句柄。
lpFileName
CHAR/WCHAR指针(取决于是否定义UNICODE)。路径或⽂件名。可以包含通配符,例如*或?。不能以\\字符结尾。如果以通配符、.字符或⽬录名结尾,⽤户必须有根⽬录和所有⼦⽬录的访问权限。(遍历⽬录中的所有⽂件时,应以*.*结尾。)
lpFindFileData
WIN32_FIND_DATA指针。⽤于接收到的⽂件/⽬录的信息。
返回值
如果成功,函数将创建⼀个搜索句柄,可以使⽤该句柄调⽤FindNextFile()和FindClose()。如果失败,返回INVALID_HANDLE_VALUE。
FindNextFile()函数
BOOL WINAPI FindNextFile(
_In_ HANDLE        hFindFile,
_Out_ LPWIN32_FIND_DATA    lpFindFileData
)
;
搜索下⼀个⽂件。
hFindFile
HANDLE。搜索句柄。
lpFindFileData
WIN32_FIND_DATA指针。⽤于接收到的⽂件/⽬录的信息。
返回值
如果成功,返回TRUE;如果失败(例如不到更多的⽂件了或其它原因),返回FALSE。
FindClose()函数
BOOL WINAPI FindClose(
_Inout_ HANDLE hFindFile
)
;
释放搜索句柄。
hFindFile
HANDLE。搜索句柄。
返回值
如果成功,返回TRUE;如果失败,返回FALSE。
通配符(wildcards)
*和?字符被⽤作通配符。
指定全部具有某个扩展名的⽂件
格式为*.ext(ext为扩展名)。例如指定所有.txt⽂件:"*.txt"。指定D:\Projects\⽬录下所有.txt⽂件:"D:\\Projects\\*.txt"。
指定全部具有某个名称的⽂件/⽬录
格式为name.*(name为⽂件名)。例如指定所有名为readme(格式不限)的⽂件和⽬录:"readme.*"。指定D:\Projects\⽬录下的所有名为readme的⽂
件:"D:\\Projects\\readme.*"。
指定具有⼀定长度的扩展名的⽂件
格式为name.???(name为⽂件名)。?的数量和扩展名长度⼀样。例如指定所有扩展名为4个字符,名为index的⽂件:"index.????"。
指定具有⼀定长度的⽂件名的⽂件
格式为???.ext(ext为扩展名)。?的数量和⽂件名的长度⼀样。例如指定所有扩展名为.txt,名字含有7个字符的⽂件:"???????.txt"。
当然还有更复杂的,例如*.???、????.*、*.*、????.???,分别是“所有扩展名长度为3的⽂件”、“所有⽂件名长度为4的⽂件/⽬录”、“所有⽂件/⽬录”、“所有⽂件名长度为4且扩展名长度为3的⽂件”。
当前⽬录和上⼀级⽬录
调⽤FindFirstFile()时,使⽤"."表⽰当前⽬录,使⽤".."表⽰上⼀级⽬录。FindFirstFile()和FindNextFile()所返回的⽂件/⽬录名也可能是"."或"..",可以忽略。
最后的⼯作
我们还需要声明⼀个HANDLE才能开始搜索:
HANDLE hFind;
程序⽰例
废话了这么久,也是该上程序代码了。
1. 遍历某个⽬录下的所有⽂件
遍历某个⽬录下的所有⽂件,并输出⽂件名和⽂件⼤⼩。
#include <iostream>
#include <cstring>
#include <windows.h>
void listFiles(const char * dir);
int main()
{
using namespace std;
char dir[100];
cout << "Enter a directory (ends with \'\\\'): ";
strcat(dir, "*.*");    // 需要在⽬录后⾯加上*.*表⽰所有⽂件/⽬录
listFiles(dir);
return0;
}
void listFiles(const char * dir)
{
using namespace std;
HANDLE hFind;
WIN32_FIND_DATA findData;
LARGE_INTEGER size;
hFind = FindFirstFile(dir, &findData);
if (hFind == INVALID_HANDLE_VALUE)
{
cout << "Failed to find first file!\n";
return;
}
do
{
// 忽略"."和".."两个结果
诺一夏天if (strcmp(findData.cFileName, ".") == 0 || strcmp(findData.cFileName, "..") == 0)
continue;
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)    // 是否是⽬录
{
cout << findData.cFileName << "\t<dir>\n";
}
我的健康码else
{
size.LowPart = findData.nFileSizeLow;
size.HighPart = findData.nFileSizeHigh;
cout << findData.cFileName << "\t" << size.QuadPart << " bytes\n";
}
} while (FindNextFile(hFind, &findData));
cout << "Done!\n";
}
以学校电脑为例,输⼊"C:\",输出如下:
$360Section <dir>
360SANDBOX <dir>
AUTOEXEC.BAT 0 bytes
boot.ini 210 bytes
bootfont.bin 322730 bytes
CONFIG.SYS 0 bytes
dell <dir>
Documents and Settings <dir>
Drivers <dir>
ExamClient <dir>
FPC <dir>
IO.SYS 0 bytes
Joinmax <dir>
ksd <dir>
MSDOS.SYS 0 bytes
MSOCache <dir>
...
(剩余输出省略)
2. 遍历某个⽬录⾥的所有⽂件
注意是“某个⽬录⾥”⽽不是“某个⽬录下”,两者是有区别的。“某个⽬录⾥”除了⽬录⾥的第⼀级的⽂件,还包括⾥⾯的⼦⽬录⾥的所有⽂件。和上⾯的例⼦⼀样,使⽤listFiles()函数遍历⼀个⽬录⾥的所有⽂件。但不同的是,这⾥的listFiles()是递归调⽤的。缘之空一共做了几次
#include <iostream>
#include <cstring>
#include <windows.h>
void listFiles(const char * dir);
int main()
{
using namespace std;
char dir[100];
cout << "Enter a directory (do not add \'\\\' in the end): ";
listFiles(dir);
return0;
}
void listFiles(const char * dir)
{
using namespace std;
HANDLE hFind;
WIN32_FIND_DATA findData;
LARGE_INTEGER size;
char dirNew[100];
// 向⽬录加通配符,⽤于搜索第⼀个⽂件