文章目录
- 第一节:文件操作原理
- 1. C文件概述
- 2. 文件指针介绍
- 第二节:文件的打开与关闭
- 1. 文件打开与关闭常用函数
- 2. 代码实战
- 第三节:文件的读写
- 1. fread函数与fwrite函数
- 2. fgets函数与fputs函数
- 第四节:文件位置指针偏移
- 1. fseek函数
- 2. ftell函数
第一节:文件操作原理
1. C文件概述
程序执行时就称为进程,进程运行过程中的数据均在内存中。需要存储运算后的数据时,就需要使用文件。这样程序下次启动后,就可以直接从文件中读取数据。
文件是指存储在外部介质(如磁盘、磁带)上的数据集合。操作系统是以文件为单位对数据进行管理的。
说明:输入和输出缓冲区实际上是同一个,这里为了形象化表示而分开了。
C语言对文件的处理方法如下:
缓冲文件系统:系统自动地在内存中为每个正在使用的文件开辟一个缓冲区。用缓冲区文件系统进行的输入/输出称为高级磁盘输入/输出。
非缓冲区文件系统:系统不自动开辟确定大小的缓冲区,而由程序为每个文件设定缓冲区。用非缓冲区文件系统进行的输入/输出称为低阶输入/输出。
缓冲区原理:
缓冲区其实就是一段内存空间,分为读缓冲、写缓冲。C语言缓冲的三种特性如下:
- 全缓冲。在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写操作。
- 行缓冲。在这种情况下,当在输入和输出中遇到换行符时,将执行真正的I/O操作,这是,我们输入的字符先放到缓冲区中,等按下回车键换行时才进行实际的I/O操作。典型代表是标准输入缓冲区(stdin)和标准输出缓冲区(stdout)。
- 不带缓冲。也就是不进行缓冲,标准出错情况(stderr)是典型代表,这使得出错信息可以直接尽快地显示出来。
2. 文件指针介绍
打开一个文件后,我们会得到一个FILE*类型的文件指针fp,然后通过该文件指针对文件进行操作。FILE是一个结构体类型,源码如下:
struct _iobuf {
char *_ptr; // 下一个要被读取的字符地址
int _cnt; // 剩余的字符,若是输入缓冲区,则表示缓冲区中还有多少个字符未被读取
char *_base; // 缓冲区基地址
int _flag; // 读写状态标志位
int _file; // 问价描述符
int _charbuf;
int _bufsiz; // 缓冲区大小
char *_tmpfname;
};
typedef struct _iobuf FILE;
说明:fp是一个指向FILE类型结构体的指针变量。可以使fp指向某个文件的结构体变量,从而通过该结构体变量中的文件信息来访问该文件。
第二节:文件的打开与关闭
1. 文件打开与关闭常用函数
fopen函数用于打开由fname(文件名)指定的文件,并返回一个关联该文件的流。如果发生错误,那么fopen返回NULL,mode(方式)用于决定文件的用途(如输入、输出等),具体形式如下:
FILE *fopen(const char *fname,const char *mode);
常用的mode参数及其各自的意义如下所示:
mode(方式) | 意义 |
---|---|
r | 打开一个用于读取的文本文件 |
w | 创建一个用于写入的文本文件,如果存在会清空文件 |
rb | 打开一个用于读取的二进制文件 |
wb | 创建一个用于写入的二进制文件,如果存在会清空文件 |
r+ | 打开一个用于读/写的文本文件 |
w+ | 创建一个用于读/写的二进制文件,如果存在会清空文件 |
rb+ | 打开一个用于读/写的二进制文件 |
wb+ | 创建一个用于读/写的二进制文件,如果存在会清空文件 |
fclose函数用于关闭给出的文件流,并释放已关联到的流的所有缓冲区。fclose执行成功时返回0,否则返回EOF。具体形式如下:
int fclose(FILE *stream);
fputc函数用于将字符ch的值输出到fp指向的文件中,如果输出成功,那么返回输出的字符;如果输出失败,那么返回EOF,具体形式如下:
int fputc(int ch,FILE *stream);
fgetc函数用于从指定的文件中读入一个字符,该文件必须是以读或写方式打开的。如果读取一个字符成功,那么赋给ch。如果遇到文件结束符,那么返回文件结束标志EOF,具体形式如下:
int fgetc(FILE *stream);
2. 代码实战
#include <stdio.h>
int main() {
FILE *fp; // 文件类型指针
int i;
char c;
fp= fopen("file.txt","r+");
if(NULL==fp){
perror("fopen");
return -1;
}
while ((c= fgetc(fp))!=EOF){
printf("%c",c);
}
printf("\n");
i= fputc('H',fp);
if(EOF==i){
perror("fputc");
}
fclose(fp);
return 0;
}
# file.txt
helloworld
F:\Computer\Project\practice\22\22.2-File\cmake-build-debug\22_2_File.exe
helloworld
进程已结束,退出代码为 0
当执行完fputc函数后,实际file.txt的内容为:
helloworldH
说明:perror函数得到打开失败的原因(对于定位函数失败的原因,常用perror函数)。如果未新建一个文件,即文件不存在,那么会出现如下如所示的失败提示。
注意:perror函数必须紧跟着失败的函数。因为perror函数时读取错误码来分析失败原因的,执行了其他函数错误码会被改为零。
文件打开成功后,使用fgetc函数可以读取文件的每个字符,然后循环打印整个文件,读到文件结尾时返回EOF,所以通过判断返回值是否等于EOF就可以确定是否读到文件结尾。注意要在自己新建的file.txt文件中先填写一些内容。
第三节:文件的读写
1. fread函数与fwrite函数
fread函数与fwrite函数具体形式如下:
int fread(void *buffer,size_t size,size_t num,FILE *stream);
int fwrite(const void *buffer,size_t size,size_t count,FILE *stream);
其中buffer是一个指针,对fread来说它是读入数据的存放地址,对fwrite来说它是输出数据的地址(均指起始地址);size是要读写的字节数;count是要进行读写多少size字节的数据项;fp是文件型指针;fread函数的返回值是读取的内容数量,fwrite写成功后的返回值是已写对象的数量。
#include <stdio.h>
#include <string.h>
int main() {
char buf[20]="hello\nworld";
FILE *fp;
int ret;
fp= fopen("file.txt","r+"); // 或者写成rb+
if(NULL==fp){
perror("fopen");
return -1;
}
// fwrite和fread不要同时执行
// ret= fwrite(buf,sizeof(char), strlen(buf),fp); // 把buf中的字符写入文件
ret= fread(buf, sizeof(char), sizeof(buf)-1,fp);
puts(buf);
fclose(fp);
return 0;
}
F:\Computer\Project\practice\22\22.4-fread-fwrite\cmake-build-debug\22_4_fread_fwrite.exe
hello
world
进程已结束,退出代码为 0
fread和fwrite函数既可以以文本方式对文件进行读写,又可以以二进制方式对文件进行读写。
说明:以“r+”即文本方式打开文件进行读写时,项文件内写入的是字符串"hello\nworld",会发现是12个字节。
原因:向文本文件中写入"\n"时实际存入磁盘的是"\r\n",这是Windows系统的底层实现所决定的(Mac和Linux不会)。以文本的方式写入,一定要以文本的方式读出。
如果以“rb+”二进制方式写入"hello\nworld",会发现还是11个字节。
通过以上差异,我们可以知道:对于字符型数据,如果是以文本方式写入的内容,那么一定要以文本方式读取;如果是以二进制方式写入的内容,那么一定要以二进制方式读取,不能混用。
而对于整型数,浮点数,一定要以二进制方式"rb+"方式打开文件,二进制方式下内存中存储的是什么,写入文件的就是什么,是一致的。
#include <stdio.h>
int main() {
FILE *fp;
int i=123456;
int ret;
fp= fopen("file.txt","rb+");
if(NULL==fp){
perror("fopen");
return -1;
}
// 向文件中写入整型数,如果我们双击打开文件会发现乱码,因为打开文件都是以字符格式取解析的
// ret= fwrite(&i, sizeof(int),1,fp);
i=0;
fread(&i, sizeof(int),1,fp);
printf("i=%d\n",i);
fclose(fp);
return 0;
}
F:\Computer\Project\practice\22\22.3-int-float\cmake-build-debug\22_3_int_float.exe
i=123456
进程已结束,退出代码为 0
2. fgets函数与fputs函数
函数fgets从给出的文件流中读取[num-1]个字符,并且把它们存储到str(字符串)中。fgets在达到末行时停止,fgets成功时返回str(字符串),失败时返回NULL,读到文件结尾时返回NULL。具体形式如下:
char *fgets(char *str,int num,FILE *stream);
fputs函数把str(字符串)指向的字符写到给出的输出流。成功时返回非负数,失败时返回EOF,具体形式如下:
int puts(const char *str,FILE *stream);
#include <stdio.h>
int main() {
char buf[20]={0}; // 用于存储读取数据
FILE *fp;
fp= fopen("file.txt","r+");
if(NULL==fp){
perror("fopen");
return -1;
}
while (fgets(buf, sizeof(buf),fp)!=NULL){ // 读到文件结尾时,fgets返回NULL
printf("%s",buf); // 打印输出某一行的内容
}
return 0;
}
F:\Computer\Project\practice\22\22.4-fgets-fputs\cmake-build-debug\22_4_fgets_fputs.exe
hello
world
how are you
进程已结束,退出代码为 0
使用fgets函数,我们可以一次读取文件的一行,这样就可以轻松地统计文件的行数。
注意:在一些机试题目中,用于fgets函数的buf不能过小,否则可能无法读取"\n",导致统计出错。
fputs函数向文件中写一个字符串,不会额外写入"\n",可以不用fputs,掌握fwrite即可。
第四节:文件位置指针偏移
1. fseek函数
fseek函数的功能是改变文件的位置指针。具体形式如下:
int fseek(FILE *stream,long offset,int origin);
// fseek(文件类型指针,位移量,起始点)
起始点的说明:
- 文件开头 SEEK_SET 0
- 当前位置 SEEK_CUR 1
- 文件末尾 SEEK_END 2
位移量是指以起始点为基点,向前移动的字节数。一般要求为long型。
fseek函数调用成功时返回零,调用失败时返回非零。
2. ftell函数
ftell函数返回stream(流)当前的文件位置,发生错误时返回-1,放我们想知道位置指针距离文件开头的位置时,就需要用到ftell函数,具体形式如下:
long ftell(FILE *stream);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
FILE *fp;
char str[20]="hello\nworld";
int len=0; // 用于保存字符串长度
long pos;
int ret;
fp= fopen("file.txt","r+");
if(NULL==fp){
perror("fopen");
return -1;
}
len= strlen(str); // 保存字符串长度
ret= fwrite(str, sizeof(char),len,fp); // 写入字符串到文件中
ret= fseek(fp,-5,SEEK_CUR); // 从当前位置向前偏移5个字节
if(ret!=0){
perror("fseek");
fclose(fp);
return -1;
}
pos= ftell(fp); // 获取位置指针举例文件开头的距离
printf("now pos=%ld\n",pos);
memset(str,0, sizeof(str)); // 把str清空
ret= fread(str, sizeof(char), sizeof(str),fp); // 这时候会读到字符串world
printf("%s\n",str);
fclose(fp);
return 0;
}
F:\Computer\Project\practice\22\23.5-ftell-fseek\cmake-build-debug\23_5_ftell_fseek.exe
now pos=7
world
进程已结束,退出代码为 0
说明:为什么pos=7,因为Windows多保存了一个"\r"。