C++ 如何遍历文件夹中的文件名
0 问题描述
在Windows环境下遍历指定文件夹下的文件名
注意:本文中的所有代码均在Win10系统下,VS2019下运行。
声明:本文中的所有代码并非原创(来自网络,在上述系统环境及编译环境下测试运行并调bug,算是做了代码整理的工作),本文中的解析是原创。
为了测试,构造如下测试文件夹结构../../data
1 <io.h>中的方法
这个方法的核心是来自 io.h 。先看例程代码。
#include <iostream>
#include <string>
#include <vector>
#include <io.h>
using namespace std;
void getFileNames(string path, vector<string>& files)
{
// hFile如果定义为long类型则会报错
intptr_t hFile = 0;
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("/*").c_str(), &fileinfo)) != -1)
{
do
{
// 如果目标是一个文件夹路径,则向下递归查找
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
getFileNames(p.assign(path).append("/").append(fileinfo.name), files);
}
else
{
files.push_back(p.assign(path).append("/").append(fileinfo.name));
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
int main(int argc, char* argv[])
{
vector<string> fileNames;
// 此处添加待处理的文件夹路径
string path("../../data");
getFileNames(path, fileNames);
for (const string& ph : fileNames)
{
cout << ph << endl;
}
system("pause");
return EXIT_SUCCESS;
}
首先来看看 _finddata_t
这个主要结构体,运行代码的系统环境是64位的。
查看定义发现来自corecrt_io.h
#define _finddata_t _finddata64i32_t
再看看_finddata64i32_t
定义仍然来自corecrt_io.h
struct _finddata64i32_t
{
unsigned attrib;
__time64_t time_create; // -1 for FAT file systems
__time64_t time_access; // -1 for FAT file systems
__time64_t time_write;
_fsize_t size;
char name[260];
};
查阅资料对这个结构体各个属性进行理解:
结构体成员变量 | 含义 | 取值 | 备注 |
---|---|---|---|
unsigned attrib | 文件属性 | _A_ARCH | 存档(0x20) |
_A_HIDDEN | 隐藏(0x02) | ||
_A_NORMAL | 正常(0x00) | ||
_A_RDONLY | 只读(0x01) | ||
_A_SUBDIR | 文件夹(0x10) | ||
_A_SYSTEM | 系统(0x04) | ||
__time64_t time_create | 文件创建的时间 | 用来保存从1970年1月1日0时0分0秒到现在时刻的秒数 | |
__time64_t time_access | 文件最后一次被访问的时间 | ||
__time64_t time_write | 文件最后一次被修改的时间 | ||
_fsize_t size | 文件的大小 | 用字节数表示 | |
char name[260] | 文件的文件名 |
接着是对这个数据结构进行处理的三个函数:
_findfirst
、_findnext
、_findclose
#define _findfirst _findfirst64i32
_Success_(return != -1)
_Check_return_
_ACRTIMP intptr_t __cdecl _findfirst64i32(
_In_z_ char const* _FileName,
_Out_ struct _finddata64i32_t* _FindData
);
#define _findnext _findnext64i32
_Success_(return != -1)
_Check_return_
_ACRTIMP int __cdecl _findnext64i32(
_In_ intptr_t _FindHandle,
_Out_ struct _finddata64i32_t* _FindData
);
_Check_return_opt_
_ACRTIMP int __cdecl _findclose(
_In_ intptr_t _FindHandle
);
每个函数都带了一堆复杂的宏定义,但不用去过分细究它,这些宏定义是微软的注释语言。我们只需要理解一下这些函数的输入输出即可:
函数名称 | 功能 | 输入 | 输出 | 备注 |
---|---|---|---|---|
_findfirst | 如果查找成功,则返回一个intptr_t类型的查找用的句柄,失败时则返回-1 | 以’\0’结尾的字符串,文件名 | _FileName | |
结构体指针 | _FindData | |||
_findnext | 若成功返回0,否则返回-1 | 查找用的句柄 | _FindHandle | |
结构体指针 | _FindData | |||
_findclose | 若成功则返回0,否则返回-1 | 关闭查找句柄 | _FindHandle |
再来关注一下查找用的句柄:intptr_t hFile = 0;
这个句柄在有些代码中使用的类型是long
,测试时会报错误,用intptr_t
则没有这个问题。
举例:显示如下的测试结果
这里有一个隐藏彩蛋:
if ((hFile = _findfirst(p.assign(path).append("/*").c_str(), &fileinfo)) != -1)
这里"/*"
中可以用通配符来进行格式限制,例如:
if ((hFile = _findfirst(p.assign(path).append("/*.jpg").c_str(), &fileinfo)) != -1)
这样就可以把文件夹中所有的jpg文件筛选出来。
2 基于以上方法构造CBrowseDir类
接下来这段代码是基于以上原理,更贴近C++的实现方式,构造了一个CBrowseDir类,并派生了一个CStatDir类,通过这两个类
CBrowseDir.h
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <io.h>
#include <stdlib.h>
#include <direct.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class CBrowseDir
{
protected:
// 存放初始目录的绝对路径,以'\'结尾
char m_szInitDir[_MAX_PATH];
public:
// 缺省构造器
CBrowseDir();
// 设置初始目录为dir,如果返回false,表示目录不可用
bool SetInitDir(const char* dir);
// 开始遍历初始目录及其子目录下由filespec指定类型的文件
// filespec 可以使用通配符 * ?,不能包含路径
// 如果返回false,表示遍历过程被用户中止
bool BeginBrowse(const char* filespec);
vector<string> BeginBrowseFilenames(const char* filespec);
protected:
// 遍历目录dir下由filespec指定的文件
// 对于子目录,采用迭代的方法
// 如果返回false,表示中止遍历文件
bool BrowseDir(const char* dir, const char* filespec);
vector<string> GetDirFilenames(const char* dir, const char* filespec);
// 函数BrowseDir 每找到一个文件,就调用ProcessFile
// 并把文件名作为参数传递过去
// 如果返回false,表示中止遍历文件
// 用户可以覆写该函数,加入自己的处理代码
virtual bool ProcessFile(const char* filename);
// 函数BrowseDir每进入一个目录,就调用ProcessDir
// 并把正在处理的目录名及上一级目录名作为参数传递过去
// 如果正在处理的是初始目录,则parentdir=NULL
// 用户可以覆写该函数,加入自己的处理代码
// 比如用户可以在这里统计子目录的个数
virtual void ProcessDir(const char* currentdir, const char* parentdir);
};
CBrowseDir.cpp
#include "CBrowseDir.h"
CBrowseDir::CBrowseDir()
{
// 用当前目录初始化m_szInitDir
_getcwd(m_szInitDir, _MAX_PATH);
// 如果目录的最后一个字母不是'\',则在最后加上一个'\'
int len = strlen(m_szInitDir);
if (m_szInitDir[len - 1] != '\\')
strcat(m_szInitDir, "\\");
}
bool CBrowseDir::SetInitDir(const char* dir)
{
// 先把dir转换为绝对路径
if (_fullpath(m_szInitDir, dir, _MAX_PATH) == NULL)
return false;
// 判断目录是否存在
if (_chdir(m_szInitDir) != 0)
return false;
// 如果目录的最后一个字母不是'\',则在最后加上一个'\'
int len = strlen(m_szInitDir);
if (m_szInitDir[len - 1] != '\\')
strcat(m_szInitDir, "\\");
return true;
}
vector<string> CBrowseDir::BeginBrowseFilenames(const char* filespec)
{
ProcessDir(m_szInitDir, NULL);
return GetDirFilenames(m_szInitDir, filespec);
}
bool CBrowseDir::BeginBrowse(const char* filespec)
{
ProcessDir(m_szInitDir, NULL);
return BrowseDir(m_szInitDir, filespec);
}
bool CBrowseDir::BrowseDir(const char* dir, const char* filespec)
{
_chdir(dir);
// 首先查找dir中符合要求的文件
intptr_t hFile;
_finddata_t fileinfo;
if ((hFile = _findfirst(filespec, &fileinfo)) != -1)
{
do
{
// 检查是不是目录
// 如果不是,则进行处理
if (!(fileinfo.attrib & _A_SUBDIR))
{
char filename[_MAX_PATH];
strcpy(filename, dir);
strcat(filename, fileinfo.name);
cout << filename << endl;
if (!ProcessFile(filename))
return false;
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
// 查找dir中的子目录
// 因为在处理dir中的文件时,派生类的ProcessFile有可能改变了
// 当前目录,因此还要重新设置当前目录为dir。
// 执行过_findfirst后,可能系统记录下了相关信息,因此改变目录
// 对_findnext没有影响。
_chdir(dir);
if ((hFile = _findfirst("*.*", &fileinfo)) != -1)
{
do
{
// 检查是不是目录
// 如果是,再检查是不是 . 或 ..
// 如果不是,进行迭代
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
{
char subdir[_MAX_PATH];
strcpy(subdir, dir);
strcat(subdir, fileinfo.name);
strcat(subdir, "\\");
ProcessDir(subdir, dir);
if (!BrowseDir(subdir, filespec))
return false;
}
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
return true;
}
vector<string> CBrowseDir::GetDirFilenames(const char* dir, const char* filespec)
{
_chdir(dir);
vector<string> filename_vector;
filename_vector.clear();
// 首先查找dir中符合要求的文件
intptr_t hFile;
_finddata_t fileinfo;
if ((hFile = _findfirst(filespec, &fileinfo)) != -1)
{
do
{
// 检查是不是目录
// 如果不是,则进行处理
if (!(fileinfo.attrib & _A_SUBDIR))
{
char filename[_MAX_PATH];
strcpy(filename, dir);
strcat(filename, fileinfo.name);
filename_vector.push_back(filename);
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
// 查找dir中的子目录
// 因为在处理dir中的文件时,派生类的ProcessFile有可能改变了
// 当前目录,因此还要重新设置当前目录为dir。
// 执行过_findfirst后,可能系统记录下了相关信息,因此改变目录
// 对_findnext没有影响。
_chdir(dir);
if ((hFile = _findfirst("*.*", &fileinfo)) != -1)
{
do
{
// 检查是不是目录;
// 如果是,再检查是不是. 或 ..
// 如果不是,进行迭代
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
{
char subdir[_MAX_PATH];
strcpy(subdir, dir);
strcat(subdir, fileinfo.name);
strcat(subdir, "\\");
ProcessDir(subdir, dir);
vector<string> tmp = GetDirFilenames(subdir, filespec);
for (vector<string>::iterator it = tmp.begin(); it < tmp.end(); it++)
{
filename_vector.push_back(*it);
}
}
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
return filename_vector;
}
bool CBrowseDir::ProcessFile(const char* filename)
{
return true;
}
void CBrowseDir::ProcessDir(const char* currentdir, const char* parentdir)
{
}
接下来是这个类的派生类定义:
CStatDir.h
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <io.h>
#include <stdlib.h>
#include <direct.h>
#include <iostream>
#include <string>
#include <vector>
#include "CBrowseDir.h"
// 从CBrowseDir派生出的子类 ,用来统计目录中的文件及子目录个数
class CStatDir :public CBrowseDir
{
protected:
int m_nFileCount; // 保存文件个数
int m_nSubdirCount; // 保存子目录个数
public:
// 缺省构造器
CStatDir();
// 返回文件个数
int GetFileCount();
// 返回子目录个数
int GetSubdirCount();
protected:
// 覆写虚函数ProcessFile,每调用一次,文件个数加1
virtual bool ProcessFile(const char* filename);
// 覆写虚函数ProcessDir,每调用一次,子目录个数加1
virtual void ProcessDir(const char* currentdir, const char* parentdir);
};
CStatDir.cpp
#include "CStatDir.h"
// 从CBrowseDir派生出的子类 ,用来统计目录中的文件及子目录个数
CStatDir::CStatDir()
{
// 初始化数据成员m_nFileCount和m_nSubdirCount
m_nFileCount = 0;
m_nSubdirCount = 0;
}
// 返回文件个数
int CStatDir::GetFileCount()
{
return m_nFileCount;
}
// 返回子目录个数
int CStatDir::GetSubdirCount()
{
// 因为进入初始目录时,会先调用一次函数Processdir,
// 所以减1后才是真正的子目录数量
return m_nSubdirCount - 1;
}
// 覆写虚函数ProcessFile,每调用一次,文件个数加1
bool CStatDir::ProcessFile(const char* filename)
{
++m_nFileCount;
return CBrowseDir::ProcessFile(filename);
}
// 覆写虚函数ProcessDir,每调用一次,子目录个数加1
void CStatDir::ProcessDir(const char* currentdir, const char* parentdir)
{
++m_nSubdirCount;
CBrowseDir::ProcessDir(currentdir, parentdir);
}
main.cpp
#include "CBrowseDir.h"
#include "CStatDir.h"
#include <iostream>
#include <io.h>
#include <string>
#include <stdlib.h>
#include <direct.h>
using namespace std;
int main(int argc, char* argv[])
{
char buf[256];
cout << "请输入要统计的目录名:" << endl;
cin >> buf;
CStatDir statdir;
if (!statdir.SetInitDir(buf))
{
cout << "目录不存在" << endl;
return EXIT_FAILURE;
}
statdir.BeginBrowse("*.*");
cout << "文件总数: " << statdir.GetFileCount() << "。子目录总数:" << statdir.GetSubdirCount() << endl;
system("pause");
return EXIT_SUCCESS;
}
一个示例的结果如下:
3 基于 <fileapi.h>中方法的实现
以下的代码来自另一种实现,其中的核心函数是基于<fileapi.h>中的相关方法。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
#include <vector>
#include <Windows.h>
#include <fstream>
#include <iterator>
#include <string>
using namespace std;
//#define MAX_PATH 1024 // 最长路径长度
/*------------------------
* 功能:递归遍历文件夹,找到其中包含的所有文件
* ------------------------
* 函数: find
* 访问: public
*
* 参数: lpPath [in] 需遍历的文件夹目录
* 参数: fileList [in] 以文件名称的形式存储遍历后的文件
*/
// LPCWSTR转换成string
string WChar2Ansi(LPCWSTR pwszSrc)
{
int nLen = WideCharToMultiByte(CP_ACP, 0, pwszSrc, -1, NULL, 0, NULL, NULL);
if (nLen <= 0)
return string("");
char* pszDst = new char[nLen];
if (NULL == pszDst)
return string("");
WideCharToMultiByte(CP_ACP, 0, pwszSrc, -1, pszDst, nLen, NULL, NULL);
pszDst[nLen - 1] = 0;
string strTemp(pszDst);
delete[] pszDst;
return strTemp;
}
// string 转 LPCWSTR
LPCWSTR stringToLPCWSTR(std::string orig)
{
size_t origsize = orig.length() + 1;
const size_t newsize = 100;
size_t convertedChars = 0;
wchar_t* wcstring = (wchar_t*)malloc(sizeof(wchar_t) * (orig.length() - 1));
mbstowcs_s(&convertedChars, wcstring, origsize, orig.c_str(), _TRUNCATE);
return wcstring;
}
// 核心函数主体实现,可以在lpPath中找到所有的目标文件
// 并将找到的目标文件路径添加进fileList中
void find(char* lpPath, vector<string>& fileList)
{
char szFind[MAX_PATH];
WIN32_FIND_DATA FindFileData;
strcpy(szFind, lpPath);
strcat(szFind, "\\*.*");
HANDLE hFind = ::FindFirstFile(stringToLPCWSTR(szFind), &FindFileData);
if (INVALID_HANDLE_VALUE == hFind)
return;
while (true)
{
if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (FindFileData.cFileName[0] != '.')
{
char szFile[MAX_PATH];
strcpy(szFile, lpPath);
strcat(szFile, "\\");
strcat(szFile, (char*)(FindFileData.cFileName));
find(szFile, fileList);
}
}
else
{
//cout << FindFileData.cFileName << endl;
fileList.push_back(WChar2Ansi(FindFileData.cFileName));
}
if (!FindNextFile(hFind, &FindFileData))
break;
}
FindClose(hFind);
}
int main(int argc, char* argv[])
{
vector<string> fileList;
char filepth[260];
sprintf(filepth, "../../data");
find(filepth, fileList);
for (int i = 0; i < fileList.size(); ++i)
{
cout << fileList[i] << endl;
}
cout << "File Number: " << fileList.size() << endl;
system("pause");
return EXIT_SUCCESS;
}
一个示例的结果如下:
跟前面的方法相比,该代码未实现通配符,也没有对子文件夹进行递归查找,仅在目标文件夹的根目录下查找所有的文件。
对于C++11以上的标准,要注意,不能将find函数的第二个参数设置成vector<const string>&
,否则会报错。因为C++11以上的标准编译器检查更为严格。