在 windows 上面如何进行一些文件夹的操作,比如删除文件夹 、判断是否是文件夹、获取文件夹大小等。
使用 win 32 api 实现文件夹操作
查询 win32 api 你会发现没有直接的函数可以使用,win 32 api 只提供了DeleteFile(删除文件)、RemoveDirectory (删除空文件夹)、GetFileSizeEx(获取文件大小)等操作文件相关 api,但是我们可以通过遍历文件夹的方式,对文件夹进行操作。
// 判断是否是文件夹
bool IsDirectory(std::wstring& dir) {
DWORD dw_attribute = ::GetFileAttributes(dir.c_str());
if ((INVALID_FILE_ATTRIBUTES != dw_attribute) &&
(FILE_ATTRIBUTE_DIRECTORY & dw_attribute)) {
return true;
}
return false;
}
// 获取文件夹大小
uint64_t GetFolderSize(const std::wstring& dir) {
uint64_t folder_size = 0;
std::wstring dir_full_path = dir;
if (dir_full_path.back() != '\\') {
dir_full_path += L"\\";
}
std::vector<std::wstring> vec_dir;
vec_dir.reserve(1000);
vec_dir.push_back(dir_full_path);
size_t i = 0;
size_t size = vec_dir.size();
while (i < size) {
std::wstring str_dir = vec_dir[i];
WIN32_FIND_DATAW find_file_data;
memset(&find_file_data, 0, sizeof(find_file_data));
HANDLE hfind = ::FindFirstFile((str_dir + L"*.*").c_str(), &find_file_data);
if (hfind != INVALID_HANDLE_VALUE) {
BOOL bFind = TRUE;
while (bFind) {
std::wstring strFile = str_dir + find_file_data.cFileName;
if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (StrCmp(find_file_data.cFileName, _T("..")) != 0 &&
StrCmp(find_file_data.cFileName, _T(".")) != 0) {
vec_dir.push_back(strFile + _T("\\"));
}
} else {
LARGE_INTEGER file_size = { 0 };
file_size.LowPart = find_file_data.nFileSizeLow;
file_size.HighPart = find_file_data.nFileSizeHigh;
folder_size += file_size.QuadPart;
}
bFind = ::FindNextFile(hfind, &find_file_data);
}
FindClose(hfind);
}
i++;
size = vec_dir.size();
}
return folder_size;
}
// 删除文件夹
bool DeleteDirectory(const std::wstring& dir) {
if (dir.empty()) {
log_fault << "dir is empty.";
return false;
}
bool ret = true;
std::wstring dir_full_path = dir;
if (dir_full_path.back() != '\\') {
dir_full_path += L"\\";
}
std::vector<std::wstring> vec_dir;
vec_dir.reserve(100);
vec_dir.push_back(dir_full_path);
int i = 0;
int size = vec_dir.size();
while (i < size) {
std::wstring str_dir = vec_dir[i];
WIN32_FIND_DATAW find_file_data;
memset(&find_file_data, 0, sizeof(find_file_data));
HANDLE hfind = ::FindFirstFile((str_dir + L"*.*").c_str(), &find_file_data);
if (hfind != INVALID_HANDLE_VALUE) {
bool find = true;
while (find) {
std::wstring str_file = str_dir + find_file_data.cFileName;
if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (StrCmp(find_file_data.cFileName, _T("..")) != 0 &&
StrCmp(find_file_data.cFileName, _T(".")) != 0) {
vec_dir.push_back(str_file + _T("\\"));
}
} else {
if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
DWORD dwFileAttributes = find_file_data.dwFileAttributes & (~FILE_ATTRIBUTE_READONLY);
SetFileAttributes(str_file.c_str(), dwFileAttributes);
}
if (!::DeleteFile(str_file.c_str())) {
ret = false;
break;
}
}
find = ::FindNextFile(hfind, &find_file_data);
}
FindClose(hfind);
}
i++;
size = vec_dir.size();
}
if (ret && !vec_dir.empty()) {
for (i = vec_dir.size() - 1; i >= 0; i--) {
ret = ::RemoveDirectory(vec_dir[i].c_str());
if (!ret) {
return false;
}
}
}
return true;
}
使用QT 的 QDir QFile 等类实现该功能
使用 QDir QFile 等类的实现方式Qt 官方文档有很多介绍,这里就不再赘述了。读者可以自行查看。
使用 C++ 17 的 filesystem 系统
C++ 17 新增了 filesystem 文件系统,提供了一些很方便使用的功能:
-
路径操作 (std::filesystem::path): 用于处理文件和目录路径的类。
操作如拼接、解析、检查路径格式等。 -
文件和目录的创建、删除和查询:
创建和删除文件夹 (create_directory, remove, remove_all 等)。
检查文件或文件夹的存在 (exists) 和状态 (is_directory, is_regular_file 等)。
文件大小和文件系统空间信息: -
查询文件大小 (file_size),以及文件系统的空闲空间和容量 (space 等)。
-
文件和目录的复制、移动和重命名:
如 copy, copy_file, move, rename 等函数。 -
目录遍历:
使用 std::filesystem::directory_iterator 或 std::filesystem::recursive_directory_iterator 遍历目录。 -
文件属性和权限:
获取和设置文件权限 (permissions),读取最后一次修改时间 (last_write_time) 等。
看起来也是非常方便了,可以满足我们对文件夹的常规操作要求了。
但是,这里要划重点,C++ 17 的 filesysetem 与 Qt 或者 win 32 api 在使用上有什么异同?
三种方式之间的区别
使用方式上的区别主要在于异常处理和错误反馈方面,Qt 文件处理 api 和 win 32 api 都是通过错误误代码和返回值告诉调用者错误信息,而 C++ 标准库则提供了错误码和异常处理机制。也就是说使用 win32 和 Qt 的 api 是不需要处理异常的,但是如果你是在使用 C++ 17 filesystem,必须要关注异常处理,否侧如果有未处理的异常,会引起 app crash。
C++ 17 filesystem 同时提供了错误码和异常两种错误信息,使用的时候一定要查看清楚该 api 是否会抛出异常。
一般有错误码返回的是不会抛出异常的,比如判断文件是否存在的 exist 函数,明确表示不会抛出异常
需要注意的是,同时有一个重载的函数,会调用上面的函数并抛出异常:
C++ 的 try cache 异常捕获机制貌似平日开发使用的比较少了,所以,使用 filesystem 的时候一定要注意了,如果你使用的 filesystem 有异常抛出但是忘记了捕获就惨了…
如果你不想处理异常,想要使用 Qt 或者 win32 api 的方式处理异常,那你可以选择使用返回错误码的api(有些可以返回错误码,但是并没有标明不会抛出异常,这种也需要捕获异常),而不是抛出异常的 api。
但是有些情况下,比如遍历文件夹或者递归遍历文件夹,必须捕获异常,因为迭代器自增的时候,有可能会抛出异常:
可以看到构造的时候有带错误码的构造函数,但是并没有标明不会抛出异常。而且截图中第一个构造函数明确会抛出异常。
迭代器自增的方法明确会抛出异常。
递归遍历的迭代器也是一样的:
所以,使用 C++ 17 filesystem 的时候,一定要关注它的异常处理,这是跟 QT 或者 win32 api 设计以及使用上的最大的不同!!!。