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
由此推出两件事:
- 删文件的操作不属于流类库 ——它属于"文件系统";
- 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 风格做不到的三件事:
- 建目录(
create_directories)——15.2.3 说过标准库不会建目录,这里补上了; - 查文件是否存在(
exists)——比"用ifstream试开"直接得多; - 列目录内容 / 遍历文件(
directory_iterator)。
代价:
- 需要较新的编译器(Dev-C++ 自带的 MinGW 常常不支持,g++ 8+ 且用
-std=c++17才行); - 链接时有些平台还要加
-lstdc++fs。
所以你可能会遇到:照着教程写
#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 存在吗: 在
数字怎么来的:
0= 成功(③ 和 ⑤);-1= 失败(② 和 ④)——标准只要求"非 0",具体值由实现决定(MinGW 上常见-1)。
读结果:
- ② 是本节最实用的一条:流没关,文件删不掉 ——
-1就是在告诉你"文件还被占着"。 (顺便注意:那个ofstream是在{ }结束时才析构关闭的,所以③ 能成功。) - ③ 证明"关掉就删得掉",而且
exists()立刻返回"不在" ——删除是即时生效的。 - ④ 说明"删除失败不抛异常" ——只是一个返回值。 所以"想确认删没删掉",就得检查返回值。
- ⑤ 证明
rename也是同一个套路(0= 成功)——而且它也能当"移动文件"用(把旧路径改成新路径)。
⚠️ 安全提醒:删除是不可逆的(不进回收站)。写涉及删除的程序时,务必:① 删之前确认路径;② 批量删除要有日志;③ 生产数据先备份。 ——这一条比本节所有语法都重要。
七、30 秒实验
- 把 ② 的
ofstream o(fname, ios::app);换成"用记事本打开to_delete.txt",然后跑程序 →remove同样失败(被别的程序占着)。这就是"文件被占用"的真面目。 - 在 ③ 之后加一句
remove(fname.c_str());(再删一次) → 返回非 0(已经不存在了)。删两次,第二次必然失败。 - 把 ⑤ 的目标改成
"sub/after.txt"(子目录不存在) →rename失败。改名不会帮你建目录(和建文件一样)。 - 如果你的编译器支持 C++17,试试
#include <filesystem>→ 能编过就试试fs::create_directories("logs/2026");——这正是 C 风格做不到的事情。
八、常见误区
- "删文件要先打开它" ——不用。
remove只认文件名,和流无关。 - "
remove成功返回非 0" ——反了:成功返回0,失败返回非 0。 - "删文件失败是因为路径写错了" ——不一定:文件被占用(最常见)、没权限、文件不存在都会失败。标准 C++ 不告诉你具体原因(想知道要用
errno或平台 API)。 - "删掉的进回收站" ——不进。
remove是直接删除,不可恢复。 (要进回收站得用系统 API,比如 Windows 的SHFileOperation。) - "删目录也用
remove" ——std::remove只能删空目录(有时甚至直接失败),不能删非空目录。递归删目录要用 C++17 的fs::remove_all。 - "两个
std::remove会冲突" ——不会(参数个数不同,构成重载)。但看代码时要注意分辨:一个参数是删文件,三个参数是算法。
自测三问(点开对答案)
1. **C++ 标准库删文件用什么函数?在哪个头文件里?返回值怎么判断成功?** ——**`std::remove("文件名")`**,在 **`- ☐ 下一节(15.7 小结)
相关笔记
- 文件和流的关联与分离 —— 上一节:为什么要先
close再删 - 小结 —— 下一节:本章知识地图与两关自测
- 修正序列算法 —— 另一个同名的
std::remove(不真删元素的那个) - 使用命名空间 ——
namespace fs = std::filesystem;这个例子的出处 - 在打开文件的同时创建文件 —— "会用
ifstream试开查存在"的老办法 - 🔷 C++基础 · 索引 —— 全课路线图