C语言文件操作全解:从打开关闭到读写函数与错误处理
C语言中的文件操作涉及磁盘文件的读取与写入,主要分为程序文件和数据文件。本文将系统性梳理文件定义、FILE结构体、打开与关闭流程、各类读写函数的使用方法,以及如何准确判断文件结束和发生错误。
文件的定义与分类
在程序设计中,通常接触两类文件:
- 程序文件:包括源程序文件(后缀为.c)、目标文件(Windows下为.obj)和可执行文件(.exe)。
- 数据文件:文件的内容不一定是程序,而是供运行时读写的数据。例如程序运行需要从中读取数据的输入文件,或向其中输出内容的日志文件。
根据存储格式的不同,文件又分为:
- 文本文件:数据以ASCII码形式存储,肉眼可直接阅读。
- 二进制文件:数据在内存中按二进制形式直接存储到外存,若不进行转换,其内容无法直接阅读。
文件名的组成
为了唯一标识一个文件,文件名由三部分组成:文件路径 + 文件名主干 + 文件后缀。
例如:c:\code\test.txt
文件指针与FILE结构体
在内存中,每个被使用的文件都会开辟一个“文件信息区”,存放文件的名称、状态、当前位置等信息。这些信息保存在一个名为 FILE 的结构体变量中。
不同编译器的 FILE 结构体内部细节可能不同(如Visual Studio 2019中定义为指向占位符的结构),但使用者无需关心具体字段。我们通常通过 FILE* 类型的指针来维护这个结构体,从而关联并访问文件。
FILE *pf;
pf 是指向 FILE 类型数据的指针变量,通过它即可找到关联的文件及其信息区。
文件的打开与关闭
ANSI C标准规定使用 fopen 打开文件,使用 fclose 关闭文件。打开成功时,返回指向该文件的 FILE* 指针;失败则返回 NULL。
路径写法
相对路径(相对于当前代码所在目录):. 表示当前路径,.. 表示上一级路径。
FILE* p = fopen("../test.txt","r");
绝对路径需明确指定盘符,注意在字符串中转义反斜杠 \\。
fopen("C:\\2012_code\\84\\test\\test1.txt", "r");
打开方式(mode)详解
文件打开方式决定了后续的操作权限及文件不存在时的行为:
| 模式 | 含义 | 若文件不存在 |
|---|---|---|
"r"(只读) |
打开已存在的文本文件 | 出错,返回 NULL |
"w"(只写) |
打开用于输出的文本文件 | 创建新文件,原有内容清空 |
"a"(追加) |
向文本文件尾部添加数据 | 创建新文件 |
"rb"(只读) |
打开已存在的二进制文件 | 出错,返回 NULL |
"wb"(只写) |
打开用于输出的二进制文件 | 创建新文件,原有内容清空 |
"ab"(追加) |
向二进制文件尾部添加数据 | 创建新文件 |
"r+"(读写) |
打开用于读写的文本文件 | 出错,返回 NULL |
"w+"(读写) |
创建新文件用于读写 | 创建新文件 |
"a+"(读写) |
打开或创建文件,末尾追加,也可读 | 创建新文件 |
安全检查
使用文件前必须进行空指针检查,以防打开失败导致程序崩溃:
FILE* pf = fopen("text.txt","r");
if(pf == NULL)
{
printf("%s\n", strerror(errno)); // 输出错误原因
return 0;
}
fclose(pf); // 操作完毕后关闭文件
pf = NULL; // 置为空指针,防止悬垂引用
文件的顺序读写
常用的读写函数针对不同类型的流(stdin/stdout)或文件进行操作。键盘对应标准输入流 stdin,屏幕对应标准输出流 stdout,它们本质上也是 FILE 类型。
| 功能 | 函数原型 | 适用对象 |
|---|---|---|
| 字符输入 | int fgetc(FILE* stream) |
所有输入流 |
| 字符输出 | int fputc(int c, FILE* stream) |
所有输出流 |
| 文本行输入 | char* fgets(char* s, int n, FILE* stream) |
所有输入流 |
| 文本行输出 | int fputs(const char* s, FILE* stream) |
所有输出流 |
| 格式化输入 | int fscanf(FILE* stream, ...) |
所有输入流 |
| 格式化输出 | int fprintf(FILE* stream, ...) |
所有输出流 |
| 二进制输入 | size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) |
文件/流 |
| 二进制输出 | size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream) |
文件/流 |
fread返回值说明
fread 返回实际读取的项数(元素个数)。如果发生错误或到达文件末尾,返回的值可能小于请求的项数。建议使用 feof() 或 ferror() 区分是EOF还是错误。
格式化函数的对比
fscanf/fprintf 是在标准 scanf/printf 基础上增加文件指针参数,针对文件流进行操作。
sscanf/sprintf 则是针对内存中的字符串缓冲区(String Buffer)进行格式化读写,常用于记录日志或字符串处理。
文件随机访问
除了顺序读写,还可以利用 fseek 定位文件指针,实现随机访问。
fseek:定位文件指针
int fseek(FILE* stream, long offset, int origin);
offset:偏移量(字节为单位),正数向后,负数向前。origin:起始位置,可选常量:SEEK_SET(文件头)、SEEK_CUR(当前位置)、SEEK_END(文件尾)。
ftell:获取当前位置
long ftell(FILE* stream);
返回当前文件指针相对于文件起始位置的字节偏移量。可用于计算文件大小(调用 fseek(pf, 0, SEEK_END) 后调用 ftell)。
rewind:重置指针
void rewind(FILE* stream);
相当于将文件指针重置到文件开头,即 fseek(stream, 0, SEEK_SET)。
文件结束判断与错误检测
这是C语言文件操作中最易出错的环节。不能用 feof() 的返回值直接作为循环结束条件,因为 feof() 只有在“尝试读取超过文件末尾”后才返回非零值。
正确判断文件结束的方法
- 文本文件:判断读取函数的返回值。
fgetc:返回EOF表示结束或错误。fgets:返回NULL表示结束或错误。fscanf:返回值小于要求的字段数表示结束。
fread:返回值 < count 表示到达末尾。
错误判断函数
当需要区分“正常结束”和“异常错误”时,需结合使用以下函数:
- perror():自动打印传入的字符串、冒号及操作系统对应的错误信息。
- ferror(FILE*):若流发生错误返回非零值,否则返回0。
- feof(FILE*):若到达文件末尾返回非零值,否则返回0。
综合示例
int main()
{
FILE* pf = fopen("test.txt","r");
if(pf == NULL)
{
perror("Open file failed");
return 0;
}
int ch = 0;
// 循环读取直到遇到 EOF 或错误
while((ch = fgetc(pf)) != EOF)
{
fputc(ch, stdout); // 将字符输出到屏幕
}
// 此时循环退出,判断是读完还是出错
if(ferror(pf))
{
printf("File read error occurred.\n");
}
else if(feof(pf))
{
printf("Reached end of file normally.\n");
}
fclose(pf);
pf = NULL;
return 0;
}
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













