C++如何遍历文件夹中的文件名

本文介绍了在Windows环境下使用C++遍历指定文件夹下的文件名,包括基于<io.h>和<fileapi.h>库的两种方法。详细解析了相关函数和结构体,如_finddata_t,展示了如何处理文件和子目录,并提供了CBrowseDir类和CStatDir类的示例,用于遍历和统计目录。同时,还给出了代码示例及运行结果。
摘要由CSDN通过智能技术生成

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以上的标准编译器检查更为严格。

参考博客(鸣谢)

  1. C++ 遍历文件夹下所有文件的多种方法
  2. LPCWSTR与string相互转换
  3. C++读取文件夹中所有的文件或者是特定后缀的文件
  4. _finddata_t结构体用法

【水平所限,错漏难免。创作不易,轻喷勿骂】

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值