C/C++文件操作详解与总结
C/C++文件操作
C/C++ 文件操作详解与总结
文件操作是程序与外部世界持久化交互数据的关键。C 语言和 C++ 都提供了各自的文件操作库,C++ 的版本通常更现代、更安全。
一、C 语言文件操作 (stdio.h)
C 语言使用 FILE 结构体和一系列函数来进行文件操作。核心思想是 文件流 和 文件指针。
1. 打开文件:fopen
FILE *fopen(const char *filename, const char *mode);
filename: 文件路径。mode: 打开模式,决定了如何操作文件。"r":只读(文件必须存在)。"w":只写。如果文件存在,内容被清空;如果不存在,则创建。"a":追加。在文件末尾写入,如果文件不存在则创建。"r+":读写(文件必须存在)。"w+":读写。如果文件存在,内容被清空;如果不存在,则创建。"a+":读写。从文件末尾开始读写,如果文件不存在则创建。- 在上述模式后加
b(如"rb","wb+") 表示以二进制模式打开文件。在 Windows 上,这可以防止\n被转换为\r\n。
返回值:成功返回 FILE* 指针,失败返回 NULL。
示例:
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("Error opening file"); // 打印系统错误信息
exit(EXIT_FAILURE);
}
2. 关闭文件:fclose
操作完成后必须关闭文件,释放资源并确保数据写入磁盘。
int fclose(FILE *stream);
返回值:成功返回 0,失败返回 EOF。
3. 读写文件
字符读写
int fgetc(FILE *stream); // 读取一个字符,返回读到的字符(转为int),遇到错误或文件结束返回EOF。
int fputc(int char, FILE *stream); // 写入一个字符,成功返回写入的字符,失败返回EOF。
字符串(行)读写
char *fgets(char *str, int n, FILE *stream); // 读取一行(最多n-1个字符),成功返回str,失败或文件结束返回NULL。
int fputs(const char *str, FILE *stream); // 写入字符串(不自动添加换行符),成功返回非负值,失败返回EOF。
格式化读写
功能强大,类似于 printf 和 scanf。
int fprintf(FILE *stream, const char *format, ...); // 格式化输出到文件
int fscanf(FILE *stream, const char *format, ...); // 从文件格式化输入
示例:
fprintf(fp, "Name: %s, Age: %d\n", "Alice", 25);
char name[20];
int age;
fscanf(fp, "Name: %s, Age: %d", name, &age); // 注意&
数据块读写(二进制操作)
用于读写结构体等二进制数据,效率高。
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
ptr: 数据内存块的指针。size: 每个数据元素的大小(字节),常用sizeof获取。count: 要读写多少个这样的元素。stream: 文件指针。
返回值:成功读写的元素个数(不是字节数!)。
示例(写入和读取一个结构体数组):
typedef struct {
char name[50];
int id;
float score;
} Student;
Student students[2] = {{"Alice", 1, 95.5f}, {"Bob", 2, 88.0f}};
// 写入
FILE *fp = fopen("data.bin", "wb");
if (fp) {
size_t elements_written = fwrite(students, sizeof(Student), 2, fp);
fclose(fp);
}
// 读取
Student read_students[2];
fp = fopen("data.bin", "rb");
if (fp) {
size_t elements_read = fread(read_students, sizeof(Student), 2, fp);
fclose(fp);
}
4. 文件定位
每个打开的文件都有一个内部位置指针,指示当前读写的位置。
int fseek(FILE *stream, long int offset, int origin); // 移动位置指针
void rewind(FILE *stream); // 将位置指针重置到文件开头 (等价于 fseek(fp, 0, SEEK_SET))
long int ftell(FILE *stream); // 获取当前位置指针相对于文件开头的偏移量(字节数)
int feof(FILE *stream); // 检查是否到达文件末尾。注意:只有在尝试读取**失败后**,这个函数才会返回真。
fseek 的 origin 参数:
SEEK_SET:文件开头。SEEK_CUR:当前位置。SEEK_END:文件末尾。
示例:获取文件大小
fseek(fp, 0, SEEK_END); // 跳到文件末尾
long file_size = ftell(fp); // 获取当前位置(即文件大小)
rewind(fp); // 跳回开头准备读取
二、C++ 文件操作 (fstream)
C++ 通过 fstream, ifstream, ofstream 类来处理文件,它们继承自 iostream(cin, cout 的类),因此用法与标准 I/O 高度一致,更面向对象,更安全。
1. 文件流类
ofstream:用于输出(写文件)。ifstream:用于输入(读文件)。fstream:既可输入也可输出。
2. 打开文件
有两种方式:
- 构造函数中直接打开:
#include <fstream>
std::ifstream inFile("input.txt", std::ios::in);
std::ofstream outFile("output.txt", std::ios::out | std::ios::app);
std::fstream ioFile("data.bin", std::ios::in | std::ios::out | std::ios::binary);
- 先创建对象,再用
.open()打开:
std::fstream file;
file.open("data.dat", std::ios::binary | std::ios::in);
if (!file.is_open()) {
// 处理错误
}
打开模式(ios 标志位,可用 | 组合):
std::ios::in:读方式打开。std::ios::out:写方式打开(默认会截断文件内容,类似 C 的"w")。std::ios::app:追加模式(所有写入都在末尾)。std::ios::ate:打开后定位到文件末尾。std::ios::trunc:如果文件存在,先清空(out的默认行为)。std::ios::binary:二进制模式。
注意:fstream 的 out 模式不会自动包含 in 模式,需要同时读写时必须同时指定 in | out。
3. 检查文件是否成功打开
if (!inFile) { // 直接判断流对象的状态
std::cerr << "Failed to open file!" << std::endl;
return;
}
// 或者更明确地
if (!inFile.is_open()) {
// ...
}
4. 关闭文件
文件流对象析构时会自动关闭文件,但可以显式调用 .close()。
outFile.close();
5. 读写文件
得益于运算符重载和继承,读写非常简单直观。
文本文件读写
使用流插入运算符 << 和流提取运算符 >>。
std::ofstream out("test.txt");
out << "Hello, World! " << 42 << std::endl; // 写入
std::ifstream in("test.txt");
std::string str;
int num;
in >> str >> num; // 读取 "Hello," 到str, 42到num。注意:>> 会以空格为分隔符。
std::cout << str << " " << num; // 输出: Hello, 42
使用 getline 读取整行:
std::string line;
while (std::getline(in, line)) { // 逐行读取,直到文件末尾
std::cout << line << std::endl;
}
二进制文件读写
使用 .read() 和 .write() 成员函数。
// 写入
Student s{"Alice", 1, 95.5f};
std::ofstream bOut("data.bin", std::ios::binary);
bOut.write(reinterpret_cast<const char*>(&s), sizeof(Student));
bOut.close();
// 读取
Student s_read;
std::ifstream bIn("data.bin", std::ios::binary);
bIn.read(reinterpret_cast<char*>(&s_read), sizeof(Student));
bIn.close();
注意:必须使用 reinterpret_cast 进行指针类型转换。
6. 文件定位
与 C 语言类似,但有成员函数形式。
std::fstream file("data.bin", std::ios::binary | std::ios::in | std::ios::out);
// 获取当前位置
std::streampos pos = file.tellg(); // tellg() 用于读位置 (get)
// file.tellp() 用于写位置 (put),在单一流中通常两者同步
// 移动位置
file.seekg(offset, origin); // 移动读指针
file.seekp(offset, origin); // 移动写指针
// origin 可以是:
std::ios::beg // 文件开头
std::ios::cur // 当前位置
std::ios::end // 文件末尾
// 示例:跳到文件开头往后10字节处
file.seekg(10, std::ios::beg);
// 示例:跳到文件末尾
file.seekg(0, std::ios::end);
三、总结与对比
| 特性 | C 语言 (stdio.h) | C++ (fstream) |
|---|---|---|
| 核心概念 | 文件指针 (FILE*) | 文件流对象 (fstream, ifstream, ofstream) |
| 打开方式 | fopen(filename, mode) | 构造函数或 .open(),使用 std::ios 标志 |
| 错误处理 | 检查返回值 (NULL, EOF) | 检查流状态 (if (!file) 或 file.fail()) |
| 读写接口 | fprintf, fscanf, fread, fwrite | 流运算符 <<, >>,成员函数 .read(), .write() |
| 关闭文件 | 显式 fclose | 析构函数自动调用或显式 .close() |
| 易用性 | 较低,需要手动管理格式和缓冲区 | 高,与标准 I/O 用法一致,类型安全 |
| 面向对象 | 否 | 是,支持 RAII(资源获取即初始化) |
| 推荐场景 | 需要与 C 代码兼容,或追求极致轻量 | 绝大多数现代 C++ 项目,代码更清晰、安全 |
四、最佳实践与常见陷阱
-
总是检查文件是否成功打开:这是最常见的错误来源。
-
明确文本模式与二进制模式:在 Windows 平台上处理非文本文件(如图片、视频、含特定格式的文本)时,务必使用二进制模式 (
"b"或std::ios::binary) 以避免换行符转换带来的问题。 -
C++ 中优先使用
std::fstream等类:它们更安全,更符合 C++ 的编程风格。 -
理解
fread/fwrite的返回值:它返回的是成功读写的元素个数,而不是字节数。循环读取时应以此作为判断条件。 -
小心
fscanf和>>:它们会跳过空白字符,并且格式错误会导致输入流进入错误状态。读取字符串时可能有缓冲区溢出的风险(应指定宽度,如%99s)。 -
使用 RAII:在 C++ 中,利用局部对象析构自动关闭文件的特性,将文件操作封装在作用域内。
{ // 进入一个作用域
std::ofstream file("output.txt");
file << "Data";
} // 离开作用域,file 析构,文件自动关闭
- 处理路径:使用绝对路径或相对于程序运行当前目录的相对路径。C++17 引入了
<filesystem>库,可以更方便、跨平台地处理路径。
更多推荐
所有评论(0)