C/C++ 文件操作全攻略:Linux、Windows、macOS 三平台实战
C/C++ 文件操作全攻略:Linux、Windows、macOS 三平台实战
前言
文件操作是每个 C/C++ 开发者的必修课。但说实话,很多人写了几年代码,对文件操作的理解还停留在 fopen("test.txt", "r") 的阶段。更别提跨平台开发时,Linux 和 Windows 那套完全不同的底层 API 能把人搞疯。
这篇文章不讲虚的,直接从三种层面把文件操作讲透:C 标准库、C++ 标准库、平台系统调用。顺便把 VS2022 那堆安全函数警告的来龙去脉说清楚,以及一个多数人踩过但没深究的坑——Unicode 文件名在 Windows 上的乱码问题。
一、C 标准库方式(跨平台通用)
C 标准库的 <stdio.h> 是跨平台文件操作的基础。无论在 Linux、Windows 还是 macOS 上,这套 API 的行为都是一致的。
1.1 传统方式:fopen / fread / fwrite / fclose
这是最经典的一套,从 K&R 时代用到现在。
#include <stdio.h>
#include <string.h>
int main() {
FILE* fp = fopen("test.txt", "w");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
fwrite("Hello World", 1, 11, fp);
fclose(fp);
return 0;
}
优点:简单、跨平台、所有编译器都支持。
缺点:错误信息模糊(只能拿到 NULL,不知道具体为啥失败)、缺乏参数校验。
1.2 安全增强方式:fopen_s(C11 Annex K)
VS2022 里直接写 fopen 会报 C4996 警告:
'fopen': This function or variable may be unsafe. Consider using fopen_s instead.
这是微软在 Visual Studio 中把传统 C 库函数标记为“弃用”的结果。fopen_s 是 C11 标准附录 K 定义的边界检查函数,函数原型如下:
errno_t fopen_s(FILE** streamptr, const char* filename, const char* mode);
关键区别:
- 返回值不再是
FILE*,而是errno_t错误码(0 表示成功) - 文件指针通过输出参数返回
- 自动校验参数是否为空指针
#include <stdio.h>
#include <errno.h>
int main() {
FILE* fp = NULL;
errno_t err = fopen_s(&fp, "test.txt", "w");
if (err != 0) {
printf("打开文件失败,错误码:%d\n", err);
return 1;
}
fwrite("Hello World", 1, 11, fp);
fclose(fp);
return 0;
}
注意:
fopen_s是 C11 Annex K 的可选特性,不是所有编译器都支持。GCC 和 Clang 默认不实现这套 _s 函数。如果你的代码需要跨平台编译(比如在 Linux 上用 GCC),直接用fopen更稳妥——或者用条件编译做适配。
1.3 Windows 平台特有:_fsopen(共享模式)
fopen_s 有个坑:用这个函数打开的文件默认是独占的,其他程序无法同时访问。
如果你需要多进程共享文件,Windows 下要用 _fsopen:
#include <stdio.h>
#include <share.h>
FILE* fp = _fsopen("test.txt", "w", _SH_DENYNO); // 允许其他程序读写
第三个参数控制共享模式:
_SH_DENYNO:允许读写共享_SH_DENYRD:拒绝读共享_SH_DENYWR:拒绝写共享
Linux/macOS 下默认就是共享的,不需要这套机制。
二、C++ 标准库方式(面向对象,推荐)
C++ 的 <fstream> 提供了面向对象的文件流操作,是现代 C++ 项目最推荐的方式。
2.1 核心类
| 类 | 用途 |
|---|---|
std::ofstream |
输出文件流(写文件) |
std::ifstream |
输入文件流(读文件) |
std::fstream |
输入输出文件流(读写均可) |
2.2 文本文件读写
#include <iostream>
#include <fstream>
#include <string>
int main() {
// 写文件
std::ofstream outFile("example.txt");
if (!outFile.is_open()) {
std::cerr << "无法打开文件" << std::endl;
return 1;
}
outFile << "Hello, C++ File I/O!" << std::endl;
outFile << "整数: " << 42 << ", 浮点数: " << 3.14159 << std::endl;
outFile.close();
// 读文件
std::ifstream inFile("example.txt");
if (!inFile.is_open()) {
std::cerr << "无法打开文件" << std::endl;
return 1;
}
std::string line;
while (std::getline(inFile, line)) {
std::cout << line << std::endl;
}
inFile.close();
return 0;
}
2.3 二进制文件读写
#include <fstream>
#include <vector>
struct Data {
int id;
double value;
char name[32];
};
int main() {
Data d = {1, 3.14, "test"};
// 写二进制
std::ofstream outFile("data.bin", std::ios::binary);
outFile.write(reinterpret_cast<const char*>(&d), sizeof(Data));
outFile.close();
// 读二进制
Data d2;
std::ifstream inFile("data.bin", std::ios::binary);
inFile.read(reinterpret_cast<char*>(&d2), sizeof(Data));
inFile.close();
return 0;
}
关键点:二进制模式必须显式指定
std::ios::binary。在 Windows 上,文本模式会自动转换换行符(\n→\r\n),二进制模式不会。
2.4 文件打开模式
| 模式 | 含义 |
|---|---|
std::ios::in |
读 |
std::ios::out |
写(默认会清空文件) |
std::ios::app |
追加 |
std::ios::ate |
打开后定位到末尾 |
std::ios::trunc |
清空文件 |
std::ios::binary |
二进制模式 |
可以组合使用:std::ios::in | std::ios::out | std::ios::binary。
三、C++17 filesystem(目录操作与路径处理)
<filesystem> 是 C++17 引入的跨平台文件系统操作库。它不负责文件内容的读写,但负责路径处理、目录遍历、文件状态查询等。
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
// 路径处理 - 自动适配不同系统的路径分隔符
fs::path p = fs::path("data") / "subdir" / "file.txt";
std::cout << p.string() << std::endl;
// Linux: data/subdir/file.txt
// Windows: data\subdir\file.txt
// 检查文件/目录是否存在
if (fs::exists("example.txt")) {
std::cout << "文件存在,大小: " << fs::file_size("example.txt") << " 字节" << std::endl;
}
// 遍历目录
if (fs::exists(".") && fs::is_directory(".")) {
for (const auto& entry : fs::directory_iterator(".")) {
std::cout << entry.path().filename().string() << std::endl;
}
}
// 创建目录
fs::create_directory("new_folder");
fs::create_directories("a/b/c/d"); // 递归创建
// 重命名/移动
fs::rename("old.txt", "new.txt");
// 删除
fs::remove("temp.txt");
fs::remove_all("temp_folder"); // 递归删除
return 0;
}
编译时注意链接:
- GCC/Clang:
g++ -std=c++17 main.cpp -lstdc++fs - MSVC(VS2022):自动链接,无需额外操作
- macOS 特别说明:Apple Clang 默认使用
libc++,文件系统支持已内置,通常无需额外链接库。若遇到链接错误,尝试g++ -std=c++17 main.cpp -lc++fs。
四、平台系统调用(底层 API)
4.1 Linux / macOS:POSIX 系统调用
POSIX 标准定义了 open、read、write、close 等系统调用。
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main() {
// 打开/创建文件(权限 0644)
int fd = open("test.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
fprintf(stderr, "open 失败: %s\n", strerror(errno));
return 1;
}
const char* msg = "Hello POSIX\n";
ssize_t written = write(fd, msg, strlen(msg));
if (written == -1) {
fprintf(stderr, "write 失败: %s\n", strerror(errno));
}
close(fd);
return 0;
}
常用 flag:
O_RDONLY:只读O_WRONLY:只写O_RDWR:读写O_CREAT:不存在则创建O_TRUNC:清空O_APPEND:追加O_EXCL:与O_CREAT配合,文件存在则报错
4.2 Windows:Win32 API
Windows 下对应的是 CreateFile、ReadFile、WriteFile、CloseHandle。
#include <windows.h>
#include <iostream>
int main() {
HANDLE hFile = CreateFileA(
"test.txt",
GENERIC_WRITE,
FILE_SHARE_READ, // 共享读
NULL,
CREATE_ALWAYS, // 总是创建/覆盖
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
std::cerr << "CreateFile 失败,错误码: " << GetLastError() << std::endl;
return 1;
}
const char* msg = "Hello Windows\n";
DWORD bytesWritten;
WriteFile(hFile, msg, strlen(msg), &bytesWritten, NULL);
CloseHandle(hFile);
return 0;
}
绝大部分场景下不需要直接调用系统 API。C 标准库和 C++ 标准库已经封装好了跨平台接口,直接用就行。只有在需要平台特定功能(如 Windows 的文件共享控制、内存映射文件)时才需要深入系统层。
五、VS2022 安全函数与跨平台 Unicode 处理详解
5.1 为什么会报 C4996?
Visual Studio 认为 fopen、scanf、strcpy 等传统 C 函数存在安全隐患:
- 不检查参数是否为空指针
- 不检查缓冲区大小
- 错误信息不精确(如
strerror返回内部静态缓冲区指针,多线程不安全)
所以微软在 MSVC 中将这些函数标记为“已弃用”,建议改用 _s 后缀的安全版本。
5.2 常见 _s 函数对照表
| 不安全函数 | 安全替代 |
|---|---|
fopen |
fopen_s |
freopen |
freopen_s |
fclose |
fclose_s |
scanf |
scanf_s |
printf |
printf_s |
strcpy |
strcpy_s |
strcat |
strcat_s |
strerror |
strerror_s |
5.3 处理 strerror 警告的跨平台方式
MSVC 报 C4996 是因为 strerror 返回的指针指向线程局部静态缓冲区,在多线程环境下可能被覆盖。strerror_s 要求用户传入缓冲区,从根本上杜绝了竞态条件。
跨平台适配代码:
#ifdef _MSC_VER
#define STRERROR_S(buf, size, err) strerror_s((buf), (size), (err))
#else
// POSIX 标准有 strerror_r(线程安全),但 GNU 和 XSI 版本行为不同
// 此处为了兼容性,直接使用 strerror,并配合 _CRT_SECURE_NO_WARNINGS
#define STRERROR_S(buf, size, err) strerror(err)
#endif
更干净的做法是在源文件顶部统一抑制:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <errno.h>
这样既能使用 strerror,又在 MSVC 下不报警。在后面的完整代码中,我采用了这种方案,并额外封装了一个 safe_strerror 辅助函数,在 MSVC 下显式调用 strerror_s,在 POSIX 系统下调 strerror。
5.4 Unicode 文件名与内容处理(重点)
为什么 Windows 上中文文件名会乱码或打开失败?
Windows 内核使用 UTF-16 LE 编码存储文件名,而 fopen、std::ifstream 接受的 char* 字符串在 Windows 上默认按 系统代码页(ANSI,如 CP936/GBK) 解释。当你传入一个 UTF-8 编码的 char* 字符串(例如从网络接收或 Linux 移植过来的代码),Windows 会把它当作 ANSI 去解码,导致找不到文件或创建出乱码文件名。
Linux / macOS 则大多使用 UTF-8 作为系统语言环境,char* 直接对应 UTF-8,因此很少出现这个问题。
解决方案:
- C 标准库:在 Windows 上使用
_wfopen_s(宽字符版本),传入 UTF-16 编码的wchar_t*。需要先将 UTF-8 字符串转换为 UTF-16(可使用MultiByteToWideChar)。 - C++ 标准库(推荐):使用
std::filesystem::path(C++17),它内部抽象了编码差异。构造path时传入 UTF-8 字符串(Linux 下)或宽字符串(Windows 下)。MSVC 的std::fstream构造函数重载了const wchar_t*,因此可以直接传入path.c_str()返回的宽字符串。 - C++ 通用技巧:统一使用
std::wstring存储文件路径,在 Linux 下用std::wstring_convert转换为 UTF-8(C++11 后可用,但 C++17 起该工具弃用,推荐直接使用std::filesystem::path)。
实践建议:如果你的项目需要跨平台处理中文路径,C++17 的 <filesystem> 是最省心的选择。下面的完整代码中,C++ 示例统一使用 fs::path 构建路径,C 示例则通过条件编译在 Windows 下调 _wfopen_s,在 Linux/macOS 下调 fopen(假定系统为 UTF-8 环境)。
六、完整代码示例(消除 Emoji,内嵌跨平台注释)
6.1 C 风格完整示例(条件编译 + 手动兼容 macOS/Linux)
/**
* file_io_c.c - C 风格文件操作完整示例
*
* 跨平台设计思路:
* 1. Windows (MSVC):使用 _wfopen_s 支持 UTF-16 文件名(原生 Unicode)
* 2. macOS / Linux (Clang / GCC):使用标准 fopen,依赖系统 UTF-8 语言环境
* 3. 因 macOS/Linux 不实现 C11 Annex K(_s 安全函数),故在 POSIX 分支中
* 手动定义 errno_t 为 int,使代码语法与 MSVC 保持一致
*
* 编译指令:
* Windows (VS2022): 直接编译为 .c 文件
* macOS (Clang): gcc -std=c11 file_io_c.c -o file_io_c
* Linux (GCC): gcc -std=c11 file_io_c.c -o file_io_c
*/
#define _CRT_SECURE_NO_WARNINGS // 仅对 MSVC 生效:抑制 fopen/strerror 弃用警告
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// ==================== 平台适配层 ====================
#ifdef _MSC_VER
// ---------- Windows 平台 (MSVC) ----------
// 原因:Windows 内核使用 UTF-16 编码文件名,char* 默认按系统代码页(如 GBK)解释
// 为了支持中文/日文等 Unicode 路径,必须使用宽字符版本的 _wfopen_s
#include <wchar.h>
// 安全错误信息函数:MSVC 提供线程安全的 strerror_s
static const char* safe_strerror(int err, char* buf, size_t size) {
if (strerror_s(buf, size, err) == 0) {
return buf;
}
return "Unknown error";
}
// 文件打开宏:使用宽字符版本,模式字符串也需加 L 前缀
#define FOPEN_W(fp, path, mode) _wfopen_s(&(fp), (path), L##mode)
#else
// ---------- POSIX 平台 (macOS / Linux) ----------
// 关键修复:macOS 的 Clang 和 Linux 的 GCC 均不实现 C11 Annex K 标准,
// 即不认识 errno_t、fopen_s、strerror_s 等类型和函数。
// 此处手动定义 errno_t 为 int,使后续代码中的 errno_t err 能正常编译。
typedef int errno_t;
// POSIX 下直接使用标准 strerror(注意:它返回内部静态缓冲区指针,
// 在多线程环境下不安全,但单线程演示代码中足够使用)
static const char* safe_strerror(int err, char* buf, size_t size) {
(void)buf; (void)size; // 屏蔽未使用参数警告
return strerror(err);
}
// 文件打开宏:直接调用标准 fopen,传入 char* 路径(假定系统 LANG 为 UTF-8)
// 注意:此处强制将模式字符串视为普通 char*,不加 L 前缀
#define FOPEN_W(fp, path, mode) ((fp) = fopen((path), (mode)), 0)
#endif
// ==================== 功能函数 ====================
// 写文本文件(支持 Unicode 文件名)
void write_text_file(const wchar_t* filename_w) {
FILE* fp = NULL;
errno_t err = 0;
#ifdef _MSC_VER
// Windows:使用宽字符版本
err = _wfopen_s(&fp, filename_w, L"w");
#else
// POSIX:将 wchar_t* 转为 char*(此处简化,假设文件名只含 ASCII 或系统 UTF-8)
// 实际项目中可用 wcstombs 或 iconv 进行转换
char filename_mb[256];
wcstombs(filename_mb, filename_w, sizeof(filename_mb));
fp = fopen(filename_mb, "w");
err = (fp == NULL) ? EIO : 0;
#endif
if (err != 0 || fp == NULL) {
char errbuf[128];
fprintf(stderr, "[ERROR] 打开文件失败: %s\n", safe_strerror(errno, errbuf, sizeof(errbuf)));
return;
}
fprintf(fp, "这是 C 标准库写入的文本,支持中文内容。\n");
fprintf(fp, "编译日期: %s\n", __DATE__);
fclose(fp);
printf("[OK] 文本文件写入成功\n");
}
// 读文本文件
void read_text_file(const wchar_t* filename_w) {
FILE* fp = NULL;
errno_t err = 0;
#ifdef _MSC_VER
err = _wfopen_s(&fp, filename_w, L"r");
#else
char filename_mb[256];
wcstombs(filename_mb, filename_w, sizeof(filename_mb));
fp = fopen(filename_mb, "r");
err = (fp == NULL) ? EIO : 0;
#endif
if (err != 0 || fp == NULL) {
char errbuf[128];
fprintf(stderr, "[ERROR] 打开文件失败: %s\n", safe_strerror(errno, errbuf, sizeof(errbuf)));
return;
}
char buffer[256];
printf("[READ] 文件内容:\n");
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf(" %s", buffer);
}
fclose(fp);
}
int main() {
// 使用宽字符串支持中文文件名(Windows 下为 UTF-16,POSIX 下会被转码)
wchar_t filename[] = L"c_文本测试.txt";
write_text_file(filename);
read_text_file(filename);
return 0;
}
6.2 C++ 风格完整示例(C++17 filesystem 完美处理 Unicode)
/**
* file_io_cpp.cpp - C++ 风格文件操作完整示例
*
* 跨平台设计思路:
* 1. 使用 std::filesystem::path 透明处理 Unicode 路径,无需手动转码
* 2. RAII 自动管理资源,避免忘记 fclose
* 3. 错误处理同时支持流状态检查和异常捕获
* 4. 使用条件编译处理 strerror_s / strerror 差异(但 C++ 中更推荐用 std::error_code)
*
* 编译指令:
* Windows (VS2022): 直接编译 (C++17)
* macOS (Clang): g++ -std=c++17 file_io_cpp.cpp -o file_io_cpp (无需 -lstdc++fs)
* Linux (GCC): g++ -std=c++17 file_io_cpp.cpp -o file_io_cpp -lstdc++fs
* (若 macOS 提示链接错误,尝试 g++ -std=c++17 file_io_cpp.cpp -lc++fs -o file_io_cpp)
*/
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <filesystem>
#include <iomanip>
namespace fs = std::filesystem;
// ========== 辅助:跨平台错误信息 ==========
// C++ 中更推荐使用 std::error_code,但为演示 strerror 处理,保留此函数
static std::string get_error_message(int err) {
#ifdef _MSC_VER
char buf[128];
if (strerror_s(buf, sizeof(buf), err) == 0) {
return std::string(buf);
}
return "Unknown error";
#else
// POSIX 下使用 strerror(注意:非线程安全,但演示足够)
return std::string(strerror(err));
#endif
}
// ========== 文本文件操作(Unicode 文件名) ==========
void text_file_demo() {
std::cout << "\n=== 文本文件操作 ===" << std::endl;
// fs::path 自动处理编码:Windows 转 UTF-16,Linux/macOS 保持 UTF-8
fs::path filepath = fs::path("cpp_文本") / "测试.txt";
// 写入
std::ofstream outFile(filepath);
if (!outFile.is_open()) {
std::cerr << "[ERROR] 无法打开文件写入" << std::endl;
return;
}
outFile << "Hello, C++ File I/O! 你好,世界!\n";
outFile << "这是第二行,包含中文。\n";
outFile << "整数: " << 42 << ", 浮点数: " << 3.14159 << "\n";
outFile.close();
std::cout << "[OK] 文本文件写入成功: " << filepath.string() << std::endl;
// 读取
std::ifstream inFile(filepath);
if (!inFile.is_open()) {
std::cerr << "[ERROR] 无法打开文件读取" << std::endl;
return;
}
std::cout << "[READ] 读取内容:\n";
std::string line;
while (std::getline(inFile, line)) {
std::cout << " " << line << std::endl;
}
inFile.close();
}
// ========== 二进制文件操作 ==========
struct PlayerData {
int id;
float health;
float position[3];
char name[32];
};
void binary_file_demo() {
std::cout << "\n=== 二进制文件操作 ===" << std::endl;
fs::path filepath = "player.dat";
// 写入
PlayerData player = {1001, 85.5f, {10.0f, 20.0f, 30.0f}, "Hero"};
std::ofstream outFile(filepath, std::ios::binary);
if (!outFile.is_open()) {
std::cerr << "[ERROR] 无法打开二进制文件写入" << std::endl;
return;
}
outFile.write(reinterpret_cast<const char*>(&player), sizeof(PlayerData));
outFile.close();
std::cout << "[OK] 二进制文件写入成功: " << filepath.string() << std::endl;
// 读取
PlayerData loaded;
std::ifstream inFile(filepath, std::ios::binary);
if (!inFile.is_open()) {
std::cerr << "[ERROR] 无法打开二进制文件读取" << std::endl;
return;
}
inFile.read(reinterpret_cast<char*>(&loaded), sizeof(PlayerData));
inFile.close();
std::cout << "[READ] 玩家数据:\n";
std::cout << " ID: " << loaded.id << "\n";
std::cout << " Health: " << loaded.health << "\n";
std::cout << " Position: (" << loaded.position[0] << ", "
<< loaded.position[1] << ", " << loaded.position[2] << ")\n";
std::cout << " Name: " << loaded.name << std::endl;
}
// ========== 追加与随机访问 ==========
void append_and_seek_demo() {
std::cout << "\n=== 追加与随机访问 ===" << std::endl;
fs::path logpath = "log.txt";
// 追加模式
std::ofstream appendFile(logpath, std::ios::app);
if (appendFile.is_open()) {
appendFile << "[LOG] 程序启动于 " << __TIME__ << "\n";
appendFile.close();
std::cout << "[OK] 日志追加成功" << std::endl;
}
// 读写模式 + 随机访问
fs::path datapath = "data.txt";
std::fstream rwFile(datapath, std::ios::in | std::ios::out | std::ios::trunc);
if (!rwFile.is_open()) {
std::cerr << "[ERROR] 无法打开文件" << std::endl;
return;
}
rwFile << "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
rwFile.seekp(5, std::ios::beg); // 定位到第 5 个字符
rwFile << "12345";
rwFile.close();
// 读取验证
std::ifstream readFile(datapath);
std::string content;
readFile >> content;
std::cout << "[READ] 修改后的内容: " << content << std::endl;
// 输出: ABCDE12345FGHIJKLMNOPQRSTUVWXYZ
}
// ========== filesystem 目录操作 ==========
void filesystem_demo() {
std::cout << "\n=== filesystem 目录操作 ===" << std::endl;
fs::path root = "测试文件夹";
// 创建目录
fs::create_directory(root);
std::cout << "[OK] 创建目录: " << root.string() << std::endl;
// 创建嵌套目录
fs::create_directories(root / "sub" / "subsub");
std::cout << "[OK] 创建嵌套目录: " << (root / "sub" / "subsub").string() << std::endl;
// 创建测试文件
std::ofstream(root / "文件1.txt") << "File 1 内容";
std::ofstream(root / "文件2.txt") << "File 2 内容";
std::ofstream(root / "sub" / "笔记.txt") << "笔记内容";
// 遍历目录(递归)
std::cout << "[INFO] 遍历 " << root.string() << ":\n";
for (const auto& entry : fs::recursive_directory_iterator(root)) {
std::cout << " " << (fs::is_directory(entry) ? "[DIR]" : "[FILE]")
<< " " << entry.path().filename().string() << std::endl;
}
// 文件信息
auto file1 = root / "文件1.txt";
if (fs::exists(file1)) {
auto size = fs::file_size(file1);
std::cout << "[INFO] 文件1.txt 大小: " << size << " 字节" << std::endl;
}
// 清理
fs::remove_all(root);
std::cout << "[OK] 清理完成" << std::endl;
}
// ========== 错误处理(含 strerror 跨平台适配) ==========
void error_handling_demo() {
std::cout << "\n=== 错误处理 ===" << std::endl;
fs::path missing = "不存在的文件.txt";
std::ifstream file(missing);
if (file.fail()) {
std::cerr << "[ERROR] 打开失败: " << get_error_message(errno) << std::endl;
}
// 使用 exceptions() 抛出异常
std::ifstream file2;
file2.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
file2.open(missing.string());
} catch (const std::ios_base::failure& e) {
std::cerr << "[ERROR] 异常捕获: " << e.what() << std::endl;
}
}
int main() {
std::cout << "========== C++ 文件操作完整示例 ==========" << std::endl;
text_file_demo();
binary_file_demo();
append_and_seek_demo();
filesystem_demo();
error_handling_demo();
std::cout << "\n========== 所有示例执行完毕 ==========" << std::endl;
return 0;
}
七、方法对比表格
| 维度 | C 标准库 (fopen) | C 安全函数 (fopen_s) | C++ fstream + filesystem | POSIX 系统调用 | Win32 API |
|---|---|---|---|---|---|
| 跨平台性 | 完全跨平台 | 仅 MSVC 原生支持 | 完全跨平台 (C++17) | Linux/macOS 专用 | Windows 专用 |
| Unicode 文件名支持 | 依赖系统代码页 (Windows 易乱码) | _wfopen_s 可支持 |
原生支持 (fs::path) |
需 UTF-8 语言环境 | 原生 UTF-16 (CreateFileW) |
| C++ 风格 (RAII) | 否 | 否 | 是 | 否 | 否 |
| 错误信息精度 | 模糊 (NULL) | 精确 (errno_t) | 精确 (failbit/异常) | 精确 (errno) | 精确 (GetLastError) |
| 参数校验 | 否 | 是 | 是 | 否 | 是 |
| 缓冲区安全 | 需手动保证 | 内置校验 | 内置校验 | 需手动保证 | 需手动保证 |
| 二进制支持 | 需指定 “b” | 需指定 “b” | 需指定 binary | 是 | 是 |
| 文件共享控制 | 否 | 否 | 否 | 是 (fcntl) | 是 (共享参数) |
| 学习曲线 | 低 | 中 | 低 | 中高 | 高 |
| 推荐场景 | 纯 C 项目、嵌入式 | Windows 专用 C 项目 | 现代跨平台 C++ 项目 | 需要底层控制 | Windows 底层开发 |
八、总结与建议
-
跨平台 C++ 项目首选
std::fstream+std::filesystem
RAII 自动回收资源,fs::path帮你挡掉 Windows / Linux 路径编码差异。处理中文路径时,直接用fs::path构造,无需手动转码。 -
纯 C 项目,按平台选 API
- Windows + VS2022:用
_wfopen_s(宽字符)处理 Unicode 路径,避免 ANSI 代码页乱码。 - Linux/macOS + GCC/Clang:用
fopen,确保系统LANG环境变量为en_US.UTF-8或zh_CN.UTF-8。 - 条件编译是处理这类差异的标准做法。特别提醒:macOS 的 Clang 不识别
errno_t,需在 POSIX 分支中手动typedef int errno_t;。
- Windows + VS2022:用
-
别迷信
_s函数,也别无视警告
C11 Annex K 的实现不是强制性的。在跨平台代码里,我会在 MSVC 下调用strerror_s,在 POSIX 下调strerror,同时用_CRT_SECURE_NO_WARNINGS抑制冗余告警,保持代码干净。 -
关于 Unicode 内容的读写
文件内容编码与文件名编码是两回事。写入中文内容时,建议统一使用 UTF-8 编码 存储文本文件(Linux 默认,Windows 上很多编辑器也支持)。C++ 的std::string存储 UTF-8 字节序列是安全的,std::wstring则对应 UTF-16(Windows)或 UTF-32(Linux)。若要在 Windows 控制台正确显示中文,记得调用SetConsoleOutputCP(CP_UTF8)。 -
二进制模式别漏掉
跨平台处理二进制数据时,ios::binary或"b"模式必须显式指定。否则 Windows 会对\n和0x1A做特殊转换,破坏数据。 -
macOS 编译小贴士
- C 代码:直接
gcc -std=c11 file_io_c.c,无需额外链接。 - C++ 代码:
g++ -std=c++17 file_io_cpp.cpp,一般无需-lstdc++fs(Apple Clang 内置支持),若遇链接错误,可尝试-lc++fs。 - 若遇到
errno_t未定义,请参照本文 C 代码中的typedef int errno_t;修复。
- C 代码:直接
更多推荐



所有评论(0)