标准I/O库是由C语言标准ISO C定义的,不仅在Unix上,还有很多系统都实现了此库,与第三章介绍的常用文件I/O函数的区别:后者是POSIX.1标准定义的,且都是不带缓冲的I/O函数,可以看作是系统调用,而前者都是带缓冲的I/O函数,是在系统调用的基础上实现的。 1. 系统调用和库函数的区别 系统调用是操作系统提供的一组特殊接口,通过这组接口用户程序可以使用操作系统内核提供的各种功能,例如分配内存、创建进程、实现进程之间的通信等。 库函数可以说是对系统调用的一种封装,如果直接使用系统调用,会影响程序的移植性,所以才有了各种库函数,比如C库。 2. 流和FILE对象 文件I/O函数是针对文件描述符进行操作的,而标准I/O库则是围绕流进行的,当用标准I/O函数打开文件时,就使流与一个文件相关联。标准I/O库使用FILE对象来管理流的各种信息,包括:用于实际I/O的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等。 流的定向决定了所读、写的字符是单字节的还是多字节的,当流最初创建时,并没有定向,第一次在未定向的流上使用多字节I/O函数还是单字节I/O函数,决定了该流的定向。 操作系统对一个进程预定义的三个流,分别是:标准输入、标准输出和标准出错,通过定义在<stdio.h>中的FILE指针:stdin、stdout和stderr加以引用。它们引用的文件已第三章中文件描述符:STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO 引用的文件相同。 3. 标准I/O库提供的缓冲 标准I/O库提供缓冲的目的是尽可能的减少使用read和write系统调用的次数。提供了三种类型的缓冲: (1)全缓冲。这种情况下,在填满标准I/O缓冲区后才进行实际I/O操作,在一个流上执行第一个I/O操作时,相关标准I/O函数会调用malloc获得所需的缓冲区。 (2) 行缓冲。当在输入和输出中遇到换行符时,标准I/O库执行I/O操作,这允许一个输出一个字符,但只有在写了一行后才进行实际的I/O操作。当流涉及一个终端(如标准输入和标准输出)时,通常使用行缓冲。行缓冲有两个限制:只要填满了缓冲区,即使没有遇见换行符,也进行I/O操作;当标准I/O库要求从内核中得到数据时,需要将缓冲区中的所有待输出数据进行冲洗。冲洗(flush)指缓冲区的写操作。 (3) 不带缓冲。不对字符进行缓冲存储,例如若使用不带缓冲的流进行I/O写操作,则标准I/O函数可能直接调用write系统调用,将字符立即写入相关文件。标准出错stderr通常是不带缓冲的,这就使得出错信息会尽快显示出来。 很多操作系统默认使用下列类型的缓冲: * 标准出错是不带缓冲的; * 若是涉及终端设备的其它流,则是行缓冲的;否则是全缓冲的。 4. 打开流 以下三个函数打开一个标准I/O流:
#include <stdio.h> FILE * fopen(const char * restrict pathname, const char* restrict type); FILE * freopen(const char * restrict pathname, const char* restrict type, FILE * restrict fp); FILE * fdopen(int filedes, const char* type); 返回值:若成功返回文件指针,若出错返回NULL三者的区别:fopen打开一个指定的文件;freopen在一个指定的流上打开一个文件,若该流已打开,则先关闭该流,若已定向,则清除定向;fdopen使一个流与文件描述符所引用的文件相关联,适用于不能通过fopen打开的特殊文件。 type参数指定对该I/O流的读、写方式,可取以下值:
type说明r或rb为读打开w或wb为写打开并将文件截短至0a或ab在文件尾端添加或为写创建r+或r+b或rb+为读和写打开w+或w+b或wb+为读和写打开并将文件截短至0a+或a+b或ab+为在文件尾读和写打开或创建b用于区分文件是文本文件还是二进制文件。当以a或w创建一个新文件时,无法说明该文件的访问权限位
#include <stdio.h> int fclose(FILE *fp); 关闭一个打开的流 返回值:成功返回0,出错返回EOF文件关闭前,冲洗缓冲区中的输出数据,丢弃任何输入数据。当进程正常退出(直接调用exit或从main函数返回),所有打开的流会被自动关闭,所有缓冲区中未写的数据会被冲洗。 5. 读和写流 :非格式化I/O函数 非格式化I/O函数包括三种:每次一个字符的I/O;每次一行的I/O;直接I/O,每次I/O操作读或写某种数量的对象,而每个对象具有指定的长度。 以下三个函数用于一次读一个字符:
#include <stdio.h> int getc(FILE *fp); int fgetc(FILE *fp); int getchar(void); 返回值: 成功返回下一个字符,到文件尾或出错返回EOF函数getchar可以视为getc(stdin),getc和fgetc区别是前者可被实现为宏。这三个函数在返回下一个字符时,会将其unsigned char转换为int类型,之所以转换为整型,是需要一个负值表示EOF(常用-1表示),所以不能用char来表示返回值;之所以unsigned,是因为即使最高位为1也不会使返回值为负。 出错和到达文件尾都返回EOF,所以需要函数来判断是哪种情况,每个流在FILE对象中维持了两个标志:出错标志和文件结束标志。
#include <stdio.h> int ferror(FILE * fp); int feof(FILE * fp); 返回值:条件为真返回非0,为假返回0; void clearerr(FILE *fp); 清楚这两个标志ungetc将字符回压入流中,当到达文件尾时,调用ungetc会清除文件结束标志:
#include <stdio.h> int ungetc(int c, FILE* fp); 返回值:成功返回c,出错返回EOF对应的输出函数:
#include <stdio.h> int putc(int c, FILE* fp); int fputc(int c, FILE* fp); int putchar(void); 返回值:成功返回c,出错返回EOFputc可实现为宏 6. 每次一行的I/O
#include <stdio.h> char * fgets(char *restrict buf, int n, FILE* fp); char * gets(char * buf); 返回值:成功返回buf,出错或到达文件尾返回NULLgets从标准输入读,且不能指定缓冲区长度,不将换行符存入缓冲区; fgets从指定的流读且必须指定缓冲区长度n,此函数一直读取一行,但是不超过n-1个字符,如果该行超过n-1个字符,则返回一个不完整的行,但缓冲区的结尾总是NULL字符。 gets不将换行符输入缓冲区,fgets将换行符输入缓冲区。
#include<stdio.h> int fputs(const char* restrict str, FILE *fp); int puts(const char* str); 返回值:成功返回非负值,出错返回EOFfputs输出以NULL字符结尾的字符串到指定的流,尾端的NULL不写出,并不一定输出一行,因为NULL字符前不一定是换行符。 puts将以NULL字符结尾的字符串写到标准输出,NULL字符不写出,但puts又将 一个换行符输出。 fgets和fputs都不处理换行符。 7. 二进制I/O
#include <stdio.h> size_t fread(void * restrict ptr, size_t size, size_t nobj, FILE* fp); size_t fwrite(void *restrict ptr, size_t size, size_t nobj, FILE* fp); 返回值:读或写的对象数这两个函数常用于读或写数组或结构,只能用于在同一系统上读写数据,因为不同系统中,同一个结构,大小可能不一样。 8. 定位流
#include <stdio.h> long ftell(FILE *fp); 返回值:成功则返回当前文件位置指示,失败则返回-1L; int fseek(FILE *fp, long offset, int wherece); 返回值: 成功则返回0,出错返回非0值; void rewind(FILE* fp);wherece参数与第三章中lseek函数的相同:SEEK_SET,SEEK_CUR,SEEK_END rewind使一个流返回到文件的起始位置。 9. 格式化I/O
#include <stdio.h> int printf(const char* restrict format, ...); int fprintf(FILE* fp, const char* restrict format, ...); 返回值: 成功返回输出字符数,出错返回负值; int sprintf(char* restrict buf, const char* restrict format, ...); int snprintf(char* restrict buf, size_t n, const char* restrict format, ...); 返回值:成功返回输入数组的字符数,出错返回负值。sprintf在数组的尾端自动添加一个NULL字节,但该字节不包含在返回个数中。 10. 临时文件
#include <stdio.h> char * tmpnam(char* ptr); 返回值:指向唯一路径名的指针; FILE * tmpfile(void); 返回值:成功则返回文件指针,出错返回NULL int mkstemp(char * template); 返回值:成功则返回文件描述符,出错返回-1。tmpnam产生一个与现有文件路径名不同的有效路径名字字符串,最多可调用TMP_MAX次。若ptr是NULL,则产生的字符串存在静态内存区中,若ptr不是NULL,则它至少指向长度是L_tmpnam个字符的数组,产生的路径名存放在该数组中,ptr作为返回值返回。 tmpfile函数先调用tmpnam产生一个唯一的路径名,如何创建临时文件,并立即unlink它。 mkstemp与tmpfile类似,但它并不自动unlink临时文件,需要手动unlink。 tmpnam的不足之处:在返回唯一路径名和应用程序用该路径名创建临时文件的空隙,另一个进程可能产生一个同名文件,所以应当避免使用它。
