什么是缓冲区?
一块内存区,在输入输出设备和CPU间,用来缓存数据。
比如键盘输入给cpu信息,我输一个字符告诉内核一次,太占用资源,那么就用缓冲区存起来你输入的信息,当达到一定条件时,再上传内核。
这三个stdin、stdout、stderr是缓冲区的句柄:
STDIN 是控制台输入缓冲区的句柄,而 STDOUT 和 STDERR 是控制台活动屏幕缓冲区的句柄
那么什么句柄?
答:句柄是硬件资源指针的引用,一种特殊的指针。
他和指针有什么区别?
答:比如声卡,一堆进程要利用声卡发出声音,那不能都给他们分配一个资源指针,这时候利用句柄实际上也就是一个int值,引用了同一个硬件资源指针,句柄在不同进程中是不一样的,比如在第一个进程中句柄可能为1,在第二个进程中句柄可能为2.
此外缓冲区还有三种缓冲模式,分别是行缓冲、块缓冲、不缓冲。
行缓冲:遇到换行符\n或者缓冲区存满后上交内核层
块缓冲:存满后上交内核层。
不缓冲:来一个字符上交一次内核层。
STDIN和STDOUT就是行缓冲模式,STDERR就是不缓冲模式。
来看几个关于缓冲区的函数
1、清理缓冲区的函数
fflush();
里面的参数填写缓冲区句柄,比如你要清理标准输入流就调用:fflush(stdin);标准输出流同样。
2、FILE指针
3、缓冲区文件打开方法fopen
FILE * fopen(const char * path,const char * mode);
返回值成功返回操作文件的指针,失败返回NULL
参数path:你要打开文件的路径
参数mode:打开方式参考下图
4、缓冲区关闭文件方法fclose
演示
#include <stdio.h>
int main(int argc,char* args[])
{
FILE *fp=fopen("/home/xxx/Desktop/shell/1234","r");
if(fp!=NULL)
{
printf("打开文件成功\n");
}
if(fclose(fp)==0)
{
printf("关闭文件成功\n");
}
}
5、fputc
函数原型:int fputc(int c, FILE *stream)
函数功能:写一个字符到文件中
函数参数:要写入的字符,要写入的文件句柄
函数返回值:成功:字符c,失败: EOF
演示
代码
#include <stdio.h>
int main(int argc,char* args[])
{
//注意!!!!你要往文件中写入内容的话,打开文件的方式必须是可写的,如果是w、w+的话在写入之前会清空文件的内容,a或a+的话则是追加方式。
FILE *fp=fopen("/home/xxx/Desktop/shell/1234","a+");
if(fp!=NULL)
{
printf("打开文件成功\n");
if(fputc('s',fp)!=EOF)
{
printf("写入字符成功\n");
}else
{
printf("写入字符失败\n");
}
}
if(fclose(fp)==0)
{
printf("关闭文件成功\n");
}
}
文件原内容
运行完后
6、fgetc
函数原型:int fgetc(FILE *stream);
函数功能:从文件中读取一个字符
函数参数:要读取的文件句柄
函数返回值:成功:字符c,失败: EOF
演示
代码
#include <stdio.h>
int main(int argc,char* args[])
{
FILE *fp=fopen("/home/xxx/Desktop/shell/1234","a+");
if(fp!=NULL)
{
printf("打开文件成功\n");
int readchar;
if((readchar=fgetc(fp))!=EOF)
{
printf("读取字符成功\n");
printf("读取到的字符为:%c\n",readchar);
}else
{
printf("读取字符失败\n");
}
}
if(fclose(fp)==0)
{
printf("关闭文件成功\n");
}
}
文件内容:
结果
6、feof
函数原型:int feof(FILE *stream);
函数功能:判断文件是否读到末尾
函数参数:文件句柄
函数返回值:文件未结束: 0,文件已结束: 1
演示
我们可以结合fgetc读取文件的所有字符
代码:
#include <stdio.h>
int main(int argc,char* args[])
{
FILE *fp=fopen("/home/xxx/Desktop/shell/1234","a+");
if(fp!=NULL)
{
printf("打开文件成功\n");
int readchar;
while(feof(fp)!=1)
{
if((readchar=fgetc(fp))!=EOF)
{
printf("%c",readchar);
}
}
printf("\n");
}
if(fclose(fp)==0)
{
printf("关闭文件成功\n");
}
}
现象:
下面介绍字符串读写:
7、fgets
函数原型:char * fgets(char * s, int size, FILE * fp);
函数功能:从fp指向的文件中读出一行(最多size-1个字符) 直到出现换行字符、读到文件尾或是已读了size-1 个字符为止,写入s指向的缓冲区。
函数参数:s 保存读取到的字符、size 要读取的字符的个数、fp 为文件流指针
函数返回值:成功返回字符串指针,失败返回NULL
演示
演示中的错误
fgets的函数参数中有字符缓冲区、读取多少字符、文件句柄。那么我们定义一个char*类型的变量,为防止它变为野指针,让他指向NULL,相当于给气球拴根绳。编译运行上述代码现象如下
可以看到段错误,为什么会出现这个错误?
因为函数的功能是给你这个指针的空间中塞入读取到的字符,然而你这个指针指向的空间是NULL所以出现段错误。
如何解决?
可以给指针分配空间,或者改用定义字符数组的方式。
代码
#include <stdio.h>
int main(int argc,char* args[])
{
FILE *fp=fopen("/home/xxx/Desktop/shell/1234","a+");
if(fp!=NULL)
{
printf("打开文件成功\n");
char ss[20];
if(fgets(ss,5,fp)!=NULL)
{
printf("读取成功\n");
printf("读取到的字符串:%s\n",ss);
}
}
if(fclose(fp)==0)
{
printf("关闭文件成功\n");
}
}
现象
8、fputs
函数原型:int fputs(const char *s, FILE *fp)
函数功能:将字符串s写入fp指向的文件中
函数参数:s: 要写入文件的缓冲区指针、fp:要写入的目标文件的流指针
函数返回值:该函数返回一个非负值,如果发生错误则返回 EOF。
演示
代码
#include <stdio.h>
int main(int argc,char* args[])
{
FILE *fp=fopen("/home/xxx/Desktop/shell/1234","a+");
if(fp!=NULL)
{
printf("打开文件成功\n");
char ss[20]="hello world!!!!";
if(fputs(ss,fp)!=EOF)
{
printf("写入成功\n");
}
}
if(fclose(fp)==0)
{
printf("关闭文件成功\n");
}
}
现象
9、块读函数fread
函数原型: size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp);
函数功能:以块的形式读入定义好的数据缓冲区
函数参数:
ptr: 要读入的缓冲区指针, 提前申请好的
size: 要读出的信息单元的大小
nmemb:要读出的信息单元的个数, size* nmemb不大于ptr大小
fp: 要读的文件指针, 提前用fopen打开的
函数返回值:成功返回读取的信息单元的个数,失败: EOF
在我之前的老师讲这个方法时只讲了一种很局限的用法,就是size给1、一个字节一个字节的读取,读取nmemb个。
那这就体现不到这个方法为什么叫块读函数,块读就是一块一块的读,将几个字节合成一块读出来存储到一块,一共读nmemb个块。
接下来我是用一种能现块读的方法演示
首先文件内容有这些
然后我定义一个short数组来接收读到的数据,因为short大小有两个字节所以在读时,参数size(块的大小给2个字节),一共读5个,然后读完之后看一下第一个数据
代码
#include <stdio.h>
int main(int argc,char* args[])
{
FILE *fp=fopen("/home/xxx/Desktop/shell/1234","a+");
if(fp!=NULL)
{
printf("打开文件成功\n");
short ss[5];
if(fread(ss,2,5,fp)!=EOF)
{
printf("读取成功\n");
printf("%d\n",ss[0]);
}
}
if(fclose(fp)==0)
{
printf("关闭文件成功\n");
}
}
现象
可以看到读到了一个较大的数字12081
为什么会是这个数字?
12081(十进制)->0010 1111 0011 0001(二进制)
文件的头两个字符为1、/
1在ascll表中对应49
/在ascll表中对应47
49(十进制)->0011 0001(二进制)
47(十进制)->0010 1111(二进制)
先把1读到低八位,再把/读到高八位。第一个块读取完毕,所以就得到了12081.
10、块写函数fwrite
函数原型: size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *fp);
函数功能:以块的形式写入文件
函数参数:
ptr: 要写入的缓冲区指针, 提前申请好的
size: 要写入的信息单元的大小 一般情况下, size设为1
nmemb:要写入的信息单元的个数, size* nmemb不大于ptr大小
fp: 要写的文件指针, 提前用fopen打开的
函数返回值:成功返回读取的信息单元的个数,失败: EOF
直接在之前的基础上再次写入文件
演示
代码
#include <stdio.h>
int main(int argc,char* args[])
{
FILE *fp=fopen("/home/xxx/Desktop/shell/1234","a+");
if(fp!=NULL)
{
printf("打开文件成功\n");
short ss[5];
if(fread(ss,2,5,fp)!=EOF)
{
printf("读取成功\n");
printf("%d\n",ss[0]);
}
if(fwrite(ss,2,5,fp)!=EOF)
{
printf("写入成功\n");
}
}
if(fclose(fp)==0)
{
printf("关闭文件成功\n");
}
}
现象
可以看到在之后多加了是个字符,刚好是5个short的大小。
11、格式化写函数fscanf
函数原型:int fscanf ( FILE * fp, const char * format, ... );
函数功能:fscanf从fp中格式化输入
函数参数:
fp: 文件句柄
format:格式
函数返回值:成功返回读取的信息单元的个数,失败: EOF
文件内容
我要把AAAA,BBBB,CCCC分别读到三个字符串
代码
#include <stdio.h>
int main(int argc,char* args[])
{
FILE *fp=fopen("/home/xxx/Desktop/shell/1234","a+");
if(fp!=NULL)
{
char ss0[20],ss1[20],ss2[20];
printf("打开文件成功\n");
if(fscanf(fp,"%s %s %s",ss0,ss1,ss2)!=EOF)
{
printf("读到的字符串:%s,%s,%s\n",ss0,ss1,ss2);
}
}
if(fclose(fp)==0)
{
printf("关闭文件成功\n");
}
}
现象
11、格式化读函数fprintf
函数原型:int fprintf(FILE *fp, const char *format, ...)
函数功能:把数据按格式存入文件
函数参数:
fp: 文件句柄
format:格式
函数返回值:成功返回读取的信息单元的个数,失败: EOF
把姓名、年龄按照“姓名:xxx,年龄:xx。”的格式存入文件
演示
代码
#include <stdio.h>
int main(int argc,char* args[])
{
FILE *fp=fopen("/home/xxx/Desktop/shell/1234","a+");
if(fp!=NULL)
{
char name[20];
int age;
printf("打开文件成功\n");
printf("请输入你的姓名\n");
scanf("%s",name);
printf("请输入你的年龄\n");
scanf("%d",&age);
if(fprintf(fp,"姓名:%s,年龄:%d.",name,age)!=EOF)
{
printf("存储信息成功\n");
}
}
if(fclose(fp)==0)
{
printf("关闭文件成功\n");
}
}
现象
12、文件指针定位函数
当读取文件时,需要打开,打开后文件指针在开头,就像你打开一个txt文件后的光标在开头
读取字符光标就会往后移动
假如你读取完1后光标这时候过去了,要想再读到这个字符,再打开不方便,那么就用到一些光标移动函数。
fseek函数
函数原型:int fseek(FILE *stream, long int offset, int whence)
函数功能:将指针定位到指定位置
函数参数:stream文件句柄,offset相对于whence的偏移量,whence:有文件开始SEEK_SET、当前位置SEEK_CUP、文件结尾SEEK_END。
函数返回值:如果成功,则该函数返回零,否则返回非零值。
ftell函数
告诉你文件指针距离开头有几个字节,通常配合fseek函数求文件大小。
fseek(fp, 0, SEEK_END);
len = ftell(fp);