ANSI C标准差点儿被全部的操作系统支持,ANSI C标准提供了完好的I/O函数,使用这些I/O操作我们能够控制程序的输入输出、读写系统磁盘文件。本文记录了用户进程I/O缓冲介绍、文件的读写、文件定位操作等内容。
库函数与系统调用
文件是位于磁盘上的,怎样在执行的程序(进程)中控制文件的读写,通过以下的这张图,我们能够看到应用程序怎样控制系统资源(包含磁盘中的文件)的大概的原理。
操作系统帮助我们管理硬件资源,封装底层实现,以接口的形式(系统调用函数)供上层应用程序调用。直接使用操作系统提供的系统调用函数我们就能够控制文件的读写,可是一般我们在应用程序中不提倡这样做,由于会带来性能上的问题。应用程序调用系统调用函数的时候,操作系统会从用户态转变成内核态,完毕调用后,再从内核态转成用户态(Linux中是通过软中断实现的),而频繁的系统状态切换是须要开销的,会给用户应用程序带来性能上的问题。另外就是可移植性的问题,每一个操作系统向外提供的接口是不尽同样的,假设直接在应用程序中使用系统调用函数,那么程序的可移植性将会非常差。在ANSI C标准中为我们提供了标准函数(库函数)去操作系统调用函数,库函数是一些可以完毕特定功能的函数,一般由某个标准组织公布,并形成一种公认的标准,所以使用库函数可以屏蔽操作系统间对外接口的差异。
以下是网友总结的库函数和系统调用函数之间的差异
缓冲和非缓冲
对于文件的訪问操作,能够依照是否使用缓冲区,分为缓冲文件操作和非缓冲文件操作
缓冲文件操作:高级文件操作,如第一幅图用户应用程序的左分支,在用户进程空间为打开的文件分配缓冲区。ANSI C标准就是使用的这样的文件操作。本文讨论的正是这样的。
非缓冲文件操作:低级文件操作,如第一幅图用户应用程序的右分支,在用户进程空间不设文件缓冲区。POSIX C标准的I/O就是使用的非缓冲文件操作。
说明:这里所说的缓冲所有指的是用户进程空间的文件缓冲,即使是非缓冲的文件操作,在内核部分也是使用了多级缓冲,而不是直接訪问磁盘(能够參考操作系统的存储器结构)。
文件与文件流
流是一种抽象的数据结构,是ANSI C用来高效的管理打开了的文件信息的,在实际编程中的体现就是struct FILE结构体,在stdio.h头文件里定义,流对象最重要的机制就是缓冲区和格式转换。在Linux系统中系统默觉得每一个进程打开3个文件,相应3个流就是标准输入流、标准输出流、标准错误流。除此之外,须要用到的其它文件须要自己打开和关闭。
文件的I/O操作
依照读/写的对象、能够分为按字符、行、块、格式化等几种读写操作。
(1)按字符读/写文件流
以下的这段程序是使用fgetc和fputc来读取指定文件里的内容到标准输出(显示器),执行时须要指定一个文件
#include<stdio.h> int main(int argc,char *argv[]) { FILE *fp=NULL; char ch; if(argc<=1) { printf("check usage of %s ",argv[0]); return -1; } if((fp=fopen(argv[1],"r"))==NULL) { printf("can not open %s ",argv[1]); return -1; } while ((ch=fgetc(fp))!=EOF) fputc(ch,stdout); fclose(fp); return 0; }
执行程序:
(2)按行读/写文件流
以下是一个使用fgets和fputs实现上述功能的程序
#include<stdio.h> int main(int argc,char *argv[]) { FILE *fp=NULL; char str[20]; if((fp=fopen(argv[1],"r"))==NULL) //按仅仅读的形式打开文件 { printf("can not open! "); return -1; } fgets(str,sizeof(str),fp); //从打开文件里读取sizeof(str)个字节到str中 fputs(str,stdout); //将str输出到标准输出 fclose(fp); //关闭文件 return 0; }
(3)依照块读写
以下是使用fread和fwrite来实现的按块读写的程序
#include<stdio.h> int main(int argc,char *argv[]) { struct student { char name[10]; int number; }; FILE *fp=NULL; int i; struct student boya[2],boyb[2],*pp,*qq; if((fp=fopen("aa.txt","w+"))==NULL) //以可读写的方式打开文件;若该文件存在则清空,若不存在就创建 { //打开文件失败 printf("can not open! "); return -1; } pp=boya; qq=boyb; printf(“please input two students‘ name and number: "); for (i=0;i<2;i++,pp++) scanf("%s\%d",pp->name,&pp->number); pp=boya; fwrite(pp,sizeof(struct student),2,fp); //将从键盘输入的信息写入到文件流fp中 rewind(fp); //将读写位置定位到文件头 fread(qq,sizeof(struct student),2,fp); //从文件流fp中读两个结构体到qq printf("name number "); for(i=0;i<2;i++,qq++) //输出qq中的内容 printf("%s %d ",qq->name,qq->number); fclose(fp); return 0; }
(4)依照格式化读/写
以下这段程序是使用sprintf和sscanf进行文件读/写操作
#include<stdio.h> int main() { FILE *fp = NULL; int i = 20; char ch = 'D'; if((fp=fopen("f2","r+"))==NULL) { printf("open file f2 failed."); return -1; } fprintf(fp,"%d:%c ",i,ch); //依照指定格式写文件 int new_i = 0; char new_ch; rewind(fp); //使读写指针归位 fscanf(fp,"%d:%c ",&new_i,&new_ch); //依照指定格式读文件 printf("new_i=%d, new_ch=%c ",new_i,new_ch); fclose(fp); return 0; }
程序执行结果:
再说缓冲区
能够发现上述4中形式的读写操作,都没有指明缓冲区,可是它们都使用到了位于用户进程空间的文件缓冲区,这是由于,对于随意的流,假设没有指明其缓冲区的类型,系统将指定默认类型的缓冲区。假设用户希望自己指定缓冲区,能够使用setbuf( )或者setvbuf( )函数指定,这两个函数的声明例如以下:
extern void setbuf ( 流对象, 缓冲区);
假设将缓冲区设置为NULL,则关闭缓冲区。
extern int setvbuf (流对象,缓冲区, 模式,缓冲区大小)
setvbuf比setbuf更加灵活,当中模式可取值有0、1、2,分别表示全缓冲、行缓冲、无缓冲,假设模式是2,那么将会忽视第二和第四个參数。
以下是一个改动缓冲区的程序:
/* Example show usage of setbuf() &setvbuf() */ #include<stdio.h> #include<error.h> #include<string.h> int main( int argc , char ** argv ) { int i; FILE * fp; char msg1[]="hello,wolrd "; char msg2[] = "hello world"; char buf[128]; //open a file and set nobuf(used setbuf).and write string to it,check it before close of flush the stream if(( fp = fopen("no_buf1.txt","w")) == NULL) { perror("file open failure!"); return(-1); } setbuf(fp,NULL); memset(buf,'