1. 程式人生 > >C/C++遍歷目錄下的檔案或指定檔案

C/C++遍歷目錄下的檔案或指定檔案

每次遇到這樣的問題總會折騰很久,到網上搜,或者查資料,弄了很多次,但就是沒記住,這次寫程式又遇到了,乾脆就把它都弄清楚了,然後順便在這裡記錄一下,以後再遇到就不用到處去找了。

        用 C/C++ 遍歷目錄檔案主要有兩種方式,分別對應在 Windows VS 環境下和 Linux\Unix 環境下的方法,它們各自所使用的函式如下:

  1. (Windows VS)_findfirst, _findnext, _findclose
  2. (Linux\Unix)opendir, readdir, closedir

下面就來詳細地說說這兩種方式

第一(_findfirst, _findnext, _findclose)

  基本流程:_findfirst-->_findnext-->_findclose

(1)First Step

         _findfirst 的函式原型:

  1. long _findfirst( char *filespec, struct_finddata_t *fileinfo );  

        它返回一個檔案控制代碼,可以作為其他函式的引數,並將檔名匹配由 filespec 指定的模式的第一個檔案的資訊儲存在 fileinfo 裡。例如我要找某個目錄下的 txt 檔案,那麼 fileinfo 就儲存了這個目錄下第一個 txt 檔案的資訊,這種情況下我們可以這樣來呼叫這個函式:

  1. long pFile = long _findfirst( "*.txt", fileinfo );  

而用來儲存檔案資訊的 fileinfo 是一個數據型別為 _finddata_t 的結構體,在標頭檔案IO.H定義:

  1. struct_finddata_t {  
  2.         unsigned    attrib;              /*檔案屬性*/
  3.         time_t      time_create;         /*檔案建立時間, -1 for FAT file systems */
  4.         time_t      time_access;         
    /*檔案最後一次訪問的時間 -1 for FAT file systems */
  5.         time_t      time_write;          /*檔案最後一次寫的時間*/
  6.         _fsize_t    size;                /*檔案大小*/
  7.         char        name[_MAX_FNAME];    /*匹配的檔名,不包含目錄,_MAX_FNAME在STDLIB.H中定義為256位元組*/
  8. };  

其中檔案屬性可以是以下的這些值(MSDN上的說明):

_A_ARCH(存檔)

Archive. Set whenever the file is changed, and cleared by the BACKUP command. Value: 0x20

_A_HIDDEN(隱藏檔案)

Hidden file. Not normally seen with the DIR command, unless the /AH option is used. Returns information about normal files as well as files with this attribute. Value: 0x02

_A_NORMAL(普通檔案,沒有讀寫限制)

Normal. File can be read or written to without restriction. Value: 0x00

_A_RDONLY(只讀檔案)

Read-only. File cannot be opened for writing, and a file with the same name cannot be created. Value: 0x01

_A_SUBDIR(子目錄)

Subdirectory. Value: 0x10

_A_SYSTEM(系統檔案)

System file. Not normally seen with the DIR command, unless the /A or /A:S option is used. Value: 0x04

(2)Second Step

        我們已經讀取了第一個檔案的資訊了,那怎麼讀取下一個呢,這時候就輪到 _findnext 出馬了,同樣我們先看看其函式原型:

  1. int _findnext( long handle, struct_finddata_t *fileinfo );  

        這個函式呼叫如果成功就返回0,否則返回-1。它將下一個檔名匹配 filespec 的檔案的資訊儲存在 fileinfo 裡面,handle 是呼叫 _findfirst 時返回的控制代碼。比如說我們這裡的例子,要得到下一個txt檔案的資訊,我們可以這樣呼叫函式

  1. _findnext(pFile, fileinfo);  

所以我們不斷呼叫 _findnext 直到它返回-1就可以遍歷所有的txt檔案了。

(3)Step Three

        函式 _findclose 就是做一些收尾工作,關閉檔案控制代碼:

  1. int _findclose( long handle );  

        同樣地,handle 也是呼叫 _findfirst 時返回的控制代碼。

(4)Final

        綜上所述,遍歷某個目錄下指定的檔案(所有檔案則用*表示)可以這樣寫:

  1. #include <io.h>
  2. int main()  
  3. {  
  4.     struct_finddata_t fileinfo;  
  5.     long hFile;  
  6.     if((hFile = _findfirst("*.txt",&fileinfo)) == -1)  
  7.         return -1;  
  8.     else {  
  9.         do {  
  10.             /*Process File*/
  11.         }while (_findnext(hFile,&file)==0);  
  12.     }  
  13.     _findclose(hFile);  
  14.     return 0;  
  15. }  

第二(opendir, readdir, closedir)

  基本流程:opendir-->readdir-->closedir

(1)Step One

       使用這些函式我們需要包含標頭檔案 <sys/types.h> 和 <dirent.h>。

        我們要讀取目錄下的檔案資訊,首先要開啟目錄,也就是要呼叫函式 opendir:

  1. DIR *opendir(constchar *pathname);  

        這個函式以目錄的路徑為引數,如果開啟成功,就返回一個DIR型別的指標,相當於上面所說的控制代碼,用於後續函式呼叫;否則返回 NULL。所以如果要開啟當前目錄可以這樣呼叫函式:

  1. DIR *pDir = opendir(".");  
        這樣我們就打開了目錄,返回值 pDir 用於接下來函式呼叫的引數。

(2)Step Two

        接下來就是讀取檔案的資訊了 ,在 <dirent.h> 中定義了一個結構體 dirent ,用來儲存檔案資訊,定義如下:

  1. struct dirent {  
  2.     ino_t          d_ino;       /* i-inode number */
  3.     off_t          d_off;       /* offset to the next dirent */
  4.     unsigned short d_reclen;    /* length of this record */
  5.     unsigned char  d_type;      /* type of file */
  6.     char           d_name[256]; /* null-terminated filename */
  7. };  

        這裡面其實經常要用到的就是 d_type 和 d_name 這兩個欄位,分別表示檔案型別和檔名。

        d_type 表示檔案型別,其取值定義如下:

  1. enum
  2. {  
  3.     DT_UNKNOWN = 0,  
  4. # define DT_UNKNOWN DT_UNKNOWN
  5.     DT_FIFO = 1,  
  6. # define DT_FIFO DT_FIFO
  7.     DT_CHR = 2,  
  8. # define DT_CHR DT_CHR
  9.     DT_DIR = 4,  
  10. # define DT_DIR DT_DIR
  11.     DT_BLK = 6,  
  12. # define DT_BLK DT_BLK
  13.     DT_REG = 8,  
  14. # define DT_REG DT_REG
  15.     DT_LNK = 10,  
  16. # define DT_LNK DT_LNK
  17.     DT_SOCK = 12,  
  18. # define DT_SOCK DT_SOCK
  19.     DT_WHT = 14  
  20. # define DT_WHT DT_WHT
  21. };  
        然後使用 readdir 這個函式來讀取檔案資訊:
  1. struct dirent *readdir(DIR *dp);  
        dp 為呼叫 opendir 時的返回值。當讀取成功時,函式返回一個儲存檔案資訊的 dirent 結構體指標,當到達目錄末尾或出錯時返回 NULL。每次呼叫該函式時讀取下一個檔案的資訊,所以不斷呼叫函式直到它返回 NULL,就可以遍歷該目錄下所有的檔案:
  1. struct dirent * ptr = readdir(pDir);  
(3)Step Three

        最後這一步也是收尾工作,關閉目錄:

  1. int closedir(DIR *dp);  
        同樣的,dp 為呼叫 opendir 時的返回值,成功時返回0,失敗時返回-1。
  1. closedir(pDir);  
(4)Final

        因此,總的遍歷目錄檔案的程式可以這樣寫:

  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <dirent.h>
  4. int main()  
  5. {  
  6.     DIR* pDir;  
  7.     struct dirent* ptr;  
  8.     if( !(dir = opendir(".")) )  
  9.         return -1;  
  10.     while( (ptr = readdir(pDir)) != 0 )  
  11.     {  
  12.         /*Processing File*/
  13.     }  
  14.     closedir(pDir);  
  15.     return 0;  
  16. }  
總結

        一般來說,第一種方法只能在 Windows VS 環境下用,而第二種方法只能在 Linux/Unix 環境下使用。因為在用 VS 寫程式的過程中想用第二種方法看看能不能用,結果是找不到標頭檔案 <dirent.h>,而且從結構體 dirent 的定義中我們就可以看出,它只能在 Linux 下使用,因為 dirent 的定義中有個欄位是表示檔案的 i-node number,這個恰恰是在Linux 的檔案管理中才有的。當在 Linux 想用第二種方法時則找不到標頭檔案 <io.h>。但網路上可能有一些方法可以兩種方法在兩個平臺上都可以使用,這個我倒沒有去搜索相關的資料,如果大家有什麼好的辦法,可以分享一下。上面的兩個程式我都已經測試過了,用的是 VS2012 和 Ubuntu。

        另外,由上面的解說可以看到,在 Windows 下可以很容易地讀取某一型別的檔案的資訊(比如 txt 檔案),但在 Linux 下則需要多花些功夫(比如要檢視 txt 檔案,你得讀取檔案資訊然後再判斷副檔名是不是 .txt)。

轉載一些自己感覺好的文章,只是為了記錄,以後查詢方便,大家儘量去閱讀原文。