目录
一、文件与文件流
在操作系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。有些输入输出函数不需要你指明到底读写哪个文件,系统已经为它们设置了默认的文件,当然也可以更改,例如让 printf 向磁盘上的文件输出数据。
文件 | 硬件设备 |
stdin | 标准输入文件,一般指键盘;scanf()、getchar() 等函数默认从 stdin 获取输入。 |
stdout | 标准输出文件,一般指显示器;printf()、putchar() 等函数默认向 stdout 输出数据。 |
stderr | 标准错误文件,一般指显示器;perror() 等函数默认向 stderr 输出数据(后续会讲到)。 |
stdprn | 标准打印文件,一般指打印机。 |
操作文件的正确流程为:打开文件 --> 读写文件 --> 关闭文件。文件在进行读写操作之前要先打开,使用完毕要关闭。
文件流
所有的文件(保存在磁盘)都要载入内存才能处理,所有的数据必须写入文件(磁盘)才不会丢失。数据在文件和内存之间传递的过程叫做文件流,类似水从一个地方流动到另一个地方。数据从文件复制到内存的过程叫做输入流,从内存保存到文件的过程叫做输出流。
文件是数据源的一种,除了文件,还有数据库、网络、键盘等;数据传递到内存也就是保存到C语言的变量。我们把数据在数据源和程序(内存)之间传递的过程叫做数据流(Data Stream)。相应的,数据从数据源到程序(内存)的过程叫做输入流(Input Stream),从程序(内存)到数据源的过程叫做输出流(Output Stream)。
输入输出(Input output,IO)是指程序(内存)与外部设备(键盘、显示器、磁盘、其他计算机等)进行交互的操作。几乎所有的程序都有输入与输出操作,如从键盘上读取数据,从本地或网络上的文件读取数据或写入数据等。通过输入和输出操作可以从外界接收信息,或者是把信息传递给外界。
打开文件就是打开了一个流。
二、文件的打开与关闭
文件的打开
所谓打开文件,就是获取文件的有关信息,例如文件名、文件状态、当前读写位置等,这些信息会被保存到一个 FILE 类型的结构体变量中。
格式:
FILE *fp = fopen("filename", "mode");
filename为文件名(包括文件路径),mode为打开方式,它们都是字符串。fopen() 会获取文件信息,包括文件名、文件状态、当前读写位置等,并将这些信息保存到一个FILE类型的结构体变量中,然后将该变量的地址返回。定义一个 FILE 类型的指针 fp 接收 fopen() 的返回值,FILE是在stdio.h头文件中定义的一个结构体,用来保存文件信息。
打开方式(mode):
r(read):读,凡用“r”打开一个文件时,该文件必须已经存在
w(write):写
a(append):追加
t(text):文本文件,可省略不写
b(banary):二进制文件,如果没有“b”字符则文件以文本方式打开
+:读和写
打开方式 | 说明 |
r | 以只读方式打开文件,只允许读取,不允许写入。该文件必须存在。 |
r+ | 以读/写方式打开文件,允许读取和写入。该文件必须存在。 |
rb+ | 以读/写方式打开一个二进制文件,允许读/写数据。 |
rt+ | 以读/写方式打开一个文本文件,允许读和写。 |
w | 以只写方式打开文件,若文件存在则长度清为0,即该文件内容消失,若不存在则创建该文件。 |
w+ | 以读/写方式打开文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。 |
a | 以追加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留(EOF符保留)。 |
a+ | 以追加方式打开可读/写的文件。若文件不存在,则会建立该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(原来的EOF符 不保留)。 |
wb | 以只写方式打开或新建一个二进制文件,只允许写数据。 |
wb+ | 以读/写方式打开或建立一个二进制文件,允许读和写。 |
wt+ | 以读/写方式打开或建立一个文本文件,允许读写。 |
at+ | 以读/写方式打开一个文本文件,允许读或在文本末追加数据。 |
ab+ | 以读/写方式打开一个二进制文件,允许读或在文件末追加数据。 |
在打开一个文件时,如果出错,fopen将返回一个空指针值NULL。在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理:
if( (fp=fopen("C:\\demo.txt","rb")) == NULL ){
printf("Cannot open file, press any key to exit!\n");
getch();
exit(1);
}
把一个文本文件读入内存时,要将ASCII码转换成二进制码,而把文件以文本方式写入磁盘时,也要把二进制码转换成ASCII码,因此文本文件的读写要花费较多的转换时间。对二进制文件的读写不存在这种转换。
标准输入文件 stdin(键盘)、标准输出文件 stdout(显示器)、标准错误文件 stderr(显示器)是由系统打开的,可直接使用。
被写入的文件可以用写、读写、追加方式打开,用写或读写方式打开一个已存在的文件时将清除原有的文件内容,并将写入的字符放在文件开头。如需保留原有文件内容,并把写入的字符放在文件末尾,就必须以追加方式打开文件。不管以何种方式打开,被写入的文件若不存在时则创建该文件。
文件的关闭
关闭文件就是断开与文件之间的联系,释放结构体变量,同时禁止再对该文件进行操作。
格式:
fclose(fp);
fp 为文件指针。
文件正常关闭时,fclose() 的返回值为0,如果返回非零值则表示有错误发生。
文本文件和二进制文件的区别
二进制和文本模式的区别就在于对换行符和一些非可见字符的转化上,如非必要,是使用二进制读取会比较安全一些。
文件可以分为两类:二进制文件和字符(文本)文件。从物理上讲二进制文件和字符文件没有区别,都是以二进制的形式保存在磁盘上。但是它们在文件的组织形式上不一样,二进制文件有文件头(File Header),用以表明文件的大小、类型等信息,程序在处理二进制文件时一般会先分析文件头,判断文件是否合法,也就是说,文件头后面的数据才是程序真正要处理的;字符文件没有文件头,第一个字节就是要显示的内容。
拿 BMP 文件举例,其头部的长度较为固定,前2字节用来记录文件为BMP格式,接下来的8个字节用来记录文件长度,再接下来的4字节用来记录 BMP 文件头的长度。
文本文件是基于字符编码的,常见的编码方式有 ASCII、UNICODE、UTF-8 等;指定编码方式后,每个字节(也可以是每两个、三个字节)所表示的字符是一样的,任何程序都可以正确读取。
二进制文件是自定义编码的,也就是说,你可以根据具体程序指定每个字节(或者每两个、三个字节)代表什么意思。例如,A 程序是图像编辑器,指定 01001111 代表红色,B 程序是视频播放器,它把 01001111 理解为快进,显然是不对的。
所以,字符文件是通用的,任何程序只要按照对应的编码方式打开都可以正确显示,二进制文件只有特定的程序才能处理。
文本文件和二进制文件都可以在屏幕上显示,但是二进制文件的内容无法读懂,大部分是乱码。
在C语言中,二进制方式很简单,读文件时,会原封不动的读出文件的全部內容,写的時候,也是把內存缓冲区的內容原封不动的写到文件中。
而对文本文件的处理就不一样了。Windows 和 DOS 下的文本文件以CRLF(0X0D 0X0A)作为换行符,而C语言本身以LF(0X0A)作为换行符,所以以文本方式写入数据时,会将LF(0X0A)替换为CRLF(0X0D 0X0A),而读取数据时又会替换回来。
CR(0X0D)表示回车符,也就是 '\r';CL(0X0A)表示换行符,也就是 '\n'。
在Linux和其他一些系统中,文本文件的换行符就是LF(0X0A),与C语言的换行符一样。所以也就沒有了文本方式和二进制方式的区分,使不使用'b'标志都是一样的。
这是由于不同操作系统对文本文件换行符的定义,和C语言中换行符的定义有所不同而造成的。在Windows下,C语言的输入输出函数会自动进行 CRLF 和 LF 的转换,而Linux等就不必了。
另外,以文本方式打开时,遇到结束符CTRLZ(0x1A)就认为文件已经结束。所以,若使用文本方式打开二进制文件,就很容易出现文件读不完整,或內容不对的错误。即使是用文本方式打开文本文件,也要谨慎使用,比如复制文件,就不应该使用文本方式。
三、顺序读写
以字符形式读写文件
读取:
fgetc ( fp );
写入:
feof ( ch, fp );
fp 为文件指针。fgetc() 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF。
在文件内部有一个位置指针,用来指向当前读写到的位置。在文件打开时,该指针总是指向文件的第一个字节。文件每读写一次,位置指针就会向后移动一次,所以可以连续多次使用函数读写字符。
读写文件:
#include <conio.h>
int main(){
FILE *fp;
char ch;
//打开文件
if( (fp=fopen("C:\C\demo.txt","rt")) == NULL ){
printf("Cannot open file, press any key to exit!");
getch();
exit(1);
}
//每次读取一个字节,直到读取完毕
while( (ch=fgetc(fp)) != EOF ){
putchar(ch);
}
//从键盘输入一行字符,写入文件
printf("Input a string:\n");
while ( (ch=getchar()) != '\n' ){
fputc(ch,fp);
}
//关闭文件
fclose(fp);
}
以字符串形式读写文件
读取:
fgets ( str, n, fp );
写入:
fputs( str, fp );
str 为字符数组,n 为要读取的字符数目,fp 为文件指针。
读取到的字符串会在末尾自动添加 '\0',如果希望读取 m 个字符,n 的值应该为 m+1。
fgets() 能够读取到换行符,而 gets() 会忽略换行符
读写文件:
#include <conio.h>
#define N 100
int main(){
FILE *fp;
char str[N+1] = {0}, strTemp[N];
//打开文件
if( (fp=fopen("C:\\demo.txt","rt")) == NULL ){
printf("Cannot open file, press any key to exit!\n");
getch();
exit(1);
}
//一行一行地读取文件
while(fgets(str, N, fp) != NULL){
printf("%s", str);
}
//输入字符串并追加到文件末尾
printf("Input a string:");
gets(strTemp);
strcat(str, "\n");
strcat(str, strTemp);
fputs(str, fp);
//关闭文件
fclose(fp);
}
以数据块的形式读写文件
所谓块数据,也就是若干个字节的数据,可以是一个字符,字符串,多行数据,并没有什么限制。
读取:
fread ( ptr, size, count, fp );
写入:
fwrite ( ptr, size, count, fp );
ptr 为内存区块的指针,为要读写的数据。
size:表示每个数据块的字节数。
count:表示要读写的数据块的块数。
fp:表示文件指针。
fwrite()/fread() 函数直接操作字节,建议使用二进制方式打开文件。因为数据以二进制形式写入文件,所以打开文件后内容一般无法阅读。文件的后缀不一定是 .txt,它可以是任意的,也可以是自己命名的。
读写文件:
#define N 5
int main(){
int a[N], b[N];
int i, size = sizeof(int);
FILE *fp;
//打开文件
if( (fp=fopen("C:\\demo.txt", "rb+")) == NULL ){
printf("Cannot open file, press any key to exit!\n");
getch();
exit(1);
}
//从键盘输入数据 并保存到数组a
for(i=0; i<N; i++){
scanf("%d", &a[i]);
}
//将数组a的内容写入到文件
fwrite(a, size, N, fp);
//将文件中的位置指针重新定位到文件开头
rewind(fp);
//从文件读取内容并保存到数组b
fread(b, size, N, fp);
//关闭文件
fclose(fp);
}
格式化读写
fscanf() 和 fprintf() 是格式化读写函数,但读写对象不是键盘和显示器,而是磁盘文件。
相比于 scanf() 和 printf() ,它们仅仅多了一个 fp 参数。
fscanf ( fp, format, ... );
fprintf ( fp, format, ... );
fp 为文件指针,format 为格式控制字符串,... 表示参数列表。
用 fprintf() 和 fscanf() 函数读写配置文件、日志文件会非常方便,不但程序能够识别,用户也可以看懂,还可以手动修改。
如果将 fp 设置为 stdin,那么 fscanf() 函数与 scanf 的作用相同;设置为 stdout,那么 fprintf() 函数与 printf 的作用相同。
读写文件:
#define N 2
int main(){
FILE *fp;
int i;
int a[N], b[N];
//打开文件
if ((fp = fopen("D:\\demo.txt", "wt+")) == NULL) {
printf("Cannot open file, press any key exit!");
getch();
exit(1);
}
//从键盘读入数据,保存到数组a
printf("Input :\n");
for (i = 0; i<N; i++) {
scanf("%d", &a[i]);
}
//将数组a中的数据写入到文件
for (i = 0; i<N; i++) {
fprintf(fp, "%d", a[i]);
}
//重置文件指针
rewind(fp);
//从文件中读取数据,保存到boyb
for (i = 0; i<N; i++) {
fscanf(fp, "%d", &b[i]);
}
//将boyb中的数据输出到显示器
for (i = 0; i<N; i++) {
printf("%d\n", b[i]);
}
//关闭文件
fclose(fp);
}
四、随机读写
实现随机读写的关键是要按要求移动文件内部位置指针,这称为文件的定位。
文件定位函数:
将位置指针移动到文件开头:rewind ( fp );
将位置指针移动到任意位置:fseek ( fp, offset, origin );
fp 为文件指针,也就是被移动的文件。
offset 为偏移量,也就是要移动的字节数。
origin 为起始位置,也就是从何处开始计算偏移量。起始位置有三种,分别为文件开头、当前位置和文件末尾,可以用常量名或常量值表示。
起始点 | 常量名 | 常量值 |
文件开头 | SEEK_SET | 0 |
当前位置 | SEEK_CUR | 1 |
文件末尾 | SEEK_END | 2 |
fseek() 一般用于二进制文件,在文本文件中由于要进行转换,计算的位置有时会出错。
在移动位置指针之后,就可以用前面介绍的任何一种读写函数进行读写了。由于是二进制文件,因此常用 fread() 和 fwrite() 读写。
从键盘输入三组学生信息,保存到文件中,然后读取第二个学生的信息:
#include<stdio.h>
#define N 3
struct stu{
char name[10]; //姓名
int num; //学号
int age; //年龄
float score; //成绩
}boys[N], boy, *pboys;
int main(){
FILE *fp;
int i;
pboys = boys;
//打开文件
if( (fp=fopen("d:\\demo.txt", "wb+")) == NULL ){
printf("Cannot open file, press any key to exit!\n");
getch();
exit(1);
}
//输入信息
printf("Input data:\n");
for(i=0; i<N; i++,pboys++){
scanf("%s %d %d %f", pboys->name, &pboys->num, &pboys->age, &pboys->score);
}
fwrite(boys, sizeof(struct stu), N, fp);
//移动位置指针
fseek(fp, sizeof(struct stu), SEEK_SET);
//读取一条学生信息并保存到boy
fread(&boy, sizeof(struct stu), 1, fp);
}
五、获取文件大小
实际开发中,有时候需要先获取文件大小再进行下一步操作。
先使用 fseek() 将文件内部指针定位到文件末尾,再使用 ftell() 返回内部指针距离文件开头的字节数,这个返回值就等于文件的大小。
ftell() 函数:
ftell ( fp );
ftell() 函数用来获取文件内部指针(位置指针)距离文件开头的字节数。
fp 要以二进制方式打开,如果以文本方式打开,函数的返回值可能没有意义。
实现代码:
/**
* 获取文件长度
* @param fp 要获取长度的文件
* @return 文件的长度
**/
long fsize(FILE *fp){
long n;
fpos_t fpos; //当前位置
fgetpos(fp, &fpos); //获取当前位置
fseek(fp, 0, SEEK_END);
n = ftell(fp);
fsetpos(fp,&fpos); //恢复之前的位置
return n;
}
fpos_t 是在 stdio.h 中定义的结构体,用来保存文件的内部指针。fgetpos() 用来获取文件内部指针,fsetpos() 用来设置文件内部指针。
六、复制、插入、删除、更改文件内容
我们平时所见的文件,例如 txt、doc、mp4 等,文件内容是按照从头到尾的顺序依次存储在磁盘上的,就像排起一条长长的队伍,称为顺序文件。除了顺序文件,还有索引文件、散列文件等,一般用于特殊领域,例如数据库、高效文件系统等。
顺序文件的存储结构决定了它能够高效读取内容,但不能够随意插入、删除和修改内容。例如在文件开头插入100个字节的数据,那么原来文件的所有内容都要向后移动100个字节,这不仅是非常低效的操作,而且还可能覆盖其他文件。因此C语言没有提供插入、删除、修改文件内容的函数,要想实现这些功能,只能自己编写函数。
以插入数据为例,假设原来文件的大小为 1000 字节,现在要求在500字节处插入用户输入的字符串,那么可以这样来实现:
1) 创建一个临时文件,将后面500字节的内容复制到临时文件;
2) 将原来文件的内部指针调整到500字节处,写入字符串;
3) 再将临时文件中的内容写入到原来的文件。
删除数据时,也是类似的思路。假设原来文件大小为1000字节,名称为 demo.mp4,现在要求在500字节处往后删除100字节的数据,那么可以这样来实现:
1) 创建一个临时文件,先将前500字节的数据复制到临时文件,再将600字节之后的所有内容复制到临时文件;
2) 删除原来的文件,并创建一个新文件,命名为 demo.mp4;
3) 将临时文件中的所有数据复制到 demo.mp4。
修改数据时,如果新数据和旧数据长度相同,那么设置好内部指针,直接写入即可;如果新数据比旧数据长,相当于增加新内容,思路和插入数据类似;如果新数据比旧数据短,相当于减少内容,思路和删除数据类似。实际开发中,我们往往会保持新旧数据长度一致,以减少编程的工作量。
文件复制函数
复制所有内容
代码说明:
实现文件复制的主要思路是:开辟一个缓冲区,不断从原文件中读取内容到缓冲区,每读取完一次就将缓冲区中的内容写入到新建的文件,直到把原文件的内容读取完。
fread ( ptr, size, count, fp ); //如果让参数 size 等于1,那么返回的就是读取的字节数。
fopen()一定要以二进制的形式打开文件,不能以文本形式打开,否则系统会对文件进行一些处理,如果是文本文件,像.txt等,可能没有问题,但如果是其他格式的文件,像.mp4等,复制后就会出错,无法读取。
通过fread()函数,每次从 fileRead 文件中读取 bufferLen 个字节,放到缓冲区,再通过fwrite()函数将缓冲区的内容写入fileWrite文件。
正常情况下,每次会读取bufferLen个字节,即readCount=bufferLen;如果文件大小不足bufferLen个字节,或者读取到文件末尾,实际读取到的字节就会小于bufferLen,即readCount<bufferLen。所以通过fwrite()写入文件时,应该以readCount为准。
代码实现:
char fileRead[100]; // 要复制的文件名
char fileWrite[100]; // 复制后的文件名
// 获取用户输入
printf("要复制的文件:");
scanf("%s", fileRead);
printf("将文件复制到:");
scanf("%s", fileWrite);
// 进行复制操作
if( copyFile(fileRead, fileWrite) ) printf("文件复制成功!\n");
else printf("文件复制失败!\n");
/**
* 文件复制函数
* @param fileRead 要复制的文件
* @param fileWrite 复制后文件的保存路径
* @return int 1: 复制成功;2: 复制失败
**/
int copyFile(char *fileRead, char *fileWrite){
FILE *fpRead; // 指向要复制的文件
FILE *fpWrite; // 指向复制后的文件
int bufferLen = 1024*4; // 缓冲区长度
char *buffer = (char*)malloc(bufferLen); // 开辟缓存
int readCount; // 实际读取的字节数
if( (fpRead=fopen(fileRead, "rb"))==NULL||(fpWrite=fopen(fileWrite, "wb") )==NULL){
printf("Cannot open file, press any key to exit!\n");
getch();
exit(1);
}
// 不断从fileRead读取内容,放在缓冲区,再将缓冲区的内容写入fileWrite
while( (readCount=fread(buffer, 1, bufferLen, fpRead)) > 0 ){
fwrite(buffer, readCount, 1, fpWrite);
}
free(buffer);
fclose(fpRead);
fclose(fpWrite);
return 1;
}
复制任意长度的内容
代码说明:
该函数可以将原文件任意位置的任意长度的内容复制到目标文件的任意位置,非常灵活。
如果希望实现复制所有内容,可以这样调用: fcopy(fSource, 0, -1, fTarget, 0);
/**
* 文件复制函数
* @param fSource 要复制的原文件
* @param offsetSource 原文件的位置偏移(相对文件开头),也就是从哪里开始复制
* @param len 要复制的内容长度,小于0表示复制offsetSource后边的所有内容
* @param fTarget 目标文件,也就是将文件复制到哪里
* @param offsetTarget 目标文件的位置偏移,也就是复制到目标文件的什么位置
* @return 成功复制的字节数
**/
long fcopy(FILE *fSource, long offsetSource, long len, FILE *fTarget, long offsetTarget){
int bufferLen = 1024*4; // 缓冲区长度
char *buffer = (char*)malloc(bufferLen); // 开辟缓存
int readCount; // 每次调用fread()读取的字节数
long nBytes = 0; //总共复制了多少个字节
int n = 0; //需要调用多少次fread()函数
int i; //循环控制变量
fseek(fSource, offsetSource, SEEK_SET);
fseek(fTarget, offsetTarget, SEEK_SET);
if(len<0){ //复制所有内容
while( (readCount=fread(buffer, 1, bufferLen, fSource)) > 0 ){
nBytes += readCount;
fwrite(buffer, readCount, 1, fTarget);
}
}else{ //复制len个字节的内容
n = (int)ceil((double)((double)len/bufferLen));
for(i=1; i<=n; i++){
if(len-nBytes < bufferLen){ bufferLen = len-nBytes; }
readCount = fread(buffer, 1, bufferLen, fSource);
fwrite(buffer, readCount, 1, fTarget);
nBytes += readCount;
}
}
fflush(fTarget);
free(buffer);
return nBytes;
}
文件内容插入函数
代码说明:
fsize() 是在自定义的函数,用来获取文件大小(以字节计)。
如果是在文件末尾,就非常简单了,直接用 fwrite() 写入即可。如果从文件开头或中间插入,就得创建临时文件。
tmpfile() 函数用来创建一个临时的二进制文件,可以读取和写入数据,相当于 fopen() 函数以"wb+"方式打开文件。该临时文件不会和当前已存在的任何文件重名,并且会在调用 fclose() 后或程序结束后自动删除。
文件内容删除函数
代码说明:
freopen() 以"w+"方式打开文件时,如果有同名的文件存在,那么先将文件内容删除,作为一个新文件对待。
/**
* 删除文件内容
* @param fp 要删除内容的文件
* @param offset 偏移量(相对文件开头),也就是从哪里开始删除
* @param len 要删除内容的长度
**/
int fdelete(FILE *fp, long offset, int len){
long fileSize = getFileSize(fp);
FILE *fpTemp;
if(offset>fileSize || offset<0 || len<0){ //用来判断传入的参数是否合法
return -1;
}
fpTemp = tmpfile();
fcopy(fp, 0, offset, fpTemp, 0); //将前offset字节的数据复制到临时文件
fcopy(fp, offset+len, -1, fpTemp, offset); //将offset+len之后的所有内容都复制到临时文件
freopen(FILENAME, "wb+", fp ); //重新打开文件
fcopy(fpTemp, 0, -1, fp, 0);
fclose(fpTemp);
return 0;
}
七、FILE结构体以及缓冲区
在C语言中,用一个指针变量指向一个文件,这个指针称为文件指针。通过文件指针就可对它所指的文件进行各种操作。
定义文件指针的一般形式为:
FILE *fp;
这里的FILE实际上是在stdio.h中定义的一个结构体,该结构体中含有文件名、文件状态和文件当前位置等信息,fopen 返回的就是FILE类型的指针。
注意:FILE是文件缓冲区的结构,fp也是指向文件缓冲区的指针。
不同编译器 stdio.h 头文件中对 FILE 的定义略有差异,这里以标准C举例说明:
typedef struct _iobuf {
int cnt; // 剩余的字符,如果是输入缓冲区,那么就表示缓冲区中还有多少个字符未被读取
char *ptr; // 下一个要被读取的字符的地址
char *base; // 缓冲区基地址
int flag; // 读写状态标志位
int fd; // 文件描述符
// 其他成员
} FILE;
当我们从键盘输入数据的时候,数据并不是直接被我们得到,而是放在了缓冲区中,然后我们从缓冲区中得到我们想要的数据 。如果我们通过setbuf()或setvbuf()函数将缓冲区设置10个字节的大小,而我们从键盘输入了20个字节大小的数据,这样前10个数据会放在缓冲区中,而剩下的那10个字节大小的数据就暂时放在了输入流中,等待下去往缓冲区中放入!
向缓冲区中放入了10个字节大小的数据,FILE结构体中的 cnt 变为了10 ,说明此时缓冲区中有10个字节大小的数据可以读,同时我们假设缓冲区的基地址也就是 base 是0x00428e60 ,它是不变的 ,而此时 ptr 的值也为0x00428e60 ,表示从0x00428e60这个位置开始读取数据,当我们从缓冲区中读取5个数据的时候,cnt 变为了5 ,表示缓冲区还有5个数据可以读,ptr 则变为了0x0042e865表示下次应该从这个位置开始读取缓冲区中的数据 ,如果接下来我们再读取5个数据的时候,cnt 则变为了0 ,表示缓冲区中已经没有任何数据了,ptr 变为了0x0042869表示下次应该从这个位置开始从缓冲区中读取数据,但是此时缓冲区中已经没有任何数据了,所以要将输入流中的剩下的那10个数据放进来,这样缓冲区中又有了10个数据,此时 cnt 变为了10 ,ptr 的值变为了0x00428e60 ,这是因为当缓冲区中没有任何数据的时候要将 ptr 这个值进行一下刷新,使其指向缓冲区的基地址也就是0x0042e860这个值!因为下次要从这个位置开始读取数据!
缓冲区的刷新就是将指针 ptr 变为缓冲区的基地址 ,同时 cnt 的值变为0 ,因为缓冲区刷新后里面是没有数据的!
当我们从键盘输入字符串的时候需要敲一下回车键才能够将这个字符串送入到缓冲区中,那么敲入的这个回车键(\r)会被转换为一个换行符\n,这个换行符\n也会被存储在缓冲区中并且被当成一个字符来计算!比如我们在键盘上敲下了123456这个字符串,然后敲一下回车键(\r)将这个字符串送入了缓冲区中,那么此时缓冲区中的字节个数是7 ,而不是6。