S SmartDocs
Serie: C++ cpp 255 líneas · Actualizado 2026-04-03

filesystem.cpp

C++/Part4_現代CPP/Ch20_CPP17特性精選/filesystem.cpp

// Ch20 範例:std::filesystem(C++17 檔案系統)
// 編譯:g++ -std=c++17 -Wall -o filesystem filesystem.cpp
// 注意:某些舊版編譯器可能需要加上 -lstdc++fs

#include <iostream>
#include <string>
#include <fstream>
#include <filesystem>
#include <vector>
#include <iomanip>
#include <sstream>

namespace fs = std::filesystem;

// ============================================================
// 輔助函式
// ============================================================

void printSeparator(const std::string& title) {
    std::cout << "\n" << std::string(60, '=') << "\n";
    std::cout << title << "\n";
    std::cout << std::string(60, '=') << "\n";
}

// 將位元組數轉為可讀的大小格式
std::string humanReadableSize(uintmax_t bytes) {
    const char* units[] = {"B", "KB", "MB", "GB", "TB"};
    int unitIdx = 0;
    double size = static_cast<double>(bytes);
    while (size >= 1024.0 && unitIdx < 4) {
        size /= 1024.0;
        ++unitIdx;
    }
    std::ostringstream oss;
    oss << std::fixed << std::setprecision(1) << size << " " << units[unitIdx];
    return oss.str();
}

// 取得檔案類型的文字描述
std::string fileTypeString(const fs::directory_entry& entry) {
    if (entry.is_regular_file())   return "[檔案]";
    if (entry.is_directory())      return "[目錄]";
    if (entry.is_symlink())        return "[符號連結]";
    if (entry.is_block_file())     return "[區塊裝置]";
    if (entry.is_character_file()) return "[字元裝置]";
    if (entry.is_fifo())           return "[FIFO]";
    if (entry.is_socket())         return "[Socket]";
    return "[其他]";
}

// ============================================================
// 主程式
// ============================================================
int main() {
    std::cout << std::string(60, '=') << "\n";
    std::cout << "C++17 std::filesystem 檔案系統操作範例\n";
    std::cout << std::string(60, '=') << "\n";

    // --- 1. 路徑(fs::path)操作 ---
    printSeparator("1. fs::path 路徑操作");

    fs::path p1 = "/home/user/documents/report.txt";
    std::cout << "完整路徑:     " << p1 << "\n";
    std::cout << "根路徑:       " << p1.root_path() << "\n";
    std::cout << "父目錄:       " << p1.parent_path() << "\n";
    std::cout << "檔案名稱:     " << p1.filename() << "\n";
    std::cout << "主檔名(stem):" << p1.stem() << "\n";
    std::cout << "副檔名:       " << p1.extension() << "\n";

    // 路徑拼接
    fs::path base = "/home/user";
    fs::path file = "data.csv";
    fs::path combined = base / "projects" / file;  // 使用 / 運算子
    std::cout << "\n路徑拼接:" << combined << "\n";

    // 路徑比較與操作
    fs::path p2 = "/home/user/../user/./documents/report.txt";
    std::cout << "\n原始路徑:     " << p2 << "\n";
    // lexically_normal 只做字串層級的正規化,不需要檔案存在
    std::cout << "正規化路徑:   " << p2.lexically_normal() << "\n";

    // --- 2. 取得目前工作目錄 ---
    printSeparator("2. 目前工作目錄與暫存目錄");

    std::cout << "目前工作目錄:" << fs::current_path() << "\n";
    std::cout << "暫存目錄:    " << fs::temp_directory_path() << "\n";

    // --- 3. 建立測試用目錄與檔案 ---
    printSeparator("3. 建立目錄與檔案");

    // 建立測試目錄結構
    fs::path testDir = fs::temp_directory_path() / "cpp17_fs_demo";

    // 清理舊測試資料
    if (fs::exists(testDir)) {
        fs::remove_all(testDir);
    }

    // 建立目錄(含巢狀)
    fs::create_directories(testDir / "subdir1" / "nested");
    fs::create_directory(testDir / "subdir2");
    std::cout << "已建立目錄結構:" << testDir << "\n";

    // 建立測試檔案
    std::vector<std::pair<std::string, std::string>> testFiles = {
        {"hello.txt",          "Hello, C++17 filesystem!"},
        {"data.csv",           "name,age,score\nAlice,20,95\nBob,21,87"},
        {"notes.md",           "# 筆記\n\n這是一個測試檔案。"},
        {"subdir1/config.ini", "[settings]\nlang=zh-TW\ntheme=dark"},
        {"subdir1/nested/deep.txt", "深層巢狀檔案"},
        {"subdir2/log.txt",    "2024-01-01 系統啟動\n2024-01-02 正常運作"},
    };

    for (const auto& [name, content] : testFiles) {
        fs::path filePath = testDir / name;
        std::ofstream ofs(filePath);
        ofs << content;
        ofs.close();
        std::cout << "  已建立:" << name << "\n";
    }

    // --- 4. 檢查路徑是否存在與檔案類型 ---
    printSeparator("4. 檢查檔案與目錄");

    fs::path checkPath = testDir / "hello.txt";

    std::cout << "路徑:" << checkPath << "\n";
    std::cout << "  exists:       " << std::boolalpha << fs::exists(checkPath) << "\n";
    std::cout << "  is_regular_file:" << fs::is_regular_file(checkPath) << "\n";
    std::cout << "  is_directory:   " << fs::is_directory(checkPath) << "\n";

    std::cout << "\n路徑:" << testDir / "subdir1" << "\n";
    std::cout << "  exists:       " << fs::exists(testDir / "subdir1") << "\n";
    std::cout << "  is_directory:   " << fs::is_directory(testDir / "subdir1") << "\n";

    std::cout << "\n路徑:" << testDir / "not_exist.txt" << "\n";
    std::cout << "  exists:       " << fs::exists(testDir / "not_exist.txt") << "\n";

    // --- 5. 檔案大小 ---
    printSeparator("5. 檔案大小");

    for (const auto& [name, content] : testFiles) {
        fs::path filePath = testDir / name;
        if (fs::is_regular_file(filePath)) {
            auto size = fs::file_size(filePath);
            std::cout << std::left << std::setw(30) << name
                      << std::right << std::setw(8) << size << " bytes ("
                      << humanReadableSize(size) << ")\n";
        }
    }

    // --- 6. 目錄巡覽:directory_iterator ---
    printSeparator("6. directory_iterator(只看第一層)");

    std::cout << testDir << " 的內容:\n\n";
    for (const auto& entry : fs::directory_iterator(testDir)) {
        std::cout << "  " << fileTypeString(entry) << " "
                  << entry.path().filename();
        if (entry.is_regular_file()) {
            std::cout << "  (" << humanReadableSize(entry.file_size()) << ")";
        }
        std::cout << "\n";
    }

    // --- 7. 遞迴目錄巡覽:recursive_directory_iterator ---
    printSeparator("7. recursive_directory_iterator(遞迴巡覽)");

    std::cout << testDir << " 的所有內容:\n\n";
    for (const auto& entry : fs::recursive_directory_iterator(testDir)) {
        // 計算相對於 testDir 的路徑來顯示層級
        auto relPath = fs::relative(entry.path(), testDir);
        int depth = 0;
        for ([[maybe_unused]] const auto& part : relPath) ++depth;

        std::cout << std::string(static_cast<size_t>((depth - 1) * 2), ' ')
                  << fileTypeString(entry) << " "
                  << entry.path().filename();
        if (entry.is_regular_file()) {
            std::cout << "  (" << humanReadableSize(entry.file_size()) << ")";
        }
        std::cout << "\n";
    }

    // --- 8. 複製、改名、刪除 ---
    printSeparator("8. 複製、改名、刪除操作");

    // 複製檔案
    fs::path src = testDir / "hello.txt";
    fs::path dst = testDir / "hello_copy.txt";
    fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
    std::cout << "已複製 hello.txt → hello_copy.txt\n";
    std::cout << "  副本大小:" << fs::file_size(dst) << " bytes\n";

    // 改名
    fs::path renamed = testDir / "greeting.txt";
    fs::rename(dst, renamed);
    std::cout << "已改名 hello_copy.txt → greeting.txt\n";
    std::cout << "  hello_copy.txt 存在?" << std::boolalpha
              << fs::exists(dst) << "\n";
    std::cout << "  greeting.txt 存在?" << fs::exists(renamed) << "\n";

    // 刪除單一檔案
    fs::remove(renamed);
    std::cout << "已刪除 greeting.txt\n";

    // --- 9. 空間資訊 ---
    printSeparator("9. 磁碟空間資訊");

    auto spaceInfo = fs::space(testDir);
    std::cout << "容量:  " << humanReadableSize(spaceInfo.capacity) << "\n";
    std::cout << "可用:  " << humanReadableSize(spaceInfo.free) << "\n";
    std::cout << "已用:  " << humanReadableSize(spaceInfo.capacity - spaceInfo.free) << "\n";

    // --- 10. 錯誤處理(使用 error_code 版本)---
    printSeparator("10. 使用 error_code 處理錯誤");

    std::error_code ec;

    // 嘗試取得不存在檔案的大小
    auto size = fs::file_size("/nonexistent/path/file.txt", ec);
    if (ec) {
        std::cout << "取得檔案大小失敗:" << ec.message() << "\n";
    } else {
        std::cout << "檔案大小:" << size << "\n";
    }

    // 嘗試建立已存在的目錄
    bool created = fs::create_directory(testDir / "subdir1", ec);
    std::cout << "建立已存在的目錄 → created = " << std::boolalpha << created;
    if (ec) std::cout << ",錯誤:" << ec.message();
    else    std::cout << "(無錯誤,只是已存在)";
    std::cout << "\n";

    // --- 清理測試目錄 ---
    printSeparator("清理");
    auto removedCount = fs::remove_all(testDir);
    std::cout << "已刪除 " << removedCount << " 個檔案/目錄\n";
    std::cout << "測試目錄是否仍存在?" << std::boolalpha
              << fs::exists(testDir) << "\n";

    // --- 總結 ---
    printSeparator("總結");
    std::cout << "std::filesystem 提供了跨平台的檔案系統操作:\n";
    std::cout << "  • fs::path — 路徑表示與操作\n";
    std::cout << "  • fs::exists / is_regular_file / is_directory — 檢查\n";
    std::cout << "  • fs::file_size — 取得檔案大小\n";
    std::cout << "  • fs::create_directory(ies) — 建立目錄\n";
    std::cout << "  • fs::copy_file / rename / remove — 檔案操作\n";
    std::cout << "  • fs::directory_iterator — 巡覽目錄\n";
    std::cout << "  • fs::recursive_directory_iterator — 遞迴巡覽\n";
    std::cout << "  • fs::space — 磁碟空間資訊\n";
    std::cout << "  • error_code 版本 — 不丟例外的錯誤處理\n";

    return 0;
}

Artículos relacionados