跳到正文

Wiki

std::filesystem

約 5 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

1.本節解決什麼問題

在 C++17 之前,操作檔案和目錄(遍歷、建立、刪除、檢查是否存在)需要依賴平臺特定的 API(如 POSIX 的 <dirent.h>、Windows 的 FindFirstFile),程式碼不能跨平臺。

std::filesystem 提供了跨平臺的檔案系統操作,一套程式碼在 Linux、macOS、Windows 上都能用。

2.這個特性是什麼

std::filesystem 是 C++17 引入的檔案系統庫,提供了路徑處理、檔案/目錄操作、遍歷、許可權查詢等功能。核心類是 std::filesystem::path

3.C++ 標準版本

C++17

4.需要的標頭檔案

#include <filesystem>

5.基本語法

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)) {} // 递归

6.常用操作

操作 說明
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" 路徑拼接(用 / 運算子)

7.示例程式碼

7.1.示例 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: "/"

7.2.示例 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
...

7.3.示例 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
  ch2-前言.md
  2000-现代C++.md
  ...

7.4.示例 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"

8.執行結果

見上方每個示例的"執行結果"。

9.示例中的關鍵語法解釋

示例 講了什麼 新出現的語法 為什麼這樣寫 注意事項
示例 1 路徑資訊查詢 fs::current_path()exists()filename() 獲取當前路徑和各組成部分 所有路徑操作都是跨平臺的
示例 2 遍歷目錄 fs::directory_iterator 遍歷當前目錄下的所有條目 僅遍歷一級,不會遞迴進入子目錄
示例 3 遞迴遍歷和過濾 fs::recursive_directory_iteratorextension() 遞迴找特定字尾的檔案 遞迴遍歷可能很慢,注意目錄深度
示例 4 建立、複製、刪除 create_directory()copy()remove_all() 常見檔案操作,跨平臺 remove_all 類似 rm -rf,小心使用

10.拋異常版本 vs error_code 版本

很多 filesystem 函式有兩種用法:

auto size = fs::file_size(path);      // 失败时抛异常

std::error_code ec;
auto size = fs::file_size(path, ec);  // 失败时不抛异常,错误写入 ec
寫法 優點 適合場景
拋異常版本 程式碼短,失敗自動中斷 檔案理應存在,失敗就是異常
error_code 版本 不拋異常,便於繼續處理 掃描大量檔案、許可權不確定、允許跳過失敗項

10.1.示例 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 可以讓程式跳過壞項繼續跑,而不是第一個失敗就退出。

11.常見錯誤

錯誤 1:忘記檢查 exists 就直接操作

std::cout << fs::file_size("/nonexistent");  // ❌ 抛异常!

正確做法:先 exists() 檢查,或使用 try-catch。

錯誤 2:create_directorycreate_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 不需要。

12.使用建議

  1. namespace fs = std::filesystem; 簡化程式碼:這個縮寫幾乎是社群標準。
  2. 路徑用 / 運算子拼接auto p = base / "subdir" / "file.txt";,簡潔且跨平臺。
  3. 遍歷大目錄用 directory_iterator:避免一次性載入所有檔案。
  4. 檔案操作注意異常安全:很多 filesystem 函式在失敗時會拋 fs::filesystem_error
  5. C++17 的 filesystem 基本夠用:C++20/23 增加了一些便利函式但核心不變。
  6. 掃描大量檔案時考慮 error_code 過載:避免一個異常路徑中斷整個掃描。

13.小結

  • std::filesystem 提供跨平臺的檔案系統操作。
  • fs::path 是路徑類,支援 / 拼接、拆分(filename()/extension()/parent_path())。
  • exists() 檢查存在,create_directory() 建立目錄,copy()/remove() 等操作檔案。
  • directory_iterator 遍歷目錄,recursive_directory_iterator 遞迴遍歷。
  • 操作前檢查 exists(),注意異常處理。