好的,让我们来一场关于 C++ std::filesystem
的深度探险,目标是搞定那些复杂的文件系统操作,并优雅地处理各种错误,让你的代码在文件系统的世界里也能横着走!
开场白:文件系统,程序猿的后花园
各位程序猿朋友们,大家好!欢迎来到本次“C++ std::filesystem
深度游”讲座。文件系统,对于我们来说,就像后花园一样,我们经常在里面创建、修改、删除各种文件和目录。但是,这个后花园也充满了陷阱,一不小心就会踩到雷,导致程序崩溃或者数据丢失。所以,掌握好 std::filesystem
这个工具,才能让我们在这个后花园里自由驰骋,种出我们想要的代码之花。
第一站:std::filesystem
的基本概念和常用操作
首先,我们要明确 std::filesystem
是什么?它就是 C++17 引入的一个标准库,专门用来处理文件系统相关的操作。有了它,我们就可以用 C++ 代码来创建目录、删除文件、遍历目录等等,而不用再依赖平台特定的 API。
1. path
:文件系统的导航仪
std::filesystem::path
是整个库的核心,它代表文件系统中的一个路径。你可以把它想象成一个导航仪,告诉程序要去哪里。
#include <iostream>
#include <filesystem>
int main() {
std::filesystem::path my_path = "/home/user/documents/my_file.txt";
std::cout << "全路径: " << my_path << std::endl;
std::cout << "文件名: " << my_path.filename() << std::endl;
std::cout << "父目录: " << my_path.parent_path() << std::endl;
std::cout << "根目录: " << my_path.root_path() << std::endl;
std::cout << "是否是绝对路径: " << my_path.is_absolute() << std::endl;
return 0;
}
这段代码展示了 path
的基本用法,你可以用它来获取文件名、父目录、根目录等等,还可以判断路径是绝对路径还是相对路径。
2. 文件和目录的基本操作
有了 path
这个导航仪,我们就可以开始进行文件和目录的基本操作了。
-
创建目录:
create_directory
和create_directories
#include <iostream> #include <filesystem> int main() { std::filesystem::path dir_path = "my_new_directory"; try { if (!std::filesystem::exists(dir_path)) { std::filesystem::create_directory(dir_path); // 创建单个目录 std::cout << "目录 " << dir_path << " 创建成功!" << std::endl; } else { std::cout << "目录 " << dir_path << " 已经存在!" << std::endl; } std::filesystem::path nested_dir_path = "nested/directory/structure"; std::filesystem::create_directories(nested_dir_path); // 创建多级目录 std::cout << "多级目录 " << nested_dir_path << " 创建成功!" << std::endl; } catch (const std::exception& e) { std::cerr << "创建目录失败: " << e.what() << std::endl; } return 0; }
create_directory
用于创建单个目录,如果目录已经存在,会抛出异常。create_directories
用于创建多级目录,如果中间的目录不存在,会自动创建。 -
删除文件和目录:
remove
和remove_all
#include <iostream> #include <filesystem> int main() { std::filesystem::path file_path = "my_file.txt"; std::filesystem::path dir_path = "my_new_directory"; try { // 创建一个空文件 std::ofstream outfile(file_path); outfile.close(); if (std::filesystem::exists(file_path)) { std::filesystem::remove(file_path); // 删除文件 std::cout << "文件 " << file_path << " 删除成功!" << std::endl; } else { std::cout << "文件 " << file_path << " 不存在!" << std::endl; } if (std::filesystem::exists(dir_path)) { std::filesystem::remove_all(dir_path); // 删除目录及其内容 std::cout << "目录 " << dir_path << " 及其内容 删除成功!" << std::endl; } else { std::cout << "目录 " << dir_path << " 不存在!" << std::endl; } } catch (const std::exception& e) { std::cerr << "删除文件/目录失败: " << e.what() << std::endl; } return 0; }
remove
用于删除文件或空目录,如果目录不为空,会删除失败。remove_all
用于删除目录及其所有内容,包括子目录和文件,要小心使用! -
复制文件:
copy
#include <iostream> #include <filesystem> int main() { std::filesystem::path source_file = "source.txt"; std::filesystem::path destination_file = "destination.txt"; try { // 创建一个源文件 std::ofstream outfile(source_file); outfile << "This is the source file."; outfile.close(); std::filesystem::copy(source_file, destination_file, std::filesystem::copy_options::overwrite_existing); std::cout << "文件 " << source_file << " 复制到 " << destination_file << " 成功!" << std::endl; } catch (const std::exception& e) { std::cerr << "复制文件失败: " << e.what() << std::endl; } return 0; }
copy
用于复制文件,可以指定复制选项,比如overwrite_existing
表示如果目标文件已经存在,则覆盖它。 -
重命名/移动文件或目录:
rename
#include <iostream> #include <filesystem> int main() { std::filesystem::path old_name = "old_file.txt"; std::filesystem::path new_name = "new_file.txt"; try { // 创建一个源文件 std::ofstream outfile(old_name); outfile << "This is the old file."; outfile.close(); std::filesystem::rename(old_name, new_name); std::cout << "文件 " << old_name << " 重命名为 " << new_name << " 成功!" << std::endl; } catch (const std::exception& e) { std::cerr << "重命名文件失败: " << e.what() << std::endl; } return 0; }
rename
用于重命名文件或目录,也可以用来移动文件或目录到新的位置。
第二站:文件属性和状态查询
除了基本的文件操作,我们还需要了解文件的属性和状态,比如文件大小、创建时间、修改时间等等。
-
文件大小:
file_size
#include <iostream> #include <filesystem> int main() { std::filesystem::path file_path = "my_file.txt"; try { // 创建一个源文件 std::ofstream outfile(file_path); outfile << "This is the file content."; outfile.close(); std::uintmax_t file_size = std::filesystem::file_size(file_path); std::cout << "文件 " << file_path << " 的大小是: " << file_size << " 字节" << std::endl; } catch (const std::exception& e) { std::cerr << "获取文件大小失败: " << e.what() << std::endl; } return 0; }
file_size
函数可以获取文件的大小,返回的是字节数。 -
文件类型:
is_regular_file
、is_directory
、is_symlink
等#include <iostream> #include <filesystem> int main() { std::filesystem::path file_path = "my_file.txt"; std::filesystem::path dir_path = "my_directory"; try { // 创建一个源文件 std::ofstream outfile(file_path); outfile << "This is the file content."; outfile.close(); std::filesystem::create_directory(dir_path); if (std::filesystem::is_regular_file(file_path)) { std::cout << file_path << " 是一个普通文件" << std::endl; } if (std::filesystem::is_directory(dir_path)) { std::cout << dir_path << " 是一个目录" << std::endl; } if (std::filesystem::exists("nonexistent_file.txt")) { std::cout << "This should not be printed." << std::endl; } else { std::cout << "nonexistent_file.txt does not exist." << std::endl; } } catch (const std::exception& e) { std::cerr << "获取文件类型失败: " << e.what() << std::endl; } return 0; }
这些函数可以判断文件是否是普通文件、目录、符号链接等等。
-
最后修改时间:
last_write_time
#include <iostream> #include <filesystem> #include <chrono> int main() { std::filesystem::path file_path = "my_file.txt"; try { // 创建一个源文件 std::ofstream outfile(file_path); outfile << "This is the file content."; outfile.close(); auto last_write_time = std::filesystem::last_write_time(file_path); std::time_t tt = std::chrono::system_clock::to_time_t(last_write_time); std::cout << "文件 " << file_path << " 的最后修改时间是: " << std::ctime(&tt) << std::endl; } catch (const std::exception& e) { std::cerr << "获取最后修改时间失败: " << e.what() << std::endl; } return 0; }
last_write_time
函数可以获取文件的最后修改时间,返回的是一个std::filesystem::file_time_type
对象,需要转换成std::time_t
才能显示。
第三站:目录遍历
目录遍历是文件系统操作中非常常见的需求,比如查找特定类型的文件、统计文件数量等等。std::filesystem
提供了 directory_iterator
和 recursive_directory_iterator
来实现目录遍历。
-
directory_iterator
:遍历当前目录#include <iostream> #include <filesystem> int main() { std::filesystem::path dir_path = "."; // 当前目录 try { std::cout << "当前目录下的文件和目录:" << std::endl; for (const auto& entry : std::filesystem::directory_iterator(dir_path)) { std::cout << entry.path() << std::endl; } } catch (const std::exception& e) { std::cerr << "遍历目录失败: " << e.what() << std::endl; } return 0; }
directory_iterator
只能遍历当前目录下的文件和目录,不会进入子目录。 -
recursive_directory_iterator
:递归遍历所有子目录#include <iostream> #include <filesystem> int main() { std::filesystem::path dir_path = "."; // 当前目录 try { std::cout << "当前目录及其所有子目录下的文件和目录:" << std::endl; for (const auto& entry : std::filesystem::recursive_directory_iterator(dir_path)) { std::cout << entry.path() << std::endl; } } catch (const std::exception& e) { std::cerr << "遍历目录失败: " << e.what() << std::endl; } return 0; }
recursive_directory_iterator
会递归遍历所有子目录,直到所有文件和目录都被访问到。使用技巧:
depth()
和recursion_pending()
在递归遍历目录时,我们可能需要知道当前的深度,或者控制是否进入子目录。
recursive_directory_iterator
提供了depth()
和recursion_pending()
方法来实现这些功能。#include <iostream> #include <filesystem> int main() { std::filesystem::path dir_path = "."; // 当前目录 try { for (auto it = std::filesystem::recursive_directory_iterator(dir_path); it != std::filesystem::recursive_directory_iterator(); ++it) { std::cout << it->path() << " (深度: " << it.depth() << ")" << std::endl; // 如果是目录,并且深度大于 1,则不进入该目录 if (std::filesystem::is_directory(it->path()) && it.depth() > 1) { it.disable_recursion_pending(); std::cout << " 跳过该目录" << std::endl; } } } catch (const std::exception& e) { std::cerr << "遍历目录失败: " << e.what() << std::endl; } return 0; }
depth()
返回当前遍历的深度,disable_recursion_pending()
可以阻止进入当前目录的子目录。
第四站:符号链接
符号链接(Symbolic Link),也称为软链接,是指向另一个文件或目录的指针。std::filesystem
提供了创建、读取和判断符号链接的功能。
-
创建符号链接:
create_symlink
和create_directory_symlink
#include <iostream> #include <filesystem> int main() { std::filesystem::path target_file = "target.txt"; std::filesystem::path symlink_file = "symlink.txt"; std::filesystem::path target_dir = "target_directory"; std::filesystem::path symlink_dir = "symlink_directory"; try { // 创建目标文件 std::ofstream outfile(target_file); outfile << "This is the target file."; outfile.close(); // 创建目标目录 std::filesystem::create_directory(target_dir); std::filesystem::create_symlink(target_file, symlink_file); // 创建文件符号链接 std::cout << "文件符号链接 " << symlink_file << " 创建成功!" << std::endl; std::filesystem::create_directory_symlink(target_dir, symlink_dir); // 创建目录符号链接 std::cout << "目录符号链接 " << symlink_dir << " 创建成功!" << std::endl; } catch (const std::exception& e) { std::cerr << "创建符号链接失败: " << e.what() << std::endl; } return 0; }
create_symlink
用于创建文件符号链接,create_directory_symlink
用于创建目录符号链接。 -
读取符号链接的目标:
read_symlink
#include <iostream> #include <filesystem> int main() { std::filesystem::path symlink_file = "symlink.txt"; try { std::filesystem::path target_path = std::filesystem::read_symlink(symlink_file); std::cout << "符号链接 " << symlink_file << " 指向: " << target_path << std::endl; } catch (const std::exception& e) { std::cerr << "读取符号链接失败: " << e.what() << std::endl; } return 0; }
read_symlink
函数可以读取符号链接指向的目标路径。 -
判断是否是符号链接:
is_symlink
#include <iostream> #include <filesystem> int main() { std::filesystem::path symlink_file = "symlink.txt"; try { if (std::filesystem::is_symlink(symlink_file)) { std::cout << symlink_file << " 是一个符号链接" << std::endl; } else { std::cout << symlink_file << " 不是一个符号链接" << std::endl; } } catch (const std::exception& e) { std::cerr << "判断符号链接失败: " << e.what() << std::endl; } return 0; }
is_symlink
函数可以判断一个路径是否是符号链接。
第五站:错误处理
文件系统操作经常会遇到各种错误,比如文件不存在、权限不足等等。std::filesystem
提供了异常处理机制来处理这些错误。
-
使用
try-catch
块捕获异常#include <iostream> #include <filesystem> int main() { std::filesystem::path file_path = "nonexistent_file.txt"; try { std::uintmax_t file_size = std::filesystem::file_size(file_path); std::cout << "文件大小: " << file_size << std::endl; } catch (const std::filesystem::filesystem_error& e) { std::cerr << "文件系统错误: " << e.what() << std::endl; std::cerr << "错误码: " << e.code() << std::endl; } catch (const std::exception& e) { std::cerr << "其他错误: " << e.what() << std::endl; } return 0; }
使用
try-catch
块可以捕获std::filesystem::filesystem_error
类型的异常,这个异常包含了错误信息和错误码。 -
error_code
:更细粒度的错误处理除了异常处理,
std::filesystem
还提供了error_code
来进行更细粒度的错误处理。#include <iostream> #include <filesystem> int main() { std::filesystem::path file_path = "nonexistent_file.txt"; std::error_code ec; std::uintmax_t file_size = std::filesystem::file_size(file_path, ec); if (ec) { std::cerr << "文件系统错误: " << ec.message() << std::endl; std::cerr << "错误码: " << ec.value() << std::endl; } else { std::cout << "文件大小: " << file_size << std::endl; } return 0; }
将
error_code
对象作为参数传递给文件系统函数,如果发生错误,错误信息会保存在error_code
对象中,而函数不会抛出异常。
总结:std::filesystem
的最佳实践
- 始终进行错误处理: 无论是使用异常还是
error_code
,都要确保对文件系统操作可能出现的错误进行处理,避免程序崩溃或者数据丢失。 - 小心使用
remove_all
: 这个函数会删除目录及其所有内容,要确保你的代码逻辑正确,避免误删重要数据。 - 注意权限问题: 在进行文件系统操作时,要确保程序有足够的权限,否则会抛出异常或者返回错误码。
- 考虑平台兼容性: 虽然
std::filesystem
是标准库,但在不同的操作系统上,文件系统的行为可能略有不同,需要进行适当的兼容性处理。 - 使用 RAII 风格: 使用 RAII (Resource Acquisition Is Initialization) 风格管理文件资源,确保资源在使用完毕后能够被正确释放,避免资源泄漏。 例如,使用
std::fstream
对象,在对象析构时会自动关闭文件。
表格:std::filesystem
常用函数总结
函数名 | 功能 |
---|---|
create_directory |
创建单个目录 |
create_directories |
创建多级目录 |
remove |
删除文件或空目录 |
remove_all |
删除目录及其所有内容 |
copy |
复制文件 |
rename |
重命名/移动文件或目录 |
file_size |
获取文件大小 |
is_regular_file |
判断是否是普通文件 |
is_directory |
判断是否是目录 |
is_symlink |
判断是否是符号链接 |
last_write_time |
获取最后修改时间 |
directory_iterator |
遍历当前目录 |
recursive_directory_iterator |
递归遍历所有子目录 |
create_symlink |
创建文件符号链接 |
create_directory_symlink |
创建目录符号链接 |
read_symlink |
读取符号链接的目标 |
exists |
检查路径是否存在 |
结束语:文件系统,尽在掌握
今天的 std::filesystem
深度游就到这里了。希望通过这次讲座,大家能够对 std::filesystem
有更深入的了解,掌握文件系统操作的各种技巧,并在实际开发中灵活运用。记住,文件系统是程序猿的后花园,只要我们用心呵护,就能种出我们想要的代码之花!
谢谢大家!