std::filesystem
本節解決什麼問題
在 C++17 之前,操作文件和目錄(遍歷、創建、刪除、檢查是否存在)需要依賴平台特定的 API(如 POSIX 的 <dirent.h>、Windows 的 FindFirstFile),代碼不能跨平台。
std::filesystem 提供了跨平台的文件系統操作,一套代碼在 Linux、macOS、Windows 上都能用。
這個特性是什麼
std::filesystem 是 C++17 引入的文件系統庫,提供了路徑處理、文件/目錄操作、遍歷、權限查詢等功能。核心類是 std::filesystem::path。
C++ 標準版本
C++17
需要的頭文件
#include <filesystem>
基本語法
namespace fs = std::filesystem;
fs::path p = "/home/user/data.txt"; // 路径对象
// 检查文件
fs::exists(p); // 是否存在
fs::is_regular_file(p); // 是否是普通文件
fs::is_directory(p); // 是否是目录
fs::file_size(p); // 文件大小
// 操作
fs::create_directory(p); // 创建目录
fs::copy(src, dst); // 拷贝文件
fs::remove(p); // 删除文件/目录
fs::rename(old, new); // 重命名
// 遍历
for (const auto& entry : fs::directory_iterator(p)) { ... } // 非递归
for (const auto& entry : fs::recursive_directory_iterator(p)) {} // 递归
常用操作
| 操作 | 説明 |
|---|---|
fs::exists(p) | 路徑是否存在 |
fs::is_directory(p) | 是否目錄 |
fs::is_regular_file(p) | 是否普通文件 |
fs::create_directory(p) | 創建目錄 |
fs::create_directories(p) | 遞歸創建目錄(類似 mkdir -p) |
fs::copy(src, dst) | 拷貝文件 |
fs::remove(p) | 刪除文件或空目錄 |
fs::remove_all(p) | 遞歸刪除(類似 rm -rf) |
fs::rename(old, new) | 重命名/移動 |
p.filename() | 獲取文件名 |
p.extension() | 獲取擴展名 |
p.parent_path() | 獲取父目錄 |
p / "subdir" | 路徑拼接(用 / 運算符) |
示例代碼
示例 1:檢查文件/目錄是否存在和大小的信息
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main()
{
// 用当前目录做演示
fs::path current = fs::current_path();
std::cout << "Current path: " << current << "\n";
std::cout << "Exists: " << std::boolalpha << fs::exists(current) << "\n";
std::cout << "Is directory: " << fs::is_directory(current) << "\n";
// 获取路径的各部分
std::cout << "Filename: " << current.filename() << "\n";
std::cout << "Parent path: " << current.parent_path() << "\n";
std::cout << "Root path: " << current.root_path() << "\n";
return 0;
}
運行結果(路徑因機器而異):
Current path: "/home/user/MySource/my-blog"
Exists: true
Is directory: true
Filename: "my-blog"
Parent path: "/home/user/MySource"
Root path: "/"
示例 2:在示例 1 基礎上,遍歷目錄中的所有文件
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main()
{
fs::path dir = fs::current_path();
std::cout << "Contents of " << dir << ":\n";
std::cout << "--------------------------------\n";
// 遍历当前目录(不递归子目录)
for (const auto& entry : fs::directory_iterator(dir))
{
std::string type = entry.is_directory() ? "[DIR] " : "[FILE]";
std::cout << type << " " << entry.path().filename().string() << "\n";
}
return 0;
}
運行結果(因目錄內容而異):
Contents of "/home/user/MySource/my-blog":
--------------------------------
[DIR] .git
[DIR] app
[DIR] content
[FILE] nuxt.config.ts
[FILE] package.json
[FILE] README.md
...
示例 3:在示例 2 基礎上,過濾特定擴展名的文件
#include <iostream>
#include <filesystem>
#include <vector>
namespace fs = std::filesystem;
// 查找指定目录下所有 .md 文件
std::vector<fs::path> find_md_files(const fs::path& dir)
{
std::vector<fs::path> result;
for (const auto& entry : fs::recursive_directory_iterator(dir))
{
if (entry.is_regular_file() && entry.path().extension() == ".md")
{
result.push_back(entry.path());
}
}
return result;
}
int main()
{
fs::path dir = fs::current_path() / "content";
if (!fs::exists(dir))
{
std::cout << "Directory not found: " << dir << "\n";
return 1;
}
auto md_files = find_md_files(dir);
std::cout << "Found " << md_files.size() << " .md files:\n";
for (const auto& f : md_files)
{
std::cout << " " << f.filename().string() << "\n";
}
return 0;
}
運行結果(因文件內容而異):
Found 15 .md files:
index.md
ch1-前言.md
ch18-现代C++.md
...
示例 4:在示例 3 基礎上,創建目錄、文件並拷貝
#include <iostream>
#include <filesystem>
#include <fstream>
namespace fs = std::filesystem;
int main()
{
fs::path test_dir = fs::temp_directory_path() / "cpp_test_demo";
// 创建目录(如果已存在也不报错)
if (fs::create_directory(test_dir))
{
std::cout << "Created: " << test_dir << "\n";
}
else
{
std::cout << "Already exists: " << test_dir << "\n";
}
// 创建一个文件
fs::path file_path = test_dir / "hello.txt";
{
std::ofstream out(file_path);
out << "Hello, filesystem!\n";
}
std::cout << "Created file: " << file_path << "\n";
std::cout << "File size: " << fs::file_size(file_path) << " bytes\n";
// 拷贝文件
fs::path copy_path = test_dir / "hello_copy.txt";
fs::copy(file_path, copy_path);
std::cout << "Copied to: " << copy_path << "\n";
// 检查两个文件都存在
std::cout << "hello.txt exists: " << fs::exists(file_path) << "\n";
std::cout << "hello_copy.txt exists: " << fs::exists(copy_path) << "\n";
// 清理:删除测试目录
fs::remove_all(test_dir);
std::cout << "Cleaned up: " << test_dir << "\n";
return 0;
}
運行結果:
Created: "/tmp/cpp_test_demo"
Created file: "/tmp/cpp_test_demo/hello.txt"
File size: 21 bytes
Copied to: "/tmp/cpp_test_demo/hello_copy.txt"
hello.txt exists: true
hello_copy.txt exists: true
Cleaned up: "/tmp/cpp_test_demo"
運行結果
見上方每個示例的"運行結果"。
示例中的關鍵語法解釋
| 示例 | 講了什麼 | 新出現的語法 | 為什麼這樣寫 | 注意事項 |
|---|---|---|---|---|
| 示例 1 | 路徑信息查詢 | fs::current_path()、exists()、filename() | 獲取當前路徑和各組成部分 | 所有路徑操作都是跨平台的 |
| 示例 2 | 遍歷目錄 | fs::directory_iterator | 遍歷當前目錄下的所有條目 | 僅遍歷一級,不會遞歸進入子目錄 |
| 示例 3 | 遞歸遍歷和過濾 | fs::recursive_directory_iterator、extension() | 遞歸找特定後綴的文件 | 遞歸遍歷可能很慢,注意目錄深度 |
| 示例 4 | 創建、拷貝、刪除 | create_directory()、copy()、remove_all() | 常見文件操作,跨平台 | remove_all 類似 rm -rf,小心使用 |
拋異常版本 vs error_code 版本
很多 filesystem 函數有兩種用法:
auto size = fs::file_size(path); // 失败时抛异常
std::error_code ec;
auto size = fs::file_size(path, ec); // 失败时不抛异常,错误写入 ec
| 寫法 | 優點 | 適合場景 |
|---|---|---|
| 拋異常版本 | 代碼短,失敗自動中斷 | 文件理應存在,失敗就是異常 |
error_code 版本 | 不拋異常,便於繼續處理 | 掃描大量文件、權限不確定、允許跳過失敗項 |
示例 5:掃描目錄時用 error_code 跳過失敗項
#include <filesystem>
#include <iostream>
#include <system_error>
// filesystem 用来跨平台处理路径、文件和目录。
namespace fs = std::filesystem;
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
fs::path dir = fs::current_path();
for (const auto& entry : fs::directory_iterator(dir))
{
std::error_code ec;
auto size = fs::file_size(entry.path(), ec);
if (ec)
{
std::cout << "[skip] " << entry.path().filename().string()
<< " reason: " << ec.message() << "\n";
continue;
}
std::cout << entry.path().filename().string()
<< " size = " << size << "\n";
}
return 0;
}
運行結果(因目錄內容而異):
[skip] content reason: Is a directory
package.json size = 1234
README.md size = 2048
掃描目錄時,遇到目錄、權限不足、符號鏈接異常都很常見。用 error_code 可以讓程序跳過壞項繼續跑,而不是第一個失敗就退出。
常見錯誤
錯誤 1:忘記檢查 exists 就直接操作
std::cout << fs::file_size("/nonexistent"); // ❌ 抛异常!
正確做法:先 exists() 檢查,或使用 try-catch。
錯誤 2:create_directory 和 create_directories 混淆
fs::create_directory("a/b/c"); // ❌ 如果 a/b 不存在则失败!
正確做法:fs::create_directories("a/b/c"); 會遞歸創建。
錯誤 3:用 == 比較路徑
if (path1 == path2) // 只是字符串比较!
路徑 /home/user 和 /home/user/ 可能不相等,用 fs::equivalent(p1, p2) 做規範比較。
錯誤 4:鏈接時需要 -lstdc++fs(舊編譯器)
在 GCC 8 及以前,需要鏈接 stdc++fs 庫:g++ -std=c++17 file.cpp -lstdc++fs。GCC 9+ 和 Clang 不需要。
使用建議
- 用
namespace fs = std::filesystem;簡化代碼:這個縮寫幾乎是社區標準。 - 路徑用
/運算符拼接:auto p = base / "subdir" / "file.txt";,簡潔且跨平台。 - 遍歷大目錄用
directory_iterator:避免一次性加載所有文件。 - 文件操作注意異常安全:很多 filesystem 函數在失敗時會拋
fs::filesystem_error。 - C++17 的 filesystem 基本夠用:C++20/23 增加了一些便利函數但核心不變。
- 掃描大量文件時考慮 error_code 重載:避免一個異常路徑中斷整個掃描。
小結
std::filesystem提供跨平台的文件系統操作。fs::path是路徑類,支持/拼接、拆分(filename()/extension()/parent_path())。exists()檢查存在,create_directory()創建目錄,copy()/remove()等操作文件。directory_iterator遍歷目錄,recursive_directory_iterator遞歸遍歷。- 操作前檢查
exists(),注意異常處理。