标准 I/O 提供流缓冲的目的是尽可能减少使用read和write调用的数量。标准I/O 提供了3 种类型的缓冲存储。
· 全缓冲。在这种情况下,当填满标准I/O 缓存后才进行实际I/O 操作。对于驻在磁盘上的文件通常是由标准I/O 库实施全缓冲的。在一个流上执行第一次I/O 操作时,通常调用malloc就是使用全缓冲。
· 行缓冲。在这种情况下,当在输入和输出中遇到新行符时,标准I/O 库执行I/O 操作。这允许我们一次输出一个字符(如fputc 函数),但只有写了一行之后才进行实际I/O 操作。当流涉及一个终端时(例如标准输入和标准输出),典型地使用行缓冲。
· 不带缓冲。标准I/O库不对字符进行缓冲。如果用标准I/O函数写若干字符到不带缓冲的流中,则相当于用write系统的用函数将这些字符写全相比较的打开文件上。标准出错况stderr通常是不带缓存后,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新行字符。
1.打开,关闭文件
FILE *fopen(const char *pathname, const char *type) ;
FILE *freopen(const char *pathname, const char *type, FILE *fp) ;
FILE *fdopen(int filedes, const char *type) ;
三个函数的返回:若成功则为文件指针,若出错则为NULL
这三个函数的区别是:
(1) fopen打开路径名由pathname指示的一个文件。
(2) freopen在一个特定的流上(由fp指示)打开一个指定的文件(其路径名由pathname指示),如若该流已经打开,则先关闭该流。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准出错。
(3) fdopen取一个现存的文件描述符(我们可能从open,dup,dup2,fcntl或pipe函数得到此文件描述符),并使一个标准的I/O流与该描述符相结合。
int fclose(FILE *fp)
2.文件读写
1)以字节为单位的I/O函数
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar(void);
返回值:成功返回读到的字节,出错或者读到文件末尾时返回EOF
第一个跟第三个本身不是函数,是通过宏定义借助fgetc来实现的。比如:
# define getc(_stream) fgetc(_stream)
# define getchar fgetc(stdin)
所以fgetc允许作为一个参数传递给另一个函数。fgetc成功时返回读到一个字节,本来应该是unsigned char型的,但由于函数原型中返回值是int型,所 以这个字节要转换成int型再返回,那为什么要规定返回值是int型呢?因为出错或读到文件末尾时fgetc将返回EOF,即-1,保存在int型的返回值中是0xffffffff,如果读到字节0xff,由unsigned char型转换为int型是0x000000ff,只有规定返回值是int型才能把这两种情况区分开,如果规定返回值是unsigned char型,那么当返回值是0xff时无法区分到底是EOF还是字节0xff。如果需要保存 fgetc的返回值,一定要保存在int型变量中,如果写成unsigned char c = fgetc(fp);,那么根据c的值又无法区分EOF 和0xff字节了。注意,fgetc读到文件末尾时返回EOF,只是用这个返回值表示已读到文件末尾,并不是说每个文件末尾都有一个字节是EOF(根据上面的分析,EOF并不是一个字节)。
int putc(int c, FILE *stream);
int fputc(int c, FILE *stream);
int putchar(int c);
返回值:若成功返回字符c,出错则为EOF
同样第一个跟第三个本身不是函数,是通过宏定义借助fgetc来实现的。
以字符串为单位的I/O函数
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
返回值:成功时s指向哪返回的指针就指向哪,出错或者读到文件末尾时返回NULL
这两个函数都指定了缓存地址,读入的字符串放入其中。gets是从标准输入读,fgets是从指定流读。
gets不推荐使用,它的存在只是为了兼容以前的程序。
fgets函数,参数s是缓冲区的首地址,size是缓冲区的长度,该函数从stream所指的文件中读取以 '\n'结尾的一行(包括'\n'在内)存到缓冲区s中,并且在该行末尾添加一个'\0'组成完整的字符串。如果文件中的一行太长,fgets从文件中读 了size-1个字符还没有读到'\n',就把已经读到的size-1个字符和一个'\0'字符存入缓冲区,文件中剩下的半行可以在下次调用fgets时 继续读。如果一次fgets调用在读入若干个字符后到达文件末尾,则将已读到的字符串加上'\0'存入缓冲区并返回,如果再次调用fgets则返回 NULL,可以据此判断是否读到文件末尾。注意,对于fgets来说,'\n'是一个特别的字符,而'\0'并无任何特别之处,如果读到'\0'就当作普 通字符读入。如果文件中存在'\0'字符(或者说0x00字节),调用fgets之后就无法判断缓冲区中的'\0'究竟是从文件读上来的字符还是由 fgets自动添加的结束符,所以fgets只适合读文本文件而不适合读二进制文件,并且文本文件中的所有字符都应该是可见字符,不能有'\0'。对于二进制文件可以通过fread来实现
int fputs(const char *s, FILE *stream);
int puts(const char *s);
返回值:成功返回一个非负整数,出错返回EOF
缓冲区s中保存的是以'\0'结尾的字符串,fputs将该字符串写入文件stream,但并不写入结尾的'\0'。与 fgets不同的是,fputs并不关心的字符串中的'\n'字符,字符串中可以有'\n'也可以没有'\n'。puts将字符串s写到标准输出(不包括 结尾的'\0'),然后自动写一个'\n'到标准输出。
二进制I/O函数
上面也提到过用字符串为单位的IO函数不适合二进制文本。当然对于二进制文件,我们可以通过使用fgetc跟fputc来实现,但是必须循环整个二进制文件,明显比较低效。因此标准IO库提供了如下两个函数对二进制文件操作:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
返回值:读或写的记录数,成功时返回的记录数等于nmemb,出错或读到文件末尾时返回的记录数小于nmemb,也可能返回0
使用二进制I/O的基本问题是,它只能用于读已写在同一系统上的数据。其原因是:
(1) 在一个结构中,同一成员的位移量可能随编译程序和系统的不同而异(由于不同的对准要求)。确实,某些编译程序有一选择项,它允许紧密包装结构(节省存储空间,而运行性能则可能有所下降)或准确对齐,以便在运行时易于存取结构中的各成员。这意味着即使在单一系统上,一个结构的二进制存放方式也可能因编译程序的选择项而不同。
(2) 用来存储多字节整数和浮点值的二进制格式在不同的系统结构间也可能不同。
1 /*fwrite.c*/ 2 #include <stdio.h> 3 void main() 4 { 5 int i; 6 FILE *stream; 7 char s[3]={'a','b','c'}; 8 /*首先使用fopen打开文件,之后再调用fwrite写入文件*/ 9 stream=fopen("hello","w"); 10 i=fwrite(s,sizeof(char),3,stream); 11 printf("i=%d\n",i); 12 fclose(stream); 13 }
3.定位标准I/O流:
(1) ftell和fseek。
(2) fgetpos和fsetpos。这两个函数是新由ANSI C引入的。它们引进了一个新的抽象数据类型 fpost,它记录文件的位置。在非UNIX系统中,这种数据类型可以定义为记录一个文件的位置所需的长度。所以移植到非UNIX系统的应用程序应当使用 fgetpos和fsetpos。
int fseek(FILE *stream, long offset, int whence);
返回值:成功返回0,出错返回-1并设置errno
long ftell(FILE *stream);
返回值:成功返回当前读写位置,出错返回-1并设置errno
void rewind(FILE *stream);
把读写位置移到文件开头
fseek的whence和offset参数共同决定了读写位置移动到何处,whence参数的含义如下:
SEEK_SET
从文件开头移动offset个字节
SEEK_CUR
从当前位置移动offset个字节
SEEK_END
从文件末尾移动offset个字节
offset可正可负,负值表示向前(向文件开头的方向)移动,正值表示向后(向文件末尾的方向)移动,如果向前移动的字节 数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸,从原来的文件末尾到fseek移动之后的读写位置之间的字节都是0。
int fgetpos(FILEf *p, fpos_t *pos) ;
int fsetpos(FILEf *p, const fpos_t *pos) ;
两个函数返回:若成功则为0,若出错则为非0
fgetpos将文件位置指示器的当前值存入由pos指向的对象中。在以后调用fsetpos时,可以使用此值将流重新定位至该位置。
4.格式化
格式化输入函数:
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
返回值:成功返回格式化输出的字节数(不包括字符串的结尾'\0'),出错返回一个负值
格式化输出函数:
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
#include <stdarg.h>
int vscanf(const char *format, va_list ap);
int vsscanf(const char *str, const char *format, va_list ap);
int vfscanf(FILE *stream, const char *format, va_list ap);
返回值:返回成功匹配和赋值的参数个数,成功匹配的参数可能少于所提供的赋值参数,返回0表示一个都不匹配,出错或者读到文件或字符串末尾时返回EOF并设置errno
5.创建临时文件
很多情况下,程序会创建一些文件形式的临时文件,这些临时文件可能保存这一个计算的中间结果,也可能是关键操作前的备份等等。这都是临时文件的好处。
标准I/O提供了两个函数创建临时文件
char *tmpnam(char *ptr) ;
返回:指向一唯一路径名的指针
FILE *tmpfile(void);
返回:若成功则为文件指针,若出错则为NULL
tmpnam函数返回一个不与任何已存在文件同名的有效文件名。每次调用它都会产生一个不同的文件名,但是一个进程中调用最多次数为TMP_MAX【在stdio.h中有定义】。如果ptr不为NULL,则认为字符串ptr的长度至少是L_tmpnam【在stdio.h 中有定义】,所产生的文件名会放入该字符串ptr中,因此返回值为ptr的值;如果ptr为NULL,则所产生的文件名存放在一个静态区中,下一次调用时,会重写改静态区。
tmpfile 创建一个临时二进制文件(类型为wb+),关闭文件或程序结束时将自动删除这种文件。
需要注意的是,tmpnam仅仅是创建一个临时文件,并没有打开它,所以我们如果要用它必须尽可能快的打开它,这样减小另一个程序用同样的名字打开文件的风险。而tmpfile除了创建外,会同时以读写方式打开。