好的,各位听众,大家好!今天我们来聊聊C++标准库里一个非常实用,但又经常被忽略的家伙——std::filesystem
。 别害怕,虽然名字听起来像个复杂的操作系统内核模块,但实际上它只是一个帮你轻松搞定各种文件系统操作的工具箱。
开场白:为什么我们需要std::filesystem
?
在C++17之前,如果你想在代码里操作文件,比如创建目录、读取文件大小、判断文件是否存在,那你可能需要用到一些平台相关的API,比如Windows下的CreateDirectory
和Linux下的mkdir
。 这就意味着你的代码必须针对不同的操作系统进行编译和修改,简直是噩梦!
std::filesystem
横空出世,就是为了解决这个问题。它提供了一套跨平台的API,让你用一套代码就能在不同的操作系统上执行文件系统操作。 简直是程序员的福音!
std::filesystem
的核心概念
要理解std::filesystem
,我们需要先了解几个核心概念:
path
: 这是std::filesystem
里最重要的类,它代表文件系统中的路径。 路径可以是绝对路径(比如/home/user/documents
)或者相对路径(比如./data.txt
)。file_status
: 这个类描述了文件或目录的状态,比如它是存在还是不存在,是普通文件还是目录,等等。directory_entry
: 这个类代表目录中的一个条目,可以是文件、目录或者其他类型的文件系统对象。
基本操作:牛刀小试
让我们从一些基本的文件系统操作开始,看看std::filesystem
是如何简化这些任务的。
1. 包含头文件
首先,我们需要包含filesystem
头文件:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem; // 为了方便,我们使用命名空间别名
2. 创建目录
创建目录非常简单,只需要调用fs::create_directory()
函数:
int main() {
fs::path dir_path = "my_new_directory"; // 创建一个名为 "my_new_directory" 的目录
if (!fs::exists(dir_path)) {
if (fs::create_directory(dir_path)) {
std::cout << "目录创建成功!" << std::endl;
} else {
std::cerr << "目录创建失败!" << std::endl;
}
} else {
std::cout << "目录已经存在!" << std::endl;
}
return 0;
}
3. 判断文件/目录是否存在
fs::exists()
函数可以判断文件或目录是否存在:
fs::path file_path = "my_file.txt";
if (fs::exists(file_path)) {
std::cout << "文件存在!" << std::endl;
} else {
std::cout << "文件不存在!" << std::endl;
}
4. 获取文件大小
fs::file_size()
函数可以获取文件的大小(以字节为单位):
fs::path file_path = "my_file.txt";
if (fs::exists(file_path)) {
std::uintmax_t file_size = fs::file_size(file_path);
std::cout << "文件大小:" << file_size << " 字节" << std::endl;
} else {
std::cout << "文件不存在!" << std::endl;
}
5. 删除文件/目录
fs::remove()
函数可以删除文件或目录。注意,如果目录不为空,remove()
函数会失败。 如果要删除非空目录,需要使用fs::remove_all()
函数:
fs::path file_path = "my_file.txt";
if (fs::exists(file_path)) {
if (fs::remove(file_path)) {
std::cout << "文件删除成功!" << std::endl;
} else {
std::cerr << "文件删除失败!" << std::endl;
}
} else {
std::cout << "文件不存在!" << std::endl;
}
fs::path dir_path = "my_new_directory";
if (fs::exists(dir_path)) {
if (fs::remove_all(dir_path)) {
std::cout << "目录删除成功!" << std::endl;
} else {
std::cerr << "目录删除失败!" << std::endl;
}
} else {
std::cout << "目录不存在!" << std::endl;
}
6. 文件复制
fs::copy()
函数可以复制文件,它有多种重载形式,可以控制复制的行为,例如是否覆盖已存在的文件。
fs::path source_file = "source.txt";
fs::path destination_file = "destination.txt";
try {
fs::copy(source_file, destination_file, fs::copy_options::overwrite_existing);
std::cout << "文件复制成功!" << std::endl;
} catch (const fs::filesystem_error& e) {
std::cerr << "文件复制失败:" << e.what() << std::endl;
}
进阶操作:探索文件系统
除了基本操作,std::filesystem
还提供了一些更高级的功能,比如遍历目录、获取文件状态等。
1. 遍历目录
fs::directory_iterator
可以用来遍历目录中的所有条目:
fs::path dir_path = "."; // 当前目录
for (const auto& entry : fs::directory_iterator(dir_path)) {
std::cout << entry.path() << std::endl;
}
fs::recursive_directory_iterator
可以递归地遍历目录及其子目录:
fs::path dir_path = "."; // 当前目录
for (const auto& entry : fs::recursive_directory_iterator(dir_path)) {
std::cout << entry.path() << std::endl;
}
2. 获取文件状态
fs::status()
函数可以获取文件或目录的状态:
fs::path file_path = "my_file.txt";
fs::file_status status = fs::status(file_path);
if (fs::exists(status)) {
if (fs::is_regular_file(status)) {
std::cout << "这是一个普通文件" << std::endl;
} else if (fs::is_directory(status)) {
std::cout << "这是一个目录" << std::endl;
} else {
std::cout << "这是一个其他类型的文件系统对象" << std::endl;
}
} else {
std::cout << "文件不存在" << std::endl;
}
3. 获取路径信息
path
类提供了一些方法来获取路径的各个部分:
filename()
: 获取文件名(不包含路径)parent_path()
: 获取父目录路径extension()
: 获取文件扩展名stem()
: 获取文件名(不包含扩展名)is_absolute()
: 判断是否是绝对路径
fs::path file_path = "/home/user/documents/my_file.txt";
std::cout << "文件名: " << file_path.filename() << std::endl;
std::cout << "父目录: " << file_path.parent_path() << std::endl;
std::cout << "扩展名: " << file_path.extension() << std::endl;
std::cout << "文件名 (不含扩展名): " << file_path.stem() << std::endl;
std::cout << "是否是绝对路径: " << file_path.is_absolute() << std::endl;
4. 创建符号链接
fs::create_symlink()
函数可以创建符号链接。
fs::path target_file = "existing_file.txt";
fs::path symlink = "link_to_file.txt";
try {
fs::create_symlink(target_file, symlink);
std::cout << "符号链接创建成功!" << std::endl;
} catch (const fs::filesystem_error& e) {
std::cerr << "符号链接创建失败:" << e.what() << std::endl;
}
5. 获取最后修改时间
fs::last_write_time()
函数可以获取文件的最后修改时间。
fs::path file_path = "my_file.txt";
try {
auto last_write_time = fs::last_write_time(file_path);
std::time_t cftime = std::chrono::system_clock::to_time_t(last_write_time);
std::cout << "最后修改时间:" << std::ctime(&cftime) << std::endl;
} catch (const fs::filesystem_error& e) {
std::cerr << "获取最后修改时间失败:" << e.what() << std::endl;
}
异常处理:小心驶得万年船
文件系统操作可能会失败,比如文件不存在、权限不足等等。 std::filesystem
使用异常来报告错误。 因此,在使用std::filesystem
时,一定要进行异常处理,否则程序可能会崩溃。
try {
fs::path file_path = "non_existent_file.txt";
fs::file_size(file_path); // 可能会抛出异常
} catch (const fs::filesystem_error& e) {
std::cerr << "文件系统错误:" << e.what() << std::endl;
}
路径操作:让路径更灵活
std::filesystem::path
类不仅仅是一个简单的字符串,它还提供了一些方法来操作路径:
append()
或/=
: 追加路径remove_filename()
: 移除文件名replace_extension()
: 替换扩展名make_preferred()
: 转换为当前操作系统偏好的路径格式
fs::path base_path = "/home/user";
base_path /= "documents"; // 追加路径
base_path /= "my_file.txt";
std::cout << "完整路径: " << base_path << std::endl;
base_path.remove_filename(); // 移除文件名
std::cout << "移除文件名后的路径: " << base_path << std::endl;
base_path /= "another_file.txt";
base_path.replace_extension(".cpp"); // 替换扩展名
std::cout << "替换扩展名后的路径: " << base_path << std::endl;
#ifdef _WIN32
base_path.make_preferred(); // 在Windows上将 / 转换为
std::cout << "转换为Windows偏好格式后的路径: " << base_path << std::endl;
#endif
权限管理:(C++20新增)
C++20 引入了权限管理,通过 fs::permissions()
函数可以设置和查询文件权限。
#if __cpp_lib_file_permissions >= 201707L // 检查编译器是否支持文件权限
fs::path file_path = "my_file.txt";
try {
// 设置文件权限为所有者读写,同组用户只读,其他用户无权限
fs::permissions(file_path,
fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read,
fs::perm_options::replace);
// 获取文件权限
fs::perms current_permissions = fs::status(file_path).permissions();
std::cout << "文件权限: " << std::hex << static_cast<int>(current_permissions) << std::endl;
} catch (const fs::filesystem_error& e) {
std::cerr << "权限设置失败:" << e.what() << std::endl;
}
#else
std::cout << "当前编译器不支持文件权限管理。" << std::endl;
#endif
总结:std::filesystem
的优势
- 跨平台: 一套代码,到处运行。
- 易于使用: API简洁明了,容易上手。
- 安全: 使用异常处理,避免程序崩溃。
- 功能强大: 提供丰富的文件系统操作功能。
注意事项
- 权限: 文件系统操作需要相应的权限。
- 路径分隔符: 不同操作系统使用不同的路径分隔符(Windows:
,Linux/macOS:
/
)。std::filesystem
会自动处理这些差异。 - 符号链接: 处理符号链接时要小心,避免循环引用。
- 并发: 多个线程同时操作文件系统可能会导致问题,需要进行同步。
实战案例:日志文件管理
让我们用一个实际的例子来展示std::filesystem
的威力。 假设我们需要编写一个日志文件管理程序,它可以:
- 每天创建一个新的日志文件。
- 如果日志文件超过一定大小,就进行归档。
- 定期删除过期的日志文件。
#include <iostream>
#include <fstream>
#include <filesystem>
#include <chrono>
#include <ctime>
namespace fs = std::filesystem;
// 配置参数
const std::string log_directory = "logs";
const std::uintmax_t max_log_file_size = 1024 * 1024; // 1MB
const int log_file_retention_days = 30;
// 获取当前日期字符串 (YYYY-MM-DD)
std::string get_current_date_string() {
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm now_tm;
localtime_r(&now_c, &now_tm); // 使用线程安全的 localtime_r
char date_str[11]; // YYYY-MM-DD
strftime(date_str, sizeof(date_str), "%Y-%m-%d", &now_tm);
return std::string(date_str);
}
// 获取当前日志文件路径
fs::path get_current_log_file_path() {
return fs::path(log_directory) / (get_current_date_string() + ".log");
}
// 归档日志文件
void archive_log_file(const fs::path& log_file_path) {
fs::path archive_path = fs::path(log_directory) / "archive";
fs::create_directory(archive_path); // 创建 archive 目录
fs::path archived_file_path = archive_path / (log_file_path.filename().string() + ".gz");
// 使用系统命令进行压缩 (需要系统支持 gzip)
std::string command = "gzip "" + log_file_path.string() + "" -c > "" + archived_file_path.string() + """;
int result = system(command.c_str()); // 执行系统命令
if (result == 0) {
fs::remove(log_file_path); // 删除原始日志文件
std::cout << "日志文件 " << log_file_path << " 归档成功." << std::endl;
} else {
std::cerr << "日志文件 " << log_file_path << " 归档失败." << std::endl;
}
}
// 删除过期的日志文件
void delete_old_log_files() {
auto now = std::chrono::system_clock::now();
auto retention_duration = std::chrono::hours(24 * log_file_retention_days);
auto cutoff_time = now - retention_duration;
for (const auto& entry : fs::directory_iterator(log_directory)) {
if (fs::is_regular_file(entry.status()) && entry.path().extension() == ".log") {
try {
auto last_write_time = fs::last_write_time(entry.path());
if (last_write_time <= cutoff_time) {
fs::remove(entry.path());
std::cout << "删除过期日志文件: " << entry.path() << std::endl;
}
} catch (const fs::filesystem_error& e) {
std::cerr << "删除日志文件 " << entry.path() << " 失败: " << e.what() << std::endl;
}
}
}
// 删除 archive 目录下的过期归档文件
fs::path archive_path = fs::path(log_directory) / "archive";
if (fs::exists(archive_path) && fs::is_directory(archive_path)) {
for (const auto& entry : fs::directory_iterator(archive_path)) {
if (fs::is_regular_file(entry.status()) && entry.path().extension() == ".gz") {
try {
auto last_write_time = fs::last_write_time(entry.path());
if (last_write_time <= cutoff_time) {
fs::remove(entry.path());
std::cout << "删除过期归档文件: " << entry.path() << std::endl;
}
} catch (const fs::filesystem_error& e) {
std::cerr << "删除归档文件 " << entry.path() << " 失败: " << e.what() << std::endl;
}
}
}
}
}
int main() {
// 创建日志目录
fs::create_directory(log_directory);
// 获取当前日志文件路径
fs::path current_log_file_path = get_current_log_file_path();
// 检查日志文件大小,如果超过限制则进行归档
if (fs::exists(current_log_file_path) && fs::file_size(current_log_file_path) > max_log_file_size) {
archive_log_file(current_log_file_path);
}
// 写入日志信息
std::ofstream log_file(current_log_file_path, std::ios::app);
if (log_file.is_open()) {
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm now_tm;
localtime_r(&now_c, &now_tm);
char timestamp[26]; // YYYY-MM-DD HH:MM:SS.milliseconds
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &now_tm);
log_file << "[" << timestamp << "] Log message: This is a test log message." << std::endl;
log_file.close();
std::cout << "写入日志信息到 " << current_log_file_path << std::endl;
} else {
std::cerr << "无法打开日志文件 " << current_log_file_path << std::endl;
}
// 删除过期的日志文件
delete_old_log_files();
return 0;
}
这个例子展示了如何使用std::filesystem
来管理日志文件,包括创建目录、获取文件大小、删除文件等等。
总结
std::filesystem
是一个非常强大的工具,它可以让你轻松地进行跨平台的文件系统操作。 掌握它,你的C++代码将会更加健壮、易于维护,并且更具有可移植性。 希望今天的讲解对你有所帮助! 谢谢大家!