标准IO库是由Dennis Ritchie于1975年左右编写的,它是Mike Lestbain写的可移植IO库的主要修改版本,2010年以后, 标准IO库几乎没有进行什么修改。标准IO库处理了很多细节,如缓冲区分配、以优化的块长度执行IO等,用户不必在担心不能正确选择块长度,这些处理方便了用户的使用。与系统调用I/O相似,也包括打开、读写、关闭这些操作,主要的函数列举如下。
◆ 打开与关闭文件:fopen,fclose。
◆ 读写文件:fread,fwrite。
◆ 读写文本行:fgets,fputs。
◆ 格式化读写:fscanf,fprintf。
◆ 标准输入输出:printf,scanf。
◆ 读写字符:fgetc,getc,getchar,fputc,putc,putchar。
◆ 其他:fflush,fseek。
1、fopen()与fclose()
fopen()、fdopen()和freopen()三个函数用于打开一个标准IO流,其函数原型如下:
#include <stdio.h> FILE *fopen(const char *path, const char *mode); FILE *fdopen(int fd,const char *mode); FILE *freopen(const char *path, const char *mode,FILE *stream);
参数mode用于定义打开文件的访问权限。下面描述了mode的取值,有如下几种方式:
- "r" 以只读方式打开文件,该文件必须存在。
- "w" 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
- "w+" 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
- "a" 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
- "a+" 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。(原来的EOF符不保留)
- "wb" 只写打开或新建一个二进制文件,只允许写数据。
- "wb+" 读写打开或建立一个二进制文件,允许读和写。
- "ab" 追加打开一个二进制文件,并在文件末尾写数据。
- "ab+"读写打开一个二进制文件,允许读,或在文件末追加数据。
后面加b字符可以表示打开的文件为二进制,而不是纯文本文件,
函数fopen()打开由参数path指定路径名的一个文件。函数fdopen()取一个已有的文件描述符,并使一个标准的IO流与该描述符项结合。函数freopen()在一个指定的流上打开一个指定的文件,若该流已经打开,则先关闭该流;若该流已经定向,则清除改定向。
一旦打开了流,即可对其进行读写操作,读写操作可在三种不同类型的非格式化IO中进行选择:
- 每次一个字符的io。一次读写一个字符,如果流带缓存,则标准io处理所有缓存
- 每次一行的io,使用fgets和fputs一次读写一行,当调用fgets时应说明能处理的he最大行长
- fread 和 fwrite函数支持这种类型的io,每次io操作或写某种数量的对象,而每个对象有指定的长度。
函数fclose()用于关闭一个已经打开的标准IO流,其函数原型如下:
#include <stdio.h>
int fclose(FILE * fp);
函数fclose()在关闭文件流之前,会刷新缓冲区,将所有数据同步到磁盘中,函数若执行成功会返回0,否则返回非0值,同时设置errno。
注意:使用fopen()打开的文件,一定要记得使用fclose()关闭,否则会出现很多意想不到的情况,例如对文件的更改没有被记录到磁盘上,其他进程无法存取该文件等。#include <stdio.h> int main(void) { FILE *pf; pf = fopen("log.txt", "w+"); /*打开文件*/ if(pf != NULL) { printf("open file ok "); } else { printf("open file error "); } fclose(pf); return 0; }
2、fgetc()与fputc()
fgetc()、getc()和getchar()三个函数用于从标准流中一次性读取一个字符,其函数原型如下
#include<stdio.h> int fgetc(FILE *stream); int getc(FILE *stream); int getchar();
上述三个函数用于返回当前位置的下一个字符,返回字符时会进行类型转换:将unsigned char类型转换为int类型。字符不带符号的理由是:最高位为1也不会使返回值为负。返回值类型为整型的理由是:可以返回所有可能的字符,再加上一个已发生错误或已达到文件尾端的标识符。值得一提的是,函数getchar()等同于函数getc(stdin).
在头文件<stdio.h>中常数EOF被要求为一个负值,通常是-1.
#define EOF (-1)
不管是出错还是到达文件结尾,上述三个函数都返回同样的值。为了区分这两种不同的情况,必须调用ferror()和feof()。
#include <stdio.h> int feof(FILE *stream); int ferror(FILE *stream); void clearerr(FILE *stream);
大多数实现中,FILE对象为每个流维护了两个标识符:
◆出错标识。
◆文件结束标识。
函数feof()用于检测当前是否到达文件结尾,若是返回0,否则返回非0值;函数ferror()用于检测当前流是否发生错误,若是返回0,否则返回非0值;函数clearerr()可以清除这两个标识。
putc()、fputc()和putchar()三个函数用于向标准流中一次性写入一个字符,其函数原型如下:
#include<stdio.h> int fputc(int c,FILE *stream); int putc(int c,FILE *stream); int putchar(int c);
与输入函数一样,函数putchatr(c)等同于函数putc(c,stdout)。
示例----对一个文件进行复制
程序源码为:
#include <stdio.h> int main(void) { char c; FILE *base_fp; FILE *copy_fp; base_fp =fopen("log.txt","r"); copy_fp =fopen("copyfile","w"); if(!base_fp)printf("error1 "); if(!copy_fp)printf("error2 "); while((c =fgetc(base_fp)) != EOF){ fputc(c,copy_fp); } }
程序延时了如何调用fgetc()函数,每次调用时,检查返回值是否为EOF以查看是否已经到达文件末尾。
程序编译运行结果如下:
3、fgets()与gets
fgets()和gets()函数用于从打开流中一次性读取一行字符,其函数原型如下:
#include <stdio.h> char *fgets(char *s,int size FILE *stream); char *gets(cahr *s);
参数说明:
- s为一个字符数组,用来保存读取到的字符。
- size为要读取的字符的个数。如果该行字符数大于size-1,则读到 size-1 个字符时结束,并在最后补充' ';如果该行字符数小于等于 size-1,则读取所有字符,并在最后补充 ' '。即,每次最多读取 size-1 个字符。
- stream为文件流指针。
【返回值】读取成功,返回读取到的字符串,即string;失败或读到文件结尾返回NULL。因此我们不能直接通过fgets()的返回值来判断函数是否是出错而终止的,应该借助feof()函数或者ferror()函数来判断。
这两个函数都指定了缓冲区地址,将读取的行送入其中,函数gets()从标准输入读取,其函数fgets()从指定的流读取。
对于函数fgets(),必须指定缓冲的长度为n,此函数一直读到下一个换行符为止,但不超过n-1个字符,读取的字符被送入缓冲区,该缓冲区以NULL字节结尾。如果改行最后一个换行符的字符数超过n-1,则函数fgets()返回一个不完整的行,但是缓冲区总是以NULL字节结尾,对函数fgets()的下一次调用会继续读该行。
函数gets()是一个不被推荐使用的函数,其问题是调用者在使用函数gets()时不能指定缓存区的长度,这样就可能造成缓冲区溢出,写到缓冲区之后的存储空间中,从而产生不可预料的错误,这种缺陷曾经造成了1988年的网络蠕虫病毒。
注意:fgets()与gets()不一样,不仅仅是因为gets()函数只有一个参数 FILE *stream,更重要的是,fgets()可以指定最大读取的字符串的个数,杜绝了gets()使用不当造成缓存溢出的问题。另外一个区别是:函数gets()不能把换行符存入缓冲区。
示例———行缓存显示文件内容
程序源码为:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { FILE *fp=NULL; char *buf; int count = 0; buf=(char*)malloc(10*sizeof(char)); memset(buf,0,10*sizeof(char)); fp=fopen("log.txt","r"); if(fp==NULL) { perror("fopen()"); exit(1); } if((fgets(buf,10,fp))==NULL) { printf("fgets():error! "); exit(1); } for(count=0;count<10;count++) { printf("buf[%d]: %c->%d ",count,buf[count],buf[count]); } exit(0); }
编译运行结果如下
4、fputs()与puts()
fputs()和puts()函数用于向打开流中一次性写入一个字符串,其函数原型如下:
#include <stdio.h> int fputs(const char *s,FILE *stream); int puts(const char *s);
函数fputs()将一个NULL 字节结尾的字符串写到指定的流中,尾端的终止符NULL不用写出。由于字符串不需要换行符作为最后一个非NULL字符,因此函数fputs()并不一定是每次输出一行。通常在NULL字符之前是一个换行符,但要求并不总是如此。
函数puts将一个NULL字节终止的字符串写到标准输出中,但终止符不需要写出。随后,函数puts()又将一个换行符写到标准输出中。函数puts()虽不像函数gets()那样不安全,但是我们还是应该避免使用它,以免增加需要记住它在最后是否添加了一个换行符的麻烦。如果总是使用函数fgets()和fputs(),那么我们就会熟知在每行终止处必须处理换行符。
示例:使用fputs()写一个文件,然后fgets()读取文件内容并显示。
程序首先创建一个空文件,并利用fputs()函数写 入两行字符串,刷新关闭。然后重新以只读的方式打开该文件, 使用fgets()函数连续两次读取该文件,第一次读取3-1个字符,第 二次读取一行。最终结果都显示出来。
#include <stdio.h> #include<stdlib.h> #include<sys/io.h> #include<curses.h> int main(void) { char msg[] = "This is a test! secend line "; char msgget[100]; int i = 0; FILE* fstream; fstream = fopen("test.txt","w+"); /*打开文件*/ if(fstream==NULL) { printf("Open file failed! "); exit(1); } fputs(msg, fstream); /*写入文件*/ fflush(fstream); close(fileno(fstream)); fstream=fopen("test.txt","r"); i = 0; fgets(msgget,3,fstream) ; fputs(msgget, stdout); printf(" "); fgets(msgget,100,fstream) ;/*从流中读取一行或者指定个数字符*/ fputs(msgget, stdout); /*送一个字符串到流中*/ return 0; }
编译运行结果如下
5、fread()与fwrite()
对于文本文件,通常以字符或行为单位进行文件读写,对于二进制文件,更倾向于一次性读写一个完整的结构。如果使用函数getc()或putc()读写一个结构,那么循环必须通过整个结构,循环每次只能处理一个字节,这样会非常麻烦,且效率低下。如果使用函数fputs(),可能实现不了完整读写结构的要求,因为函数fputs()在遇到NULL字节时就会停止,而在结构中可能含有NULL字节。类似的,如果输入数据中包含有NULL字节或换行符,则函数fgets()也不能进行完整的读写的操作。因此,提供了函数fread()和fwrite(),用以执行二进制文件,其函数原型如下:
#include <stdio.h> size_t fread(void *ptr,size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr,size_t size, size_ nmemb,FILE *stream);
参数说明:
(1)ptr:是一个指针,对fread来说,它是读入数据的存放地址。对fwrite来说,是要输出数据的地址。
(2)size:要读写的字节数;
(3)nmemb:要进行读写多少个size字节的数据项;
(4)stream:文件型指针。
返回值:读或写的记录数,成功时返回的记录数等于nmemb,出错或读到文件末尾时返回的记录数小于nmemb,也可能返回0。
注意:1 完成一次写操作(fwrite())后必须关闭流(fclose());
2 完成一次读操作(fread())后,如果没有关闭流(fclose()),则指针(FILE * fp)自动向后移动前一次读写的长度,不关闭流继续下一次读操作则接着上次的输出继续输出;
3、读写函数fread()和fwrite()是按照数据项item来读写的,一次读或者写一个数据项,而不是按照字节读写的。数据项可以是一个int数据,char数据,字符串,结构体数据等等。
函数fread()和fwrite()通常有以下两种常见的用法:
1、读写常用类型
下面为一个写int数据到文件的一个示例:
#include <stdio.h> #include <stdlib.h> int main () { FILE * pFile; int buffer[] = {1, 2, 3, 4}; if((pFile = fopen ("myfile.txt", "wb"))==NULL)//从wb可知道,写的是一个二进制文件爱你 { printf("cant open the file"); exit(0); } //可以写多个连续的数据(这里一次写4个) fwrite (buffer , sizeof(int), 4, pFile); fclose (pFile); return 0; }
编译运行结果如下:
对应的有将一个int数据从一个文件中读取出来
#include <stdio.h> #include <stdlib.h> int main () { FILE * fp; int buffer[4]; if((fp=fopen("myfile.txt","rb"))==NULL) { printf("cant open the file"); exit(0); } if(fread(buffer,sizeof(int),4,fp)!=4) //可以一次读取 { printf("file read error "); exit(0); } for(int i=0;i<4;i++) printf("%d ",buffer[i]); return 0; }
运行结果如下:
2、读写结构体数据
下面为一个示例写结构提到数据。
#include <stdio.h> #include <string.h> #include <stdlib.h> typedef struct{ int age; char name[30]; }people; int main () { FILE * pFile; int i; people per[3]; per[0].age=20;strcpy(per[0].name,"li"); per[1].age=18;strcpy(per[1].name,"wang"); per[2].age=21;strcpy(per[2].name,"zhang"); if((pFile = fopen ("myfile.txt", "wb"))==NULL) { printf("cant open the file"); exit(0); } for(i=0;i<3;i++) { if(fwrite(&per[i],sizeof(people),1,pFile)!=1) printf("file write error "); } fclose (pFile); return 0; }
运行结果如下
最后我们通过读结构体将其读取出来
#include <stdio.h> #include <string.h> #include <stdlib.h> typedef struct{ int age; char name[30]; }people; int main () { FILE * fp; people per; if((fp=fopen("myfile.txt","rb"))==NULL) { printf("cant open the file"); exit(0); } while(fread(&per,sizeof(people),1,fp)==1) //如果读到数据,就显示;否则退出 { printf("%d %s ",per.age,per.name); } return 0; }
运行结果如下
二进制文件读写操作的基本问题是:他只能用于读在同一系统上已写的数据。以前这不是问题,但是,现在由于很多异构系统通过网络互联了起来,就会出现在一个系统上写的数据要在另一个系统上处理的文件(比如FPGA的DE10-NANO、DE1-SOC等都有这种情况),在这种环境下,这两个函数可能就无法正常工作,其原因是:
(1)在一个结构中,同一成员的偏移量可能随编译程序和系统的不同而不同,某些编译程序有一选项,可选择不同值,可以使结构中的各成员紧密包装(节省空间的同时性能有所下降),或者准确对其对齐(以便在运行时易于存取结构中的各个成员)。这意味着即使同一操作系统上,一个结构的二进制存取方式也可能因编译程序选项的不同而不同。
(2)用来存储多字节整数和浮点数的二进制格式在不同的操作系统结构间也可能不同,在不同系统之间交换二进制数据的实际解决方法也是使用互认的规范格式。
6、文件流定位
文件流定位的方法通常有三种:
(1)ftell和fseek函数。这两个函数自V7以来就存在了,但是它们都假定文件的位置可以存放在一个长整型中。
(2)ftello和fseeko函数。Single UNIX Specification引入了这两个函数,可以使文件偏移量不必一定使用长整型。它们使用off_t数据类型代替了长整型。
(3)fgetpos和fsetpos函数。这两个函数是由ISO C引入的。它们使用一个抽象数据类型fpos_t记录文件的位置。这种数据类型可以定义为记录一个文件位置所需的长度。需要移植到非UNIX系统上运行的应用程序应当使用fgetpos和fsetpos。
上述文件流函数定位的函数原型如下:
#include <stdio.h> long ftell( FILE *stream); long fseek(FILE *stream,long offset,int whence); off_set ftello(FILE *stream); int fseeko(FILE *stream,off_t offset,int whence); int fgetpos(FILE *stream, fpos_t *pos); int fsetpos(FILE *stream, fpos_t *pos);
第一个参数stream为文件指针。
第二个参数offset为偏移量,正数表示正向偏移,负数表示负向偏移。off_t类型默认是32位的long int
第三个参数whence设定文件哪里开始偏移,可能取值为:SEEK_CUR、SEEK_END或SEEK_SET。
SEEK_SET:文件开头
SEEK_CUR:当前位置
SEEK_END:文件结尾
fpos_t是代表文件访问指针位置信息的类型名,你可以把它看成跟int或long这样的类型名一样的东西。
对于一个二进制文件,其文件位置指示器是从文件起始位置开始度量,并以字节为计量单位。ftell用于二进制文件时,其返回值就是这种字节位置。为了用fseek定位一个二进制文件,必须指定一个字节offset,以及解释这种偏移量的方式。whence的值与lseek函数的相同:SEEK_SET表示从文件的起始位置开始,SEEK_CUR表示从当前文件位置开始,SEEK_END表示从文件的尾端开始。
对于文本文件,它们的文件当前位置可能不以简单的字节偏移量来度量。这主要也是在非UNIX系统中,它们可能以不同的格式存放文本文件。为了定位一个文本文件,whence一定要是SEEK_SET,而且offset只能有两种值:0(绕回到文件的起始位置),或是对该文件调用ftell所返回的值。使用rewind函数也可以将一个流设置到文件的起始位置。
下面通过一些实例对上述函数进行了解
1、ftell函数用于得到文件位置指针当前位置相对与文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。
#include <stdio.h> int main( void ) { FILE *stream; stream = fopen( "MYFILE.TXT", "w+" ); fprintf( stream, "This is a test" ); printf( "The file pointer is at byte %ld ", ftell( stream ) ); fclose( stream ); return(0); }
程序编译运行结果如下:
ftell一般用于读取文件的长度,下面补充一个例子,读取文本文件中的内容:
#include <stdio.h> #include <stdlib.h> int main() { FILE *fp; int flen; char *p; /* 以只读方式打开文件 */ if ( (fp = fopen( "log.txt", "r" ) ) == NULL ) { printf( " file open error " ); exit( 0 ); } fseek( fp, 0L, SEEK_END ); /* 定位到文件末尾 */ flen = ftell( fp ); /* 得到文件大小 */ p = (char *) malloc( flen + 1 ); /* 根据文件大小动态分配内存空间 */ if ( p == NULL ) { fclose( fp ); return(0); } fseek( fp, 0L, SEEK_SET ); /* 定位到文件开头 */ fread( p, flen, 1, fp ); /* 一次性读取全部文件内容 */ p[flen] = '