首页 > 代码库 > C文件IO

C文件IO

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 \n",argv[0]);
        return -1;
    }
    if((fp=fopen(argv[1],"r"))==NULL) 
    {
        printf("can not open %s\n",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!\n");
        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!\n");
        return -1;
    }
    pp=boya;
    qq=boyb;
    printf(“please input two students‘ name and number:\n");
    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\t\t number\n");
    for(i=0;i<2;i++,qq++) //输出qq中的内容
        printf("%s\t\t %d\n",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\n",i,ch);  //按照指定格式写文件

        int new_i = 0;
        char new_ch;

        rewind(fp);  //使读写指针归位

        fscanf(fp,"%d:%c\n",&new_i,&new_ch);  //按照指定格式读文件

        printf("new_i=%d, new_ch=%c\n",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\n";
	char msg2[] = "hello\nworld";
	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,'\0',128);
	fwrite( msg1 , 7 , 1 , fp );
	printf("test setbuf(no buf)!check no_buf1.txt\n");
	printf("now buf data is :buf=%s\n",buf);

	printf("press enter to continue!\n");
	getchar();
	fclose(fp);


//open a file and set nobuf(used setvbuf).and write string to it,check it before close of flush the stream
	if(( fp = fopen("no_buf2.txt","w")) == NULL)
	{
		perror("file open failure!");
		return(-1);
	}
	setvbuf( fp , NULL, _IONBF , 0 );
	memset(buf,'\0',128);
	fwrite( msg1 , 7 , 1 , fp );
	printf("test setvbuf(no buf)!check no_buf2.txt\n");

	printf("now buf data is :buf=%s\n",buf);

	printf("press enter to continue!\n");
	getchar();
	fclose(fp);

//open a file and set line buf(used setvbuf).and write string(include '\n') to it,
//
//check it before close of flush the stream
	if(( fp = fopen("l_buf.txt","w")) == NULL)
	{
		perror("file open failure!");
		return(-1);
	}
	setvbuf( fp , buf , _IOLBF , sizeof(buf) );
	memset(buf,'\0',128);
	fwrite( msg2 , sizeof(msg2) , 1 , fp );
	printf("test setvbuf(line buf)!check l_buf.txt, because line buf ,only data before enter send to file\n");

	printf("now buf data is :buf=%s\n",buf);
	printf("press enter to continue!\n");
	getchar();
	fclose(fp);

//open a file and set full buf(used setvbuf).and write string to it for 20th time (it is large than the buf)
//check it before close of flush the stream
	if(( fp = fopen("f_buf.txt","w")) == NULL){
		perror("file open failure!");
		return(-1);
	}
	setvbuf( fp , buf , _IOFBF , sizeof(buf) );
	memset(buf,'\0',128);
	fwrite( msg2 , sizeof(msg2) , 1 , fp );
	printf("test setbuf(full buf)!check f_buf.txt\n");
	
	printf("now buf data is :buf=%s\n",buf);
	printf("press enter to continue!\n");
	getchar();

	fclose(fp);
	
}


其他文件操作

下面介绍一些在文件操作中常用的函数

打开、关闭文件操作fopen和fclose

fp = fopen(文件名,文件操作方式);

文件的操作方式有以下这些


关闭文件使用fclose(文件指针); 或者 fcloseall()函数。


文件流检测

extern  int  feof(流对象)

用于判断流对象是否读到文件尾部,如果是返回1,否则,返回0;


extern  int   ferror(流对象) 

用于判断流对象是否出现了错误,若没有错误,则返回0,否则,返回非0。


extern  long  int  ftell(流对象)

返回当前读写位置距离文件开头位置的字节数,若执行失败,返回-1


extern  int  fseek(流对象,偏移距离,基准位置)

基准位置可取值有0、1、2分别表示文件开头、当前位置、文件结尾

将读写位置移到,距离基准位置偏移距离处。若成功,返回0;否则,返回,-1


extern  void  rewind(流对象)

将读写位置重置到文件开始处。


C文件IO