C/C++遍歷目錄下的所有文件(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就是有定義的。(根據Stack Overflow)因為這里說的是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_MOUNT_POINT,IO_REPARSE_TAG_NFS,IO_REPARSE_TAG_SIS,IO_REPARSE_TAG_SIMLINK,IO_REPARSE_TAG_WIM。

 

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)

 

*和?字符被用作通配符。

 

指定全部具有某個擴展名的文件

 

格式為*.extext為擴展名)。例如指定所有.txt文件:"*.txt"。指定D:\Projects\目錄下所有.txt文件:"D:\\Projects\\*.txt"。

 

指定全部具有某個名稱的文件/目錄

 

格式為name.*(name為文件名)。例如指定所有名為readme(格式不限)的文件和目錄:"readme.*"。指定D:\Projects\目錄下的所有名為readme的文件:"D:\\Projects\\readme.*"。

 

指定具有一定長度的擴展名的文件

 

格式為name.???(name為文件名)。?的數量和擴展名長度一樣。例如指定所有擴展名為4個字符,名為index的文件:"index.????"。

 

指定具有一定長度的文件名的文件

 

格式為???.extext為擴展名)。?的數量和文件名的長度一樣。例如指定所有擴展名為.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 \'\\\'): ";
    cin.getline(dir, 100);
    strcat(dir, "*.*");    // 需要在目錄后面加上*.*表示所有文件/目錄
    listFiles(dir);
    return 0;
}

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): ";
    cin.getline(dir, 100);
    
    listFiles(dir);
    return 0;
}

void listFiles(const char * dir)
{
    using namespace std;
    
    HANDLE hFind;
    WIN32_FIND_DATA findData;
    LARGE_INTEGER size;
    char dirNew[100];
    
    // 向目錄加通配符,用於搜索第一個文件 
    strcpy(dirNew, dir);
    strcat(dirNew, "\\*.*");
    
    hFind = FindFirstFile(dirNew, &findData);    
    do
    {
        // 是否是文件夾,並且名稱不為"."或".." 
        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0
        && strcmp(findData.cFileName, ".") != 0
        && strcmp(findData.cFileName, "..") != 0
        )
        {
            // 將dirNew設置為搜索到的目錄,並進行下一輪搜索 
            strcpy(dirNew, dir);
            strcat(dirNew, "\\");
            strcat(dirNew, findData.cFileName);
            listFiles(dirNew);
        }
        else
        {
            size.LowPart = findData.nFileSizeLow;
            size.HighPart = findData.nFileSizeHigh;
            cout << findData.cFileName << "\t" << size.QuadPart << " bytes\n";
        }
    } while (FindNextFile(hFind, &findData));
    
    FindClose(hFind);
}

 

呼,總算講完了~如果有什么問題,請在評論中提出哦~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM