【C语言】文件操作全解析
文章目录
- 一、为什么需要文件操作?
- 二、认识文件:不止是磁盘上的存储
- 2.1 程序文件
- 2.2 数据文件
- 2.3 文件名的构成
- 三、文本文件与二进制文件:数据的两种形态
- 3.1 存储方式差异
- 3.2 实例对比:整数10000的存储
- 3.3 二进制文件操作示例
- 四、文件的打开与关闭:操作的开始与结束
- 4.1 流的概念
- 4.2 文件指针
- 4.3 打开与关闭函数
- 4.4 文件打开模式
- 4.5 示例代码
- 五、文件的顺序读写:按部就班的数据处理
- 5.1 常用顺序读写函数
- 5.2 函数对比
- 六、文件的随机读写:自由定位数据位置
- 6.1 fseek函数:定位文件指针
- 6.2 ftell函数:获取当前位置偏移量
- 6.3 rewind函数:重置文件指针
- 七、文件读取结束的判定:正确识别结束条件
- 7.1 feof函数的正确用法
- 7.2 正确的判断方式
- 八、文件缓冲区:提升效率的中间层
- 8.1 缓冲区演示
- 8.2 重要结论
- 总结
在C语言编程中,文件操作是实现数据持久化存储的关键技术。无论是保存用户数据、配置信息还是处理大量数据,都离不开文件操作。本文将详细讲解C语言文件操作的方方面面,从基本概念到实际应用,帮助你全面掌握这一重要技能。
一、为什么需要文件操作?
在程序运行过程中,我们处理的数据通常存储在内存中。然而,内存具有临时性的特点——当程序退出或计算机断电时,内存中的数据会全部丢失。如果我们希望数据能够长期保存,以便下次运行程序时继续使用,就必须使用文件将数据存储到磁盘等外部存储设备中。
简单来说,文件操作让程序的数据拥有了"记忆"能力,是实现数据持久化的核心手段。
二、认识文件:不止是磁盘上的存储
在C语言中,我们通常从功能角度将文件分为两类:
2.1 程序文件
程序文件是用于构成程序本身的文件,包括:
- 源程序文件(.c后缀)
- 目标文件(Windows环境下为.obj后缀)
- 可执行程序(Windows环境下为.exe后缀)
2.2 数据文件
数据文件是程序运行时读写的数据载体,比如:
- 程序需要读取的配置文件
- 程序输出的日志文件
- 存储用户信息的数据库文件
本文重点讨论的是数据文件的操作。
2.3 文件名的构成
一个完整的文件名包含三个部分:
- 文件路径:指示文件在磁盘中的位置
- 文件名主干:文件的核心标识
- 文件后缀:指示文件类型
例如:c:\code\test.txt
中,c:\code\
是路径,test
是文件名主干,.txt
是后缀。
三、文本文件与二进制文件:数据的两种形态
根据数据的存储形式,数据文件可分为文本文件和二进制文件,它们的区别如下:
3.1 存储方式差异
- 二进制文件:数据在内存中以二进制形式存储,不加转换直接输出到外存
- 文本文件:数据在存储前被转换为ASCII码形式,以字符形式存储
3.2 实例对比:整数10000的存储
以整数10000为例:
- 文本文件存储:占用5个字节(每个字符一个字节),存储内容是’1’,‘0’,‘0’,‘0’,'0’的ASCII码
- 二进制文件存储:在VS2019环境下仅占用4个字节,直接存储其二进制形式
00000000 00000000 00100111 00010000
3.3 二进制文件操作示例
#include <stdio.h>
int main()
{int a = 10000;FILE* pf = fopen("test.txt", "wb"); // 以二进制写入模式打开文件fwrite(&a, 4, 1, pf); // 将a以二进制形式写入文件fclose(pf);pf = NULL;return 0;
}
在VS中查看二进制文件时,需要使用二进制编辑器,10000会显示为10270000
。
四、文件的打开与关闭:操作的开始与结束
4.1 流的概念
C语言通过"流"(stream)来操作各种外部设备(包括文件)。流可以想象成"流淌着字符的河",程序通过流与外部设备进行数据交换。
C程序启动时会默认打开3个标准流:
stdin
:标准输入流(通常对应键盘)stdout
:标准输出流(通常对应显示器)stderr
:标准错误流(通常对应显示器)
这就是为什么我们可以直接使用scanf
和printf
函数进行输入输出,而无需手动打开流。
4.2 文件指针
缓冲文件系统中,通过文件指针(FILE*
类型)来管理文件。每个被打开的文件在内存中都有一个对应的文件信息区(结构体),存储文件名、状态、当前位置等信息,文件指针就指向这个结构体。
定义文件指针的方式:
FILE* pf; // 声明一个文件指针变量
4.3 打开与关闭函数
-
打开文件:使用
fopen
函数FILE * fopen ( const char * filename, const char * mode );
-
关闭文件:使用
fclose
函数int fclose ( FILE * stream );
4.4 文件打开模式
文件打开模式决定了对文件的操作权限,常见模式如下:
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“r”(只读) | 打开文本文件用于输入 | 出错 |
“w”(只写) | 打开文本文件用于输出 | 创建新文件 |
“a”(追加) | 向文本文件末尾添加数据 | 创建新文件 |
“rb”(只读) | 打开二进制文件用于输入 | 出错 |
“wb”(只写) | 打开二进制文件用于输出 | 创建新文件 |
“ab”(追加) | 向二进制文件末尾添加数据 | 创建新文件 |
“r+”(读写) | 打开文本文件用于读写 | 出错 |
“w+”(读写) | 创建文本文件用于读写 | 创建新文件 |
“a+”(读写) | 打开文本文件用于读写,从末尾开始 | 创建新文件 |
4.5 示例代码
#include <stdio.h>
int main ()
{FILE * pFile;// 打开文件pFile = fopen ("myfile.txt","w");// 文件操作if (pFile!=NULL){fputs ("fopen example",pFile);// 关闭文件fclose (pFile);}return 0;
}
五、文件的顺序读写:按部就班的数据处理
顺序读写是指从文件的开头到结尾依次进行读写操作,C语言提供了一系列函数实现这一功能:
5.1 常用顺序读写函数
函数名 | 功能 | 适用于 |
---|---|---|
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件 |
fwrite | 二进制输出 | 文件 |
5.2 函数对比
-
输入函数家族:
scanf
:从标准输入流读取格式化数据fscanf
:从指定输入流读取格式化数据sscanf
:从字符串读取格式化数据
-
输出函数家族:
printf
:向标准输出流输出格式化数据fprintf
:向指定输出流输出格式化数据sprintf
:将格式化数据输出到字符串
六、文件的随机读写:自由定位数据位置
随机读写允许程序直接定位到文件的任意位置进行操作,主要通过以下函数实现:
6.1 fseek函数:定位文件指针
根据文件指针的当前位置和偏移量来定位指针:
int fseek ( FILE * stream, long int offset, int origin );
参数origin
可以是:
SEEK_SET
:从文件开头开始计算SEEK_CUR
:从当前位置开始计算SEEK_END
:从文件末尾开始计算
示例:
#include <stdio.h>
int main ()
{FILE * pFile;pFile = fopen ( "example.txt" , "wb" );fputs ( "This is an apple." , pFile );fseek ( pFile , 9 , SEEK_SET ); // 定位到第10个字符位置fputs ( " sam" , pFile ); // 从该位置开始写入fclose ( pFile );return 0;
}
// 最终文件内容为:"This is a sample."
6.2 ftell函数:获取当前位置偏移量
返回文件指针相对于文件起始位置的偏移量:
long int ftell ( FILE * stream );
示例:计算文件大小
#include <stdio.h>
int main ()
{FILE * pFile;long size;pFile = fopen ("myfile.txt","rb");if (pFile==NULL) perror ("Error opening file");else{fseek (pFile, 0, SEEK_END); // 定位到文件末尾size=ftell (pFile); // 获取偏移量,即文件大小fclose (pFile);printf ("Size of myfile.txt: %ld bytes.\n",size);}return 0;
}
6.3 rewind函数:重置文件指针
将文件指针重新定位到文件开头:
void rewind ( FILE * stream );
示例:
#include <stdio.h>
int main ()
{int n;FILE * pFile;char buffer [27];pFile = fopen ("myfile.txt","w+");for ( n='A' ; n<='Z' ; n++)fputc ( n, pFile);rewind (pFile); // 回到文件开头fread (buffer,1,26,pFile);fclose (pFile);buffer[26]='\0';printf(buffer); // 输出ABCDEFGHIJKLMNOPQRSTUVWXYZreturn 0;
}
七、文件读取结束的判定:正确识别结束条件
很多初学者会错误地使用feof
函数来判断文件是否结束,这是需要避免的。
7.1 feof函数的正确用法
feof
函数的作用是:当文件读取结束时,判断结束的原因是否是"遇到文件尾"。它并不能直接用来判断文件是否结束。
7.2 正确的判断方式
-
文本文件:
- 使用
fgetc
时,判断返回值是否为EOF
- 使用
fgets
时,判断返回值是否为NULL
示例:
#include <stdio.h> #include <stdlib.h> int main(void) {int c; // 注意:必须是int类型,以处理EOFFILE* fp = fopen("test.txt", "r");if(!fp) {perror("File opening failed");return EXIT_FAILURE;}// fgetc读取失败或遇到文件结束时返回EOFwhile ((c = fgetc(fp)) != EOF)putchar(c);// 判断结束原因if (ferror(fp))puts("I/O error when reading");else if (feof(fp))puts("End of file reached successfully");fclose(fp); }
- 使用
-
二进制文件:
- 使用
fread
时,判断返回值是否小于实际要读取的个数
示例:
#include <stdio.h> enum { SIZE = 5 }; int main(void) {double a[SIZE] = {1.,2.,3.,4.,5.};FILE *fp = fopen("test.bin", "wb"); // 二进制模式fwrite(a, sizeof *a, SIZE, fp); // 写入数组fclose(fp);double b[SIZE];fp = fopen("test.bin","rb");size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读取数组if(ret_code == SIZE) {puts("Array read successfully, contents: ");for(int n = 0; n < SIZE; ++n) printf("%f ", b[n]);putchar('\n');} else { // 错误处理if (feof(fp))printf("Error reading test.bin: unexpected end of file\n");else if (ferror(fp)) {perror("Error reading test.bin");}}fclose(fp); }
- 使用
八、文件缓冲区:提升效率的中间层
ANSI C标准采用"缓冲文件系统",系统会自动为每个正在使用的文件在内存中开辟一块"文件缓冲区":
- 输出数据时:先送到内存缓冲区,缓冲区满后再一起写入磁盘
- 输入数据时:先从磁盘读取到缓冲区,再从缓冲区送到程序数据区
缓冲区的大小由C编译系统决定。
8.1 缓冲区演示
#include <stdio.h>
#include <windows.h> // VS2019 WIN11环境
int main()
{FILE* pf = fopen("test.txt", "w");fputs("abcdef", pf); // 数据先存入输出缓冲区printf("睡眠10秒-此时打开test.txt,文件无内容\n");Sleep(10000);printf("刷新缓冲区\n");fflush(pf); // 手动刷新缓冲区,数据写入磁盘printf("再睡眠10秒-此时打开test.txt,文件有内容\n");Sleep(10000);fclose(pf); // 关闭文件时也会刷新缓冲区pf = NULL;return 0;
}
8.2 重要结论
由于缓冲区的存在,操作文件时必须注意:
- 及时刷新缓冲区(使用
fflush
函数) - 操作结束后关闭文件(
fclose
会自动刷新缓冲区)
否则可能导致数据未能正确写入文件,造成数据丢失或不一致。
总结
文件操作是C语言编程中的重要技能,掌握它可以让你的程序具备数据持久化能力。本文介绍了文件的基本概念、类型划分、打开关闭、顺序读写、随机读写、结束判定以及缓冲区机制等内容。
在实际编程中,要注意以下几点:
- 始终检查文件是否成功打开
- 操作完成后及时关闭文件
- 正确判断文件读取结束的条件
- 理解并合理利用缓冲区机制