平时使用电脑时,我们习惯说 “文件夹”,而在 C++ 的 <filesystem> 标准库中则统一称为 “目录(directory)”。其实这两个词指的是同一个东西。
在 C++ 中,我们通常先将 std::filesystem 简写为别名 fs。新建和删除文件夹主要用到以下 4 个函数,它们的职责分工如下表所示。
| 函数 | 主要用途 | 适用条件与范围 |
|---|---|---|
| fs::create_directory(path) | 创建单层目录 | 父目录必须已存在,只创建最后这一层目录 |
| fs::create_directories(path) | 创建多层嵌套目录 | 如果中间的父目录不存在,会自动一层层全部创建 |
| fs::remove(path) | 删除空目录或单个文件 | 用于文件夹时只能删除空目录,非空目录会报错失败 |
| fs::remove_all(path) | 递归删除整个目录树 | 将该目录及其内部所有子目录和文件一次性全部删除 |
简单来说,新建文件夹时主要看 “要不要自动补齐缺失的父目录”,删除文件夹时主要看 “删的是空文件夹,还是非空文件夹”。把这两点分清楚,我们就能轻松选对函数了。
创建文件夹
在 <filesystem> 库中,新建文件夹分为创建 “单层目录” 和创建 “多层嵌套目录” 两种情况。
1. 创建单层目录:create_directory()
当外层的父目录已经存在,我们只想在它下面新建一层文件夹时,可以调用 fs::create_directory() 函数。
语法:
fs::create_directory(path)说明:
fs::create_directory() 接收要创建的目录路径 path,我们既可以传入 fs::path 对象,也可以直接传入路径字符串。它的返回值规则如下:
- 目录原本不存在且新建成功:返回
true。 - 同名目录本来就已经存在:返回
false,并且不会报错。 - 创建过程失败:例如上一级父目录不存在、已存在同名普通文件,或者没有写入权限,此时函数会直接抛出异常。
示例 1:创建单层目录
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path dir = "logs";
bool first = fs::create_directory(dir); // 第 1 次创建:原本不存在
bool second = fs::create_directory(dir); // 第 2 次创建:目录已存在
std::cout << std::boolalpha;
std::cout << "第 1 次创建结果:" << first << '\n';
std::cout << "第 2 次创建结果:" << second << '\n';
return 0;
}运行结果如下。
第 1 次创建结果:true
第 2 次创建结果:false分析:
在这个例子中,第 1 次调用 fs::create_directory(dir) 时,当前目录下还没有 logs 文件夹。程序真正新建了这个目录,因此返回 true。
第 2 次对同一个路径调用时,因为 logs 刚才已经建好了,本次并没有新建任何目录,所以返回 false。这里小伙伴们要注意,返回 false 并不代表执行出错,程序既不会报错,也不会清空该文件夹里原有的文件。
2. 创建多层目录:create_directories()
fs::create_directory() 只负责新建最后那一层的文件夹。如果我们要创建像 "project/data/cache" 这样的多层路径,而外层的 project 和 data 还不存在,直接调用它就会因为找不到父目录而报错。
遇到需要连续创建多层文件夹的情况,我们应该使用 fs::create_directories() 函数。大家看函数名就能发现规律:末尾使用的是复数形式 directories,也就是把单数 directory 末尾的 y 改成了 ies。
语法:
fs::create_directories(path)说明:
fs::create_directories() 会从左往右依次检查路径中的每一层目录,缺哪一层就自动补建哪一层。当最终目标目录在本次调用中被新建出来时返回 true;如果整条路径原本就已经存在,则返回 false 且不会报错。
示例 2:创建多层嵌套目录
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path target = fs::path("project") / "data" / "cache";
bool created = fs::create_directories(target);
bool exists = fs::is_directory(target);
std::cout << std::boolalpha;
std::cout << "是否新建了目录:" << created << '\n';
std::cout << "目标目录是否存在:" << exists << '\n';
return 0;
}运行结果如下。
是否新建了目录:true
目标目录是否存在:true分析:
在上面的代码中,fs::create_directories(target) 根据路径 "project/data/cache",依次创建了 project、data 和 cache 三层文件夹。随后我们调用 fs::is_directory() 检查路径时,返回结果为 true。
在实际开发中,只要不确定外层父目录是否已经存在,我们都更推荐直接调用 fs::create_directories()。比如在向 "data/logs/app.log" 写入数据之前,可以先执行一次 fs::create_directories("data/logs"):目录缺失时它会自动补齐,目录已存在时也会完整保留里面的原有文件。
删除文件夹
删除文件夹时,C++ 严格区分了 “删除空目录” 和 “删除包含内容的非空目录”,分别对应 fs::remove() 和 fs::remove_all() 两个函数。
1. 删除空目录:remove()
在上一节 “C++ 复制与删除文件” 中,我们已经使用过 fs::remove() 来删除单个普通文件。实际上,同一个函数也可以用来删除一个 “空文件夹”。
语法:
fs::remove(path)说明:
当 path 指向一个没有任何文件或子目录的空文件夹时,调用 fs::remove(path) 会将其删除并返回 true。如果该文件夹原本就不存在,则返回 false 且不会报错。需要注意,如果文件夹不是空的,调用 fs::remove() 就会直接报错失败。
示例 3:删除空目录
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path dir = "empty_dir";
fs::create_directory(dir); // 先新建一个空目录
std::cout << std::boolalpha;
std::cout << "删除前是否存在:" << fs::exists(dir) << '\n';
bool removed = fs::remove(dir); // 删除空目录
std::cout << "是否成功删除:" << removed << '\n';
std::cout << "删除后是否存在:" << fs::exists(dir) << '\n';
return 0;
}运行结果如下。
删除前是否存在:true
是否成功删除:true
删除后是否存在:false分析:
empty_dir 新建后里面没有任何内容,属于标准的空文件夹。因此我们调用 fs::remove(dir) 能够直接将其删掉,删除后再次检查路径是否存在时返回 false。
为什么要限制 fs::remove() 不能删除非空文件夹呢?这是为了防止我们在只想删除空目录或单个文件时,误把装有重要资料的整个文件夹删掉。小伙伴们如果确定要把非空文件夹连同里面的内容一起清理掉,就需要使用下面的 fs::remove_all() 函数。
2. 递归删除非空目录:remove_all()
如果一个文件夹里面还装着文件或者多层子文件夹,我们想要把这个文件夹连同里面的所有内容一次性清理干净,就需要使用 fs::remove_all() 函数。
语法:
fs::remove_all(path)说明:
fs::remove_all() 会进入目标文件夹内部,把里面的所有文件、子文件夹一层层全部删除,最后再把最外层的文件夹本身删掉。
与前面几个返回 bool 值的函数不同,fs::remove_all() 返回的是定义在 <cstdint> 头文件中的无符号大整数 std::uintmax_t,表示 “本次一共删除了多少个文件和目录”。如果目标路径原本就不存在,它会返回 0 且不会报错。
示例 4:递归删除非空目录
#include <cstdint> // 提供 std::uintmax_t
#include <filesystem>
#include <fstream>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path root = "temp_data";
// 1. 创建两层目录:temp_data 和 temp_data/sub
fs::create_directories(root / "sub");
// 2. 在子目录中创建一个普通文件
std::ofstream outFile(root / "sub" / "note.txt");
outFile << "temporary data\n";
outFile.close();
// 3. 递归删除整个 temp_data 目录树
std::uintmax_t count = fs::remove_all(root);
bool exists = fs::exists(root);
std::cout << "共删除条目数量:" << count << '\n';
std::cout << std::boolalpha;
std::cout << "temp_data 是否存在:" << exists << '\n';
return 0;
}运行结果如下。
共删除条目数量:3
temp_data 是否存在:false分析:
在这个例子中,我们先创建了根目录 temp_data 和子目录 temp_data/sub,接着在子目录中新建了一个普通文件 note.txt。
整个目录树里包含 temp_data、sub 这 2 个文件夹和 note.txt 这 1 个文件,共计 3 个条目。因此当我们调用 fs::remove_all(root) 将它们一次性全部删除后,返回的删除数量 count 为 3。
此外,与前面学过的文件操作函数一样,这里介绍的 4 个目录函数也都支持在末尾额外传入 std::error_code 对象,方便在不抛出异常的情况下检查错误。
注意 fs::remove_all() 会把目标文件夹及其内部的所有子目录和文件直接从磁盘上彻底移除,不会进入电脑的 “回收站”。在实际开发中,小伙伴们调用该函数前一定要仔细核对路径,千万不要传入 "."、".." 或重要项目目录。
