🔷 第十五章 文件操作
📄 一句话概括

C++ 标准库没有"删文件"的成员函数 ——你得借 C 的 std::remove("文件名")(在 <cstdio> 里)。 两个要点:① 它按"文件名"操作,不需要先打开文件;② 成功返回 0,失败返回非 0。 改名同理:std::rename("旧名", "新名")。 更现代的做法(C++17 起):std::filesystem::remove ——它还能建目录、查存在、列文件,是标准库第一次真正管起"文件系统"。 一句话概括:"删文件"这件事不在流的世界里——因为"流"管的是内容,"文件系统"管的是文件名。

📝 本节和前面几节的关系

本节是 15.3~15.5 的"补集":前面全是"内容"的操作,这一节是"名字"的操作。 - 15.2.3 的 fileExists()(用 ifstream 试开)在这里有了"正规版":C++17 的 fs::exists; - 10.6.1 的命名空间别名(namespace fs = std::filesystem;)当时举的就是这个例子——现在它真的用上了; - 13.3.2 的 std::remove(那个"不删元素只挪位置"的算法)和本节的 std::remove 同名——本节会讲清这两个"同名兄弟"怎么区分。 一句话:本节把"文件"这个词的两层含义(内容 / 名字)分开讲清。

一、为什么标准库不给你 fstream.remove()

先想一个问题:"删除文件"和"流"有什么关系?

答案是:没有关系。

所以删除不需要"打开文件" ——你告诉系统一个文件名,它就删掉:

remove("temp.txt");       // 不需要 ifstream / ofstream

由此推出两件事:

  1. 删文件的操作不属于流类库 ——它属于"文件系统";
  2. C++98 年代,标准库压根没有"文件系统"这一块 ——所以只能借 C 的 remove / rename。

二、C 风格的答案:std::remove 和 std::rename

头文件:

#include <cstdio>        // ★ C 标准库的输入输出(含 remove / rename)

两个函数:

int remove(const char *filename);                // 删除文件
int rename(const char *oldname, const char *newname);   // 改名 / 移动

返回值规则(记清楚,容易记反):

返回值 含义
0 成功
非 0 失败

用法:

if (remove("temp.txt") == 0)
    cout << "删除成功" << endl;
else
    cout << "删除失败" << endl;

改名也一样:

if (rename("old.txt", "new.txt") == 0)
    cout << "改名成功" << endl;

注意参数是 const char * ——所以传 string 要 .c_str():

string fname = "temp.txt";
remove(fname.c_str());        // ★ 别写成 remove(fname)

三、一个有意思的"同名兄弟":两个 std::remove

你在 13.3.2 学过 std::remove ——那个是"算法",作用是把"要保留的元素挪到前面"(它不真删元素)。

本节的 std::remove 是"删文件的 C 函数"。

它们同名,怎么区分?

<algorithm> 的 remove <cstdio> 的 remove
参数 三个(首、尾、值) 一个(文件名)
返回 新末尾的迭代器 int(0 = 成功)
作用 挪元素,不真删 删掉磁盘上的文件

它们构成"函数重载",编译器按参数个数和类型自动选 ——所以一般不冲突。

但如果你既 using namespace std; 又同时包含了两个头文件,看代码时可要留神:

remove(v.begin(), v.end(), 3);       // 显然是算法
remove("temp.txt");                  // 显然是删文件

参数个数就区分开了 ——这也顺便说明"读代码先看参数"有多重要。

想要绝对清楚,就写全名或加注释:

std::remove("temp.txt");             // 删文件(<cstdio>)
std::remove(v.begin(), v.end(), 3);  // 算法(<algorithm>)

四、删除的三个前提条件

remove 不是"想删就删",它会失败。三个最常见的失败原因:

原因 说明
① 文件不存在 删一个不存在的文件 → 失败(返回非 0)
② 文件正被占用 Windows 上最常见:文件还被某个程序打开着,就删不掉(包括"你自己的程序忘了 close()")
③ 没有权限 / 是只读文件 只读属性、目录权限不够 → 失败

所以在 Windows 上写"删文件"的代码,最重要的纪律是:

先把所有打开这个文件的流 close() 掉,再删。

这就是 15.5 的"分离"在这里的直接后果 ——流没分离,文件就"还被占着",删不掉。

五、现代做法:C++17 的 <filesystem>

C++17 起,标准库终于有了"文件系统"模块:

#include <filesystem>
namespace fs = std::filesystem;          // ★ 10.6.1 讲过的命名空间别名

fs::remove("temp.txt");                  // 删文件(返回 bool)
fs::remove_all("temp_dir");              // 删目录(递归删光里面所有东西)
fs::create_directory("logs");            // 建目录 ★ 这是 C 风格做不到的
fs::create_directories("logs/2026");     // 建多层目录
fs::exists("a.txt");                     // 查存在(不用再"用 ifstream 试开")
fs::file_size("a.txt");                  // 文件大小(不用 seekg/tellg 了)
fs::rename("a.txt", "b.txt");            // 改名

它解决了 C 风格做不到的三件事:

  1. 建目录(create_directories)——15.2.3 说过标准库不会建目录,这里补上了;
  2. 查文件是否存在(exists)——比"用 ifstream 试开"直接得多;
  3. 列目录内容 / 遍历文件(directory_iterator)。

代价:

所以你可能会遇到:照着教程写 #include <filesystem>,编译报 "No such file or directory"。 那不是你写错了,是编译器太老。 ——这种情况下退回 C 风格的 remove / rename,或者用 system("mkdir ...") 建目录。

六、动手 Demo:删文件的五种情形

#include <iostream>
#include <fstream>
#include <string>
#include <cstdio>            // ★ remove / rename
using namespace std;

bool exists(const string &f) {
    ifstream in(f);
    return (bool)in;
}

int main() {
    const string fname = "to_delete.txt";

    // ══ ① 先造一个文件 ══
    {
        ofstream o(fname);
        if (!o) { cerr << "创建失败" << endl; return 1; }
        o << "temporary content" << endl;
        o.close();
    }
    cout << "① 已创建 " << fname << ",存在吗: "
         << (exists(fname) ? "在" : "不在") << endl;

    // ══ ② 文件还被打开着就删(Windows 上通常失败)══
    {
        ofstream o(fname, ios::app);            // ★ 故意不 close
        int r = remove(fname.c_str());
        cout << "② 没关流就删: remove 返回 " << r << " "
             << (r == 0 ? "(这次成功了)" : "(失败:文件还被占着)") << endl;
    }                                           // 出作用域,流析构 → 自动 close

    // ══ ③ 关掉之后再删 ══
    {
        int r = remove(fname.c_str());
        cout << "③ 关掉后删:   remove 返回 " << r << " "
             << (r == 0 ? "(成功)" : "(失败)") << endl;
        cout << "   删完还存在吗: " << (exists(fname) ? "在" : "不在(已删掉)") << endl;
    }

    // ══ ④ 删一个不存在的文件 ══
    {
        int r = remove("no_such_file_here.txt");
        cout << "④ 删不存在的文件: remove 返回 " << r
             << "(非 0 = 失败)" << endl;
    }

    // ══ ⑤ 改名 ══
    {
        { ofstream o("before.txt"); o << "x" << endl; o.close(); }
        int r = rename("before.txt", "after.txt");
        cout << "⑤ 改名: rename 返回 " << r
             << ",after.txt 存在吗: "
             << (exists("after.txt") ? "在" : "不在") << endl;
        remove("after.txt");                    // 收拾干净
    }
    return 0;
}

看到什么算成功:

① 已创建 to_delete.txt,存在吗: 在
② 没关流就删: remove 返回 -1 (失败:文件还被占着)
③ 关掉后删:   remove 返回 0 (成功)
   删完还存在吗: 不在(已删掉)
④ 删不存在的文件: remove 返回 -1(非 0 = 失败)
⑤ 改名: rename 返回 0,after.txt 存在吗: 在

数字怎么来的:

读结果:

  1. ② 是本节最实用的一条:流没关,文件删不掉 ——-1 就是在告诉你"文件还被占着"。 (顺便注意:那个 ofstream 是在 { } 结束时才析构关闭的,所以③ 能成功。)
  2. ③ 证明"关掉就删得掉",而且 exists() 立刻返回"不在" ——删除是即时生效的。
  3. ④ 说明"删除失败不抛异常" ——只是一个返回值。 所以"想确认删没删掉",就得检查返回值。
  4. ⑤ 证明 rename 也是同一个套路(0 = 成功)——而且它也能当"移动文件"用(把旧路径改成新路径)。

⚠️ 安全提醒:删除是不可逆的(不进回收站)。写涉及删除的程序时,务必:① 删之前确认路径;② 批量删除要有日志;③ 生产数据先备份。 ——这一条比本节所有语法都重要。

七、30 秒实验

  1. 把 ② 的 ofstream o(fname, ios::app); 换成"用记事本打开 to_delete.txt",然后跑程序 → remove 同样失败(被别的程序占着)。这就是"文件被占用"的真面目。
  2. 在 ③ 之后加一句 remove(fname.c_str());(再删一次) → 返回非 0(已经不存在了)。删两次,第二次必然失败。
  3. 把 ⑤ 的目标改成 "sub/after.txt"(子目录不存在) → rename 失败。改名不会帮你建目录(和建文件一样)。
  4. 如果你的编译器支持 C++17,试试 #include <filesystem> → 能编过就试试 fs::create_directories("logs/2026"); ——这正是 C 风格做不到的事情。

八、常见误区

  1. "删文件要先打开它" ——不用。remove 只认文件名,和流无关。
  2. "remove 成功返回非 0" ——反了:成功返回 0,失败返回非 0。
  3. "删文件失败是因为路径写错了" ——不一定:文件被占用(最常见)、没权限、文件不存在都会失败。标准 C++ 不告诉你具体原因(想知道要用 errno 或平台 API)。
  4. "删掉的进回收站" ——不进。remove 是直接删除,不可恢复。 (要进回收站得用系统 API,比如 Windows 的 SHFileOperation。)
  5. "删目录也用 remove" ——std::remove 只能删空目录(有时甚至直接失败),不能删非空目录。递归删目录要用 C++17 的 fs::remove_all。
  6. "两个 std::remove 会冲突" ——不会(参数个数不同,构成重载)。但看代码时要注意分辨:一个参数是删文件,三个参数是算法。
自测三问(点开对答案) 1. **C++ 标准库删文件用什么函数?在哪个头文件里?返回值怎么判断成功?** ——**`std::remove("文件名")`**,在 **``** 里。**成功返回 `0`,失败返回非 `0`**(注意别记反)。 2. **为什么"文件还被打开着"就删不掉?这跟 15.5 有什么关系?** ——**因为删除要求"没有程序正在使用这个文件"**(**Windows 上尤其严格**)。**而"文件被打开着"正是"流还关联着它"** ——**所以必须先 `close()` 分离**(15.5 的关联/分离在这里变成了实际后果)。 3. **C++17 的 `` 解决了 C 风格做不到的哪几件事?** ——**① 建目录**(`create_directories`,15.2.3 说过标准库不会建目录);**② 直接查文件是否存在**(`exists`,不用"用 `ifstream` 试开");**③ 遍历/列出目录内容**(`directory_iterator`)。**还有 `remove_all` 递归删目录。**

相关笔记