哈喽,各位好!
今天咱们来聊聊 C++17 引入的 std::filesystem
,这个库简直就是文件系统操作的一把瑞士军刀,让咱们在 C++ 里也能像玩泥巴一样轻松地摆弄文件和目录。
一、告别老古董:为什么我们需要 std::filesystem
?
在 C++17 之前,咱们操作文件系统,要么用 C 标准库的 stdio.h
(比如 fopen
, fclose
, fread
, fwrite
这些),要么用平台特定的 API(比如 Windows 的 CreateFile
, ReadFile
,Linux 的 open
, read
)。
这些方法问题多多:
- 平台依赖性高: 同一段代码,在 Windows 上跑得欢,到了 Linux 上就歇菜了。跨平台?不存在的。
- 错误处理繁琐: 动不动就要检查返回值,
errno
,各种宏定义,头都大了。 - 功能有限: 创建目录、遍历目录这些常见操作,实现起来都比较麻烦。
std::filesystem
的出现,就是为了解决这些痛点。它提供了一套标准的、跨平台的、面向对象的文件系统操作接口,让咱们的代码更简洁、更易维护、更具可移植性。
二、std::filesystem
的核心概念
std::filesystem
库的核心类是 std::filesystem::path
。可以把它理解为文件或目录的路径,它提供了一系列方法来操作路径,比如拼接、分解、判断存在等等。
除了 path
,还有一些其他的类,比如 file_status
(文件状态),directory_entry
(目录项),space_info
(空间信息)等等。
三、std::filesystem
的基本用法
下面咱们通过一些例子来学习 std::filesystem
的用法。
-
路径操作
#include <iostream> #include <filesystem> namespace fs = std::filesystem; // 方便使用 int main() { fs::path p1 = "/home/user/documents/report.txt"; // 绝对路径 fs::path p2 = "data/input.csv"; // 相对路径 std::cout << "Path 1: " << p1 << std::endl; std::cout << "Path 2: " << p2 << std::endl; // 拼接路径 fs::path p3 = p1 / "backup"; // 相当于 /home/user/documents/report.txt/backup std::cout << "Path 3 (拼接): " << p3 << std::endl; // 获取文件名 std::cout << "Filename: " << p1.filename() << std::endl; // report.txt // 获取父目录 std::cout << "Parent path: " << p1.parent_path() << std::endl; // /home/user/documents // 获取根目录 std::cout << "Root path: " << p1.root_path() << std::endl; // / // 获取扩展名 std::cout << "Extension: " << p1.extension() << std::endl; // .txt // 判断路径是否是绝对路径 std::cout << "Is absolute: " << p1.is_absolute() << std::endl; // true std::cout << "Is absolute: " << p2.is_absolute() << std::endl; // false return 0; }
这个例子展示了如何创建
path
对象,如何拼接路径,以及如何获取路径的各个组成部分。 注意/
操作符可以直接拼接path
对象和字符串,非常方便。 -
文件和目录的创建、删除、重命名
#include <iostream> #include <filesystem> namespace fs = std::filesystem; int main() { fs::path dir_path = "my_directory"; fs::path file_path = dir_path / "my_file.txt"; // 创建目录 if (!fs::exists(dir_path)) { if (fs::create_directory(dir_path)) { std::cout << "Directory created successfully." << std::endl; } else { std::cerr << "Failed to create directory." << std::endl; } } else { std::cout << "Directory already exists." << std::endl; } // 创建文件 (创建空文件) std::ofstream outfile(file_path.string()); // 需要转换为 string outfile.close(); std::cout << "File created successfully." << std::endl; // 重命名文件 fs::path new_file_path = dir_path / "new_file.txt"; fs::rename(file_path, new_file_path); std::cout << "File renamed successfully." << std::endl; // 删除文件 fs::remove(new_file_path); std::cout << "File deleted successfully." << std::endl; // 删除目录 (只能删除空目录,否则会抛出异常) if (fs::is_empty(dir_path)) { fs::remove(dir_path); std::cout << "Directory deleted successfully." << std::endl; } else { std::cout << "Directory is not empty, cannot delete." << std::endl; } return 0; }
这个例子展示了如何创建目录和文件,如何重命名文件,以及如何删除文件和目录。 注意:
- 创建目录需要使用
fs::create_directory
函数。 - 创建文件可以使用
std::ofstream
,也可以使用fs::create_symlink
创建符号链接。 - 删除目录只能删除空目录,如果目录不为空,需要先删除目录下的所有文件和子目录。 可以使用
fs::remove_all
来递归删除目录及其内容,但要小心使用,避免误删重要文件。 fs::remove
如果删除失败会抛出异常,建议使用try-catch
块来处理异常。
- 创建目录需要使用
-
文件和目录的判断
#include <iostream> #include <filesystem> namespace fs = std::filesystem; int main() { fs::path p = "test_file.txt"; // 判断文件是否存在 if (fs::exists(p)) { std::cout << "File exists." << std::endl; } else { std::cout << "File does not exist." << std::endl; } // 判断是否是文件 if (fs::is_regular_file(p)) { std::cout << "It's a regular file." << std::endl; } else { std::cout << "It's not a regular file." << std::endl; } // 判断是否是目录 if (fs::is_directory(p)) { std::cout << "It's a directory." << std::endl; } else { std::cout << "It's not a directory." << std::endl; } // 判断是否是符号链接 if (fs::is_symlink(p)) { std::cout << "It's a symbolic link." << std::endl; } else { std::cout << "It's not a symbolic link." << std::endl; } return 0; }
这个例子展示了如何判断文件或目录是否存在,以及如何判断文件类型。
-
文件大小、最后修改时间
#include <iostream> #include <filesystem> #include <chrono> #include <ctime> namespace fs = std::filesystem; int main() { fs::path p = "example.txt"; // 创建一个示例文件 std::ofstream outfile(p.string()); outfile << "Hello, world!"; outfile.close(); // 获取文件大小 std::uintmax_t file_size = fs::file_size(p); std::cout << "File size: " << file_size << " bytes" << std::endl; // 获取最后修改时间 auto last_write_time = fs::last_write_time(p); std::time_t cftime = std::chrono::system_clock::to_time_t(last_write_time); std::cout << "Last write time: " << std::ctime(&cftime) << std::endl; // 删除示例文件 fs::remove(p); return 0; }
这个例子展示了如何获取文件的大小和最后修改时间。 注意:
last_write_time
返回的是file_time_type
类型,需要转换为std::time_t
才能格式化输出。 -
目录遍历
#include <iostream> #include <filesystem> namespace fs = std::filesystem; int main() { fs::path dir_path = "."; // 当前目录 // 遍历目录 for (const auto& entry : fs::directory_iterator(dir_path)) { std::cout << entry.path() << std::endl; // 获取文件类型 if (fs::is_regular_file(entry.path())) { std::cout << " - Regular file" << std::endl; } else if (fs::is_directory(entry.path())) { std::cout << " - Directory" << std::endl; } } // 递归遍历目录 std::cout << "nRecursive directory iteration:" << std::endl; for (const auto& entry : fs::recursive_directory_iterator(dir_path)) { std::cout << entry.path() << std::endl; } return 0; }
这个例子展示了如何遍历目录,可以使用
fs::directory_iterator
遍历目录下的直接子项,也可以使用fs::recursive_directory_iterator
递归遍历目录下的所有子项。
四、文件状态 (file_status
)
std::filesystem::file_status
类用于表示文件或目录的状态信息。可以使用 fs::status
函数获取文件状态。
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path p = "example.txt";
// 创建一个示例文件
std::ofstream outfile(p.string());
outfile << "Hello, world!";
outfile.close();
fs::file_status status = fs::status(p);
// 判断文件类型
if (fs::is_regular_file(status)) {
std::cout << "It's a regular file." << std::endl;
} else if (fs::is_directory(status)) {
std::cout << "It's a directory." << std::endl;
} else if (fs::is_symlink(status)) {
std::cout << "It's a symbolic link." << std::endl;
} else {
std::cout << "It's something else." << std::endl;
}
// 获取权限信息 (平台相关)
// 注意:权限信息的具体含义和表示方式在不同操作系统上可能不同。
std::cout << "Permissions: " << static_cast<int>(status.permissions()) << std::endl;
// 删除示例文件
fs::remove(p);
return 0;
}
file_status
可以用来判断文件类型,也可以获取权限信息。 注意:权限信息的具体含义和表示方式在不同操作系统上可能不同,所以在使用权限信息时需要小心处理。
五、空间信息 (space_info
)
std::filesystem::space_info
类用于表示磁盘空间信息。可以使用 fs::space
函数获取磁盘空间信息。
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path p = "/"; // 根目录
fs::space_info space = fs::space(p);
std::cout << "Capacity: " << space.capacity << " bytes" << std::endl;
std::cout << "Free: " << space.free << " bytes" << std::endl;
std::cout << "Available: " << space.available << " bytes" << std::endl;
return 0;
}
space_info
可以用来获取磁盘的总容量、可用空间和空闲空间。
六、异常处理
std::filesystem
的很多函数在出错时会抛出异常,比如 fs::create_directory
在创建目录失败时会抛出异常,fs::remove
在删除文件失败时会抛出异常。
建议使用 try-catch
块来处理这些异常,避免程序崩溃。
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path dir_path = "my_directory";
try {
fs::create_directory(dir_path);
std::cout << "Directory created successfully." << std::endl;
} catch (const fs::filesystem_error& e) {
std::cerr << "Failed to create directory: " << e.what() << std::endl;
}
return 0;
}
七、线程安全
std::filesystem
的线程安全性取决于具体的实现和操作系统。一般来说,多个线程可以同时访问不同的文件,但是如果多个线程同时访问同一个文件,可能会导致数据竞争和未定义行为。
建议使用互斥锁等同步机制来保护共享的文件资源。
八、与其他库的结合
std::filesystem
可以和其他库结合使用,比如:
std::fstream
: 可以使用std::fstream
来读写文件内容。boost::filesystem
: 如果需要使用 C++17 之前的编译器,可以使用boost::filesystem
库,它提供了类似的功能。
九、总结
std::filesystem
是 C++17 中一个非常强大的库,它提供了一套标准的、跨平台的、面向对象的文件系统操作接口,让咱们可以更方便地操作文件和目录。 掌握 std::filesystem
,可以大大提高咱们的开发效率,写出更简洁、更易维护、更具可移植性的代码。
十、一些建议和注意事项
- 错误处理: 务必进行错误处理,
std::filesystem
操作可能会抛出异常。 - 权限问题: 注意程序运行时的权限,确保程序有足够的权限进行文件系统操作。
- 路径分隔符: 尽量使用
/
作为路径分隔符,std::filesystem
会自动将其转换为平台特定的分隔符。 - 符号链接: 在处理符号链接时要小心,避免死循环或者安全问题。
- 性能: 频繁的文件系统操作可能会影响性能,需要根据实际情况进行优化。
- 跨平台: 虽然
std::filesystem
提供了跨平台的能力,但仍然有一些平台相关的差异,需要注意。
十一、一些常用函数表格整理
函数名 | 功能 |
---|---|
fs::path |
创建路径对象 |
path::operator/ |
拼接路径 |
path::filename() |
获取文件名 |
path::parent_path() |
获取父目录 |
path::extension() |
获取扩展名 |
fs::exists(path) |
判断路径是否存在 |
fs::is_regular_file(path) |
判断是否是普通文件 |
fs::is_directory(path) |
判断是否是目录 |
fs::create_directory(path) |
创建目录 |
fs::remove(path) |
删除文件或空目录 |
fs::remove_all(path) |
递归删除目录及其内容 |
fs::file_size(path) |
获取文件大小 |
fs::last_write_time(path) |
获取最后修改时间 |
fs::directory_iterator(path) |
遍历目录下的直接子项 |
fs::recursive_directory_iterator(path) |
递归遍历目录下的所有子项 |
fs::status(path) |
获取文件状态 |
fs::space(path) |
获取磁盘空间信息 |
fs::copy(from, to) |
复制文件或者目录(可以选择复制选项,如覆盖、递归复制等) |
fs::equivalent(path1, path2) |
检查两个路径是否指向同一个文件或目录(通过比较设备ID和inode号等) |
fs::canonical(path) |
返回路径的绝对路径,解析所有符号链接和. 和.. 等相对路径组件,并移除冗余的分隔符。 |
fs::relative(path, base) |
返回一个从base 路径到path 路径的相对路径. |
fs::current_path() |
获取当前工作目录的路径 |
fs::absolute(path) |
返回路径的绝对路径. 如果path 已经是绝对路径,则直接返回path . 否则,将path 与当前工作目录连接起来,形成绝对路径。 |
希望今天的分享能帮助大家更好地理解和使用 std::filesystem
库。 下次再见!