跳到主要内容

C++ 17文件系统

引言

在C++17之前,C++标准库并没有提供直接处理文件系统的功能,开发者不得不依赖系统特定的API或第三方库来操作文件和目录。C++17引入了<filesystem>库,它是基于Boost.Filesystem设计的,提供了一套平台无关的文件系统操作接口,使文件和目录操作变得简单而统一。

本文将介绍std::filesystem命名空间中的主要组件和功能,帮助你理解如何在C++17中进行文件系统操作。

准备工作

首先,要使用C++17的文件系统功能,你需要:

  1. 支持C++17的编译器(如GCC 8+,Clang 7+,MSVC 2017 15.7+)
  2. 在你的程序中包含文件系统头文件:
cpp
#include <filesystem>

// 为方便使用,可以添加命名空间别名
namespace fs = std::filesystem;
备注

在某些编译器上,你可能需要链接文件系统库。例如,在GCC上,需要添加-lstdc++fs链接选项。

核心概念:路径(path)

std::filesystem::path是文件系统库的核心类,它表示文件系统中的路径。

基本用法

cpp
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main() {
// 创建路径对象
fs::path myPath = "C:/Users/Documents/example.txt";

// 输出路径信息
std::cout << "完整路径: " << myPath << std::endl;
std::cout << "文件名: " << myPath.filename() << std::endl;
std::cout << "文件名(无扩展名): " << myPath.stem() << std::endl;
std::cout << "扩展名: " << myPath.extension() << std::endl;
std::cout << "父路径: " << myPath.parent_path() << std::endl;

return 0;
}

输出:

完整路径: "C:/Users/Documents/example.txt"
文件名: "example.txt"
文件名(无扩展名): "example"
扩展名: ".txt"
父路径: "C:/Users/Documents"

路径操作

path类提供了丰富的路径操作方法:

cpp
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main() {
fs::path p1 = "/home/user";
fs::path p2 = "documents";
fs::path p3 = "file.txt";

// 路径拼接
fs::path combined = p1 / p2 / p3; // 注意使用 / 运算符进行路径拼接
std::cout << "拼接路径: " << combined << std::endl;

// 路径分解
for(const auto& part : combined) {
std::cout << "路径部分: " << part << std::endl;
}

return 0;
}

输出:

拼接路径: "/home/user/documents/file.txt"
路径部分: "/"
路径部分: "home"
路径部分: "user"
路径部分: "documents"
路径部分: "file.txt"

文件和目录操作

检查文件或目录是否存在

cpp
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main() {
fs::path p = "example.txt";

if(fs::exists(p)) {
std::cout << p << " 存在" << std::endl;
} else {
std::cout << p << " 不存在" << std::endl;
}

return 0;
}

创建目录

cpp
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main() {
fs::path dir = "new_directory";

if(fs::create_directory(dir)) {
std::cout << "目录创建成功" << std::endl;
} else {
std::cout << "目录创建失败" << std::endl;
}

// 创建嵌套目录
fs::path nested_dir = "parent/child/grandchild";
if(fs::create_directories(nested_dir)) {
std::cout << "嵌套目录创建成功" << std::endl;
}

return 0;
}

迭代目录内容

cpp
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main() {
fs::path dirPath = "."; // 当前目录

std::cout << "当前目录内容:" << std::endl;

// 使用目录迭代器遍历目录内容
for(const auto& entry : fs::directory_iterator(dirPath)) {
std::cout << entry.path().filename() << std::endl;

if(fs::is_regular_file(entry)) {
std::cout << " (文件, 大小: " << fs::file_size(entry) << " 字节)" << std::endl;
}
else if(fs::is_directory(entry)) {
std::cout << " (目录)" << std::endl;
}
}

return 0;
}

递归迭代目录

如果要递归地遍历目录结构,可以使用recursive_directory_iterator

cpp
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main() {
fs::path dirPath = "."; // 当前目录

std::cout << "目录树:" << std::endl;

// 使用递归目录迭代器
for(const auto& entry : fs::recursive_directory_iterator(dirPath)) {
// 计算缩进层级
int depth = entry.depth();
std::string indent(depth * 2, ' ');

std::cout << indent << entry.path().filename();

if(fs::is_directory(entry)) {
std::cout << " (目录)";
}
std::cout << std::endl;
}

return 0;
}

复制、移动和删除

cpp
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main() {
// 复制文件
fs::path source = "source.txt";
fs::path destination = "destination.txt";

try {
fs::copy_file(source, destination, fs::copy_options::overwrite_existing);
std::cout << "文件复制成功" << std::endl;
} catch(const fs::filesystem_error& e) {
std::cerr << "文件复制错误: " << e.what() << std::endl;
}

// 移动文件
fs::path from = "old_name.txt";
fs::path to = "new_name.txt";

try {
fs::rename(from, to);
std::cout << "文件移动/重命名成功" << std::endl;
} catch(const fs::filesystem_error& e) {
std::cerr << "文件移动错误: " << e.what() << std::endl;
}

// 删除文件
try {
if(fs::remove(to)) {
std::cout << "文件删除成功" << std::endl;
}
} catch(const fs::filesystem_error& e) {
std::cerr << "文件删除错误: " << e.what() << std::endl;
}

// 删除目录及其内容
fs::path dirToRemove = "temp_dir";
try {
std::size_t count = fs::remove_all(dirToRemove);
std::cout << "已删除 " << count << " 个文件/目录" << std::endl;
} catch(const fs::filesystem_error& e) {
std::cerr << "目录删除错误: " << e.what() << std::endl;
}

return 0;
}

文件状态和属性

可以查询文件的各种属性和状态:

cpp
#include <iostream>
#include <filesystem>
#include <chrono>
namespace fs = std::filesystem;

int main() {
fs::path p = "example.txt";

if(fs::exists(p)) {
// 文件大小
std::cout << "文件大小: " << fs::file_size(p) << " 字节" << std::endl;

// 文件类型
if(fs::is_regular_file(p)) {
std::cout << "是普通文件" << std::endl;
}
if(fs::is_directory(p)) {
std::cout << "是目录" << std::endl;
}
if(fs::is_symlink(p)) {
std::cout << "是符号链接" << std::endl;
}

// 获取最后修改时间
auto ftime = fs::last_write_time(p);
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
std::time_t cftime = std::chrono::system_clock::to_time_t(sctp);
std::cout << "最后修改时间: " << std::ctime(&cftime);

// 权限
fs::perms p_perms = fs::status(p).permissions();
std::cout << "文件权限: ";
std::cout << ((p_perms & fs::perms::owner_read) != fs::perms::none ? "r" : "-");
std::cout << ((p_perms & fs::perms::owner_write) != fs::perms::none ? "w" : "-");
std::cout << ((p_perms & fs::perms::owner_exec) != fs::perms::none ? "x" : "-");
std::cout << std::endl;
}

return 0;
}

实际应用案例

案例1:创建文件备份工具

下面是一个简单的目录备份工具,它会复制源目录的所有内容到目标目录:

cpp
#include <iostream>
#include <filesystem>
#include <string>
namespace fs = std::filesystem;

void backup_directory(const fs::path& source, const fs::path& destination) {
try {
// 确保源目录存在
if (!fs::exists(source) || !fs::is_directory(source)) {
std::cerr << "源目录不存在或不是一个目录" << std::endl;
return;
}

// 创建目标目录(如果不存在)
if (!fs::exists(destination)) {
fs::create_directories(destination);
}

// 复制所有文件和子目录
for (const auto& entry : fs::recursive_directory_iterator(source)) {
// 构建相对路径
fs::path relative = fs::relative(entry.path(), source);
fs::path dest = destination / relative;

if (fs::is_directory(entry)) {
fs::create_directories(dest);
} else if (fs::is_regular_file(entry)) {
fs::copy_file(entry.path(), dest, fs::copy_options::overwrite_existing);
std::cout << "已复制: " << relative << std::endl;
}
}

std::cout << "备份完成!" << std::endl;
} catch (const fs::filesystem_error& e) {
std::cerr << "备份过程中出错: " << e.what() << std::endl;
}
}

int main(int argc, char* argv[]) {
if (argc != 3) {
std::cout << "用法: " << argv[0] << " <源目录> <目标目录>" << std::endl;
return 1;
}

fs::path source = argv[1];
fs::path destination = argv[2];

backup_directory(source, destination);

return 0;
}

案例2:磁盘空间分析器

这个程序会计算目录及其所有子目录的总大小,并按照从大到小的顺序显示:

cpp
#include <iostream>
#include <filesystem>
#include <vector>
#include <algorithm>
#include <iomanip>
namespace fs = std::filesystem;

struct DirInfo {
fs::path path;
uintmax_t size;
};

// 计算目录大小
uintmax_t calculate_directory_size(const fs::path& dir_path) {
uintmax_t size = 0;

for (const auto& entry : fs::recursive_directory_iterator(dir_path)) {
if (fs::is_regular_file(entry)) {
size += fs::file_size(entry);
}
}

return size;
}

// 格式化大小为可读形式
std::string format_size(uintmax_t bytes) {
static const char* units[] = {"B", "KB", "MB", "GB", "TB"};
int unit_index = 0;
double size = static_cast<double>(bytes);

while (size >= 1024.0 && unit_index < 4) {
size /= 1024.0;
unit_index++;
}

std::stringstream ss;
ss << std::fixed << std::setprecision(2) << size << " " << units[unit_index];
return ss.str();
}

int main(int argc, char* argv[]) {
fs::path root_dir = (argc > 1) ? argv[1] : fs::current_path();
std::vector<DirInfo> directories;

std::cout << "分析目录: " << root_dir << std::endl;

try {
// 收集第一级子目录信息
for (const auto& entry : fs::directory_iterator(root_dir)) {
if (fs::is_directory(entry)) {
DirInfo info;
info.path = entry.path();

std::cout << "计算 " << entry.path().filename() << " 的大小..." << std::endl;
info.size = calculate_directory_size(entry.path());

directories.push_back(info);
}
}

// 按大小排序
std::sort(directories.begin(), directories.end(),
[](const DirInfo& a, const DirInfo& b) { return a.size > b.size; });

// 显示结果
std::cout << "\n目录大小分析结果 (从大到小):\n" << std::endl;
std::cout << std::left << std::setw(50) << "目录" << "大小" << std::endl;
std::cout << std::string(60, '-') << std::endl;

for (const auto& dir : directories) {
std::cout << std::left << std::setw(50) << dir.path.filename().string()
<< format_size(dir.size) << std::endl;
}
} catch (const fs::filesystem_error& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}

return 0;
}

错误处理

文件系统操作可能会失败,所以良好的错误处理是必要的。std::filesystem库中的函数通常有两种形式:一种抛出异常,另一种返回错误码。

cpp
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main() {
fs::path p = "nonexistent_file.txt";

// 方式1:使用try-catch捕获异常
try {
fs::file_size(p); // 如果文件不存在会抛出异常
} catch (const fs::filesystem_error& e) {
std::cerr << "异常: " << e.what() << std::endl;
std::cerr << "错误码: " << e.code() << std::endl;
}

// 方式2:使用接受error_code参数的版本
std::error_code ec;
uintmax_t size = fs::file_size(p, ec);
if (ec) {
std::cerr << "错误: " << ec.message() << std::endl;
std::cerr << "错误码: " << ec.value() << std::endl;
} else {
std::cout << "文件大小: " << size << std::endl;
}

return 0;
}

总结

C++17的文件系统库提供了一套强大而统一的文件和目录操作接口,它是C++标准库的一个重要扩展,解决了长期以来C++在文件系统操作方面的不足。通过std::filesystem,你可以:

  • 处理文件路径(拼接、分解、规范化)
  • 检查文件存在性和属性
  • 创建、复制、移动和删除文件和目录
  • 迭代目录内容
  • 查询文件系统信息

这些功能在跨平台程序开发中特别有用,因为它们抽象了不同操作系统之间的文件系统差异,让你的代码可以在各种平台上工作,无需为每个平台编写特定的代码。

练习

  1. 编写一个程序,递归地列出指定目录中所有的文本文件(*.txt)。
  2. 创建一个简单的文件管理工具,支持浏览目录、创建文件/目录、删除文件/目录等功能。
  3. 实现一个程序,找出并删除指定目录中的所有空目录。
  4. 开发一个文件同步工具,能够比较两个目录的内容并同步差异。
  5. 编写一个程序,计算一个项目代码库中各种文件类型的数量和总大小(如.cpp、.h等)。

额外资源