每次遇到這樣的問題總會折騰很久,到網上搜,或者查資料,弄了很多次,但就是沒記住,這次寫程序又遇到了,干脆就把它都弄清楚了,然后順便在這里記錄一下,以后再遇到就不用到處去找了。
用 C/C++ 遍歷目錄文件主要有兩種方式,分別對應在 Windows VS 環境下和 Linux\Unix 環境下的方法,它們各自所使用的函數如下:
- (Windows VS)_findfirst, _findnext, _findclose
- (Linux\Unix)opendir, readdir, closedir
下面就來詳細地說說這兩種方式
第一(_findfirst, _findnext, _findclose)
基本流程:_findfirst-->_findnext-->_findclose
(1)First Step
_findfirst 的函數原型:
它返回一個文件句柄,可以作為其他函數的參數,並將文件名匹配由 filespec 指定的模式的第一個文件的信息保存在 fileinfo 里。例如我要找某個目錄下的 txt 文件,那么 fileinfo 就保存了這個目錄下第一個 txt 文件的信息,這種情況下我們可以這樣來調用這個函數:
而用來保存文件信息的 fileinfo 是一個數據類型為 _finddata_t 的結構體,在頭文件IO.H定義:
- struct _finddata_t {
- unsigned attrib; /*文件屬性*/
- time_t time_create; /*文件創建時間, -1 for FAT file systems */
- time_t time_access; /*文件最后一次訪問的時間 -1 for FAT file systems */
- time_t time_write; /*文件最后一次寫的時間*/
- _fsize_t size; /*文件大小*/
- char name[_MAX_FNAME]; /*匹配的文件名,不包含目錄,_MAX_FNAME在STDLIB.H中定義為256字節*/
- };
其中文件屬性可以是以下的這些值(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 出馬了,同樣我們先看看其函數原型:
這個函數調用如果成功就返回0,否則返回-1。它將下一個文件名匹配 filespec 的文件的信息保存在 fileinfo 里面,handle 是調用 _findfirst 時返回的句柄。比如說我們這里的例子,要得到下一個txt文件的信息,我們可以這樣調用函數
所以我們不斷調用 _findnext 直到它返回-1就可以遍歷所有的txt文件了。
(3)Step Three
函數 _findclose 就是做一些收尾工作,關閉文件句柄:
同樣地,handle 也是調用 _findfirst 時返回的句柄。
(4)Final
綜上所述,遍歷某個目錄下指定的文件(所有文件則用*表示)可以這樣寫:
第二(opendir, readdir, closedir)
基本流程:opendir-->readdir-->closedir
(1)Step One
使用這些函數我們需要包含頭文件 <sys/types.h> 和 <dirent.h>。
我們要讀取目錄下的文件信息,首先要打開目錄,也就是要調用函數 opendir:
這個函數以目錄的路徑為參數,如果打開成功,就返回一個DIR類型的指針,相當於上面所說的句柄,用於后續函數調用;否則返回 NULL。所以如果要打開當前目錄可以這樣調用函數:
這樣我們就打開了目錄,返回值 pDir 用於接下來函數調用的參數。
(2)Step Two
接下來就是讀取文件的信息了 ,在 <dirent.h> 中定義了一個結構體 dirent ,用來保存文件信息,定義如下:
- struct dirent {
- ino_t d_ino; /* i-inode number */
- off_t d_off; /* offset to the next dirent */
- unsigned short d_reclen; /* length of this record */
- unsigned char d_type; /* type of file */
- char d_name[256]; /* null-terminated filename */
- };
這里面其實經常要用到的就是 d_type 和 d_name 這兩個字段,分別表示文件類型和文件名。
d_type 表示文件類型,其取值定義如下:
- enum
- {
- DT_UNKNOWN = 0,
- # define DT_UNKNOWN DT_UNKNOWN
- DT_FIFO = 1,
- # define DT_FIFO DT_FIFO
- DT_CHR = 2,
- # define DT_CHR DT_CHR
- DT_DIR = 4,
- # define DT_DIR DT_DIR
- DT_BLK = 6,
- # define DT_BLK DT_BLK
- DT_REG = 8,
- # define DT_REG DT_REG
- DT_LNK = 10,
- # define DT_LNK DT_LNK
- DT_SOCK = 12,
- # define DT_SOCK DT_SOCK
- DT_WHT = 14
- # define DT_WHT DT_WHT
- };
然后使用 readdir 這個函數來讀取文件信息:
dp 為調用 opendir 時的返回值。當讀取成功時,函數返回一個保存文件信息的 dirent 結構體指針,當到達目錄末尾或出錯時返回 NULL。每次調用該函數時讀取下一個文件的信息,所以不斷調用函數直到它返回 NULL,就可以遍歷該目錄下所有的文件:
(3)Step Three
最后這一步也是收尾工作,關閉目錄:
同樣的,dp 為調用 opendir 時的返回值,成功時返回0,失敗時返回-1。
(4)Final
因此,總的遍歷目錄文件的程序可以這樣寫:
- #include <stdio.h>
- #include <sys/types.h>
- #include <dirent.h>
- int main()
- {
- DIR* pDir;
- struct dirent* ptr;
- if( !(dir = opendir(".")) )
- return -1;
- while( (ptr = readdir(pDir)) != 0 )
- {
- /*Processing File*/
- }
- closedir(pDir);
- return 0;
- }
總結
一般來說,第一種方法只能在 Windows VS 環境下用,而第二種方法只能在 Linux/Unix 環境下使用。因為在用 VS 寫程序的過程中想用第二種方法看看能不能用,結果是找不到頭文件 <dirent.h>,而且從結構體 dirent 的定義中我們就可以看出,它只能在 Linux 下使用,因為 dirent 的定義中有個字段是表示文件的 i-node number,這個恰恰是在Linux 的文件管理中才有的。當在 Linux 想用第二種方法時則找不到頭文件 <io.h>。但網絡上可能有一些方法可以兩種方法在兩個平台上都可以使用,這個我倒沒有去搜索相關的資料,如果大家有什么好的辦法,可以分享一下。上面的兩個程序我都已經測試過了,用的是 VS2012 和 Ubuntu。
另外,由上面的解說可以看到,在 Windows 下可以很容易地讀取某一類型的文件的信息(比如 txt 文件),但在 Linux 下則需要多花些功夫(比如要查看 txt 文件,你得讀取文件信息然后再判斷文件擴展名是不是 .txt)。


