第 18.20 節

std::filesystem

0瀏覽次數0訪問次數--跳出率--平均停留

本節解決什麼問題

在 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_iteratorextension()遞歸找特定後綴的文件遞歸遍歷可能很慢,注意目錄深度
示例 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_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 不需要。

使用建議

  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 重載:避免一個異常路徑中斷整個掃描。

小結

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