文件操作是C语言的基础,但凡做项目都需要涉及到文件操作,因为我们需要将信息保存,否则一个进程结束后数据都丢失了。
虽然目前文件操作基本上都是使用数据库(毕竟功能强大),但是基本的文件操作还是有意义的(比如课设)。
因此,本篇文章将详细地讲解在C语言中的文件操作,相信会有很大的帮助。
目录
一、文件定义
文件:一般指存储在外部介质(如磁盘磁带)上数据的集合。
操作系统的角度
操作系统是以文件为单位对系统进行管理的。linux下万物皆文件。
从操作系统的角度看,每一个与主机相连的输入输出(IO)设备看做是一个文件。
- 输入文件:终端、键盘
- 输出文件:显示器、打印机
文件的分类
按数据的组织形式,可将文件分为两类:
- 文本文件(ASCII文件):每一个字节放一个ASCII代码。
- 二进制文件:把数据按其在内存中的存储形式原样输出到磁盘中存放。
下图是整数10000在两种方式下的存储形式。在内存中,10000的二进制源码为:0010011100010000,转化为十进制 = 2^13 + 2^10 + 2^9 + 2^8 + 2^4 = 8192 + 1024 + 512 + 256 + 16 = 10000。
若是按二进制文件存储,这以0010011100010000存储;若是按文本文件存储,则将整数10000分为5个字节,每个字节存储一个ASCII代码。
ASCII文件和二进制文件的比较
- ASCII文件便于对字符进行逐个处理,也便于输出字符。缺点:占存储空间较多,而且要花费转换时间。
- 二进制文件可以节省外存空间和转换时间。缺点:一个字节并不对应一个字符,不能直接输出字符形式。
- 因此,如果数据经常在外存和内存间输入输出,使用二进制保存更好。
文件处理方法的分类
- 缓冲文件系统:系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区。用缓冲文件系统
进行的输入输出又称为高级磁盘输入输出。 - 系统不自动开辟确定大小的缓冲区,而由程序为每个文件设定缓冲区。用非缓冲文件系统进行的输入输出又称为低级输入输出系统。
二、文件操作
2.1 文件类型指针(FILE)
头文件:#include <stdio.h>
文件指针定义:
FILE *fp;
在缓冲文件系统中,每个被使用的文件都要在内存中开辟一FILE类型的区,存放文件的有关信息。
文件指针指向一个结构,该结构包含以下信息:
- 文件名
- 文件的当前位置
- 文件是否正在读或写
- 是否出错
- 是否达到文件末尾
2.2 文件函数
文件函数可以分为4类:打开和关闭文件、文件定位、文件状态、文件读写
(1)打开和关闭文件
函数名 | 功能 |
---|---|
fopen() | 打开文件 |
fclose() | 关闭文件 |
(2)文件定位
函数名 | 功能 |
---|---|
fseek() | 改变文件位置指针的位置 |
rewind() | 使文件位置指针指向文件开头 |
ftell() | 返回文件位置指针的当前值 |
(3)文件状态
函数名 | 功能 |
---|---|
feof() | 如果到文件末尾,函数值为真 |
ferror() | 如果对文件操作出错,函数值为真 |
clearerr() | 是feof()和ferror()函数值置零 |
(4)文件读写
函数名 | 功能 |
---|---|
fgetc() / getc() | 从指定文件取得一个字符 |
fputc() / putc() | 把字符输出到指定文件 |
fgets() | 从指定文件读取字符串 |
fputs() | 把字符串输出到指定文件 |
getw() | 从指定文件读取一个字(int) |
putw() | 把一个字输出到指定文件 |
fread() | 从指定文件读取数据项 |
fwrite() | 把数据项写到指定文件中 |
fscanf() | 从指定文件按格式输入数据 |
fprintf() | 按指定格式将数据写到指定文件中 |
三、打开和关闭文件
3.1 fopen——打开文件
函数定义:FILE *fopen(char *pname, char *mode)
函数说明:pname是文件名,mode是打开文件的方式
调用fopen文件,需要知道文件名(文件路径),并确定使用文件的方式,然后将返回值赋给文件类型指针。
FILE *fp;
fp = fopen(文件名,使用文件方式);
文件使用方式
文本文件:
文件使用方式 | 含义 |
---|---|
“r” | 只读,打开一个文本文件 |
“w” | 只写,打开一个文本文件 |
“r+” | 读写,打开一个文本文件 |
“w+” | 读写,创建一个新的文本文件 |
“a” | 追加,向文本文件尾增加数据 |
“a+” | 追加,为读写打开一个文本文件 |
二进制文件:
可以看到,二进制文件只是在文本文件的基础上加上了b
。
文件使用方式 | 含义 |
---|---|
“rb” | 只读,打开一个二进制文件 |
“wb” | 只写,打开一个二进制文件 |
“rb+” | 读写,打开一个二进制文件 |
“wb+” | 读写,创建一个新的二进制文件 |
“ab” | 追加,向二进制文件尾增加数据 |
“ab+” | 追加,为读写打开一个二进制文件 |
具体案例
创建一个名为data.txt
的文件,读写方式打开。
FILE *fp;
fp = fopen("data.txt", "w+");//"w+"是以读写的方式打开一个文件
然而,实际工程中,我们可能需要反复对一个文件打开和关闭,若是不存在则创建,用w+
;存在则直接打开,用r+
。所以,可以封装一个函数:打开一个文件,如果不存在则创建,存在则直接打开。
/*
输入参数:文件路径
返回值:文件类型指针
功能:打开一个文件,如果不存在则创建
*/
FILE* fileOpen(const char *pName)
{
FILE* fp;
fp = fopen(pName, "r+");//首先,以读写的方式打开一个文件
if (fp == NULL)
{
fp = fopen(pName, "w+");//如果文件不存在,创建一个文件
}
return fp;
}
3.2 fclose(关闭文件)
函数定义:int fclose(FILE *fp);
函数说明:fp是一个已经打开文件的文件指针
返回值:关闭成功返回值为0;否则返回EOF(-1)
fclose(fp);
四、文件定位
-
FILE结构提供了一个指针,用于跟踪发生I/O操作位置。
-
每当从流中读取或写入一个字符,当前活动指针(即curp)就会向前移动。
4.1 ftell——当前指针位置
long int ftell(FILE *fp);
返回值:成功时返回文件指针位置,否则返回-1L。
4.2 rewind——指向开头
void rewind(FILE * fp) ;
功能说明:将文件位置指示器置于文件开头,相当于reset(重置)文件指针。
4.3 fseek——重定位指针
修改文件指针的位置:从origin的位置开始往后偏移offset个字节。
其中,参数origin
有三个值:
origin | 文件文字 |
---|---|
SEEK_SET 或 0 | 文件开头 |
SEEK_CUR 或 1 | 当前文件指针段的位置 |
SEEK_END 或 2 | 文件末尾 |
(1)偏移到文件末尾
fseek(fp, 0, SEEK_END);//文件指针偏移到文件末尾
(2)偏移到文件开头
fseek(fp, 0, SEEK_SET);
(3)从当前位置往后偏移4个字节
fseek(fp, 4, SEEK_CUR);
(4)计算文件字节数
fseek(fp, 0, SEEK_END);
length = ftell(fp);//length即为当前文件指针离文件开头的字节数
五、文件状态
5.1 feof——文件末尾判断
int feof(FILE *fp);
返回值:
- 文件末尾:非0;
- 非文件末尾:0。
feof()是ANSI C提供的标准函数,用于识别二进制文件是否结束,当然也可以用于文本文件。因为在文本文件中,用EOF(-1)作为文件的结束符,ASCII码不会出现**-1**所以对文本来说可行。但是,在二进制文件中-1往往可能是一个有意义的数据,所以在二进制文件使用feof()。
5.2 ferror——文件错误判断
int ferror(FILE * fp);
返回值:
- 出错:非0;
- 未出错:0。
在执行fopen()函数时,ferror()函数的初始值自动设置为0,表示文件正确。
在调用一个输入输出函数时,需要立即检查ferror()函数的值,否则信息丢失。
5.3 clearerr——清除标志
void clearerr(FILE *fp);
函数作用:使文件错误标志(ferror)和文件结束标志(feof)置位0。
只要出现错误标志,该标志就会一直保留,直到以下情况清除:
- 调用clearerr()函数;
- 调用rewind()函数;
- 调用任意一个输入输出函数。
六、文件读写
6.1 fgetc——从文件读取一个字符
int fgetc(FILE *fp);
函数说明:从文件读取一个字符,该字符在返回值中返回。
返回值:
- 读取成功,返回该字符;
- 读取失败(文件末尾),返回EOF。
读取文本文件的所有内容
char ch = fgetc(fp);while (ch != EOF){ putchar(ch); ch = fgetc(fp);}
6.2 fputc——将字符输出到文件
int fputc(int ch, FILE *fp);
函数说明:将字符ch写到文件中。
返回值:
- 写入成功,返回写入的字符;
- 写入失败,返回EOF。
往文件写入一个字符’1’,ASCII码为49,所以在中显示为1。
fputc('1', fp);
6.3 fgets——从文件读取一个字符串
char* fgets(char* str, int n, FILE* fp);
函数说明:从fp指向的文件读取n-1个字符,将它们存放在str对应的字符数组区域,加上’\0’。
返回值:
- 正常返回:字符串str的首地址
- 异常返回:返回一个NULL值,这时候用feof()和ferror()判断是文件末尾还是发生错误。
读取fp文件中9个字符到str。
char *str;
fgets(str, 10, fp);
如果,读取的字符数大于文件中的字符数,并不会报错,而是根据文件的内容输出到str。
6.4 fputs——将字符串写入文件
int fputs(char *str, FILE *fp);
函数说明:把str字符串写入到fp所指的文件中去。
返回值:
- 输入成功,返回值为0;
- 输入失败,返回值为EOF。
在文件末尾输入字符串"china"
fseek(fp, 0, SEEK_END);
int ans = fputs("china", fp);
6.5 fread——无格式读函数
功能描述:fread是无格式读函数,用于向文件读出整块的数据。经常用fwrite函数写入,读取时用fread函数。
返回值:成功时返回读出的单元数,否则返回0。
6.6 fwrite——无格式写函数
功能说明:fwrite是无格式写函数,用于向文件写入整块的数据。最有价值的一个应用就是读写用户定义的数据类型,尤其是结构。
返回值:成功时返回写入的单元数,否则返回0
实际案例
fwrite
将结构体写入文件,而后用fread
可以将结构体读出文件。
//结构定义
typedef struct user
{
int id;
char name[10];
}User;
int main()
{
FILE *fp;
fp = fileOpen("user5.txt");
User user1[3] = {{1001, "aaa"}, {1002, "bbb"}, {1003, "ccc"}};
fseek(fp, 0, SEEK_END);
fwrite(&user1, sizeof(User), 3, fp);//写入3个User结构体
User user2[3];
rewind(fp);
int size = fread(&user2, sizeof(User), 3, fp);//读出三个User结构体
for (int i = 0; i < size; i++)
{
printf("%d %s\n", user2[i].id, user2[i].name);
}
fclose(fp);
system("pause");
return 0;
}
6.7 fscanf——从文件格式化输入
fscanf(FILE *fp, char *format, arg_list);
- fp:文件指针
- format:将arg_list的变量按format的格式从fp指定的文件中输入。若fp为stdin,就是从键盘等设备输入。
- arg_list:写入文件的变量列表。
所以,fscanf和scanf无太大区别,参数格式参考scanf。
读取文件fp的字符串,直到遇到\n
:
char *str;
fscanf(fp, "%s", str);
printf("%s\n", str);
6.8 fprintf——从文件格式化输出
fprintf(FILE *fp, char *format, arg_list);
- fp:文件指针
- format:将arg_list的变量按format的格式输出到fp指定的文件中。若fp为stdout,就是输出到屏幕文件,即在屏幕中显示。
- arg_list:写入文件的变量列表。
将一字符串输出至文件:
int grade = 80;
fprintf(fp, "grade = %d\n", grade);
而后用fscanf
读取:
char *str;
int num;
fscanf(fp, "%s = %d", str, &num);
printf("%s\n", str);
printf("%d\n", num);
由此可见,fprintf
写入的字符串grade是有字符串结束符'\0'
的。
七、总结
最后,总结一下:fopen、fclose
是基础操作,fseek
移动指针好用,ftell
可算长度以便偏移,feof
文件末尾,fread、fwrite
常用。
好了,本篇文章结束,感谢观看!
觉得不错的同学,一键三连