标准I/O是在系统调用基础上构造的;
所有C语言均有I/O,但可能在实现上各有不同;
标准I/O并不可靠,使用时要小心。
流和文件对象
在系统调用(低级I/O)时当打开一个文件时,即返回一个整数文件描述符,然后该文件描述符就用于后续的I/O操作。
对于标准I/O库,它们的操作则是围绕流(stream)进行的。当用标准I/O库打开或创建一个文件时,标准I/O函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了I/O库为管理该流所需要的所有信息:用于实际I/O的文件描述符,指向流缓存的指针,缓存的长度,当前在缓存中的字符数,出错标志等等。应用程序没有必要检验FILE对象。
象系统调用一样,为了引用一个流,需将FILE指针作为参数传递给每个标准I/O函数。
标准I/O流与系统V的STREAMSI/O系统不同。
标准输入、标准输出和标准出错
对一个进程预定义了三个流,它们自动地可为进程使用:标准输入、标准输出和标准出错。
文件描述符STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO分别表示它们。
这三个标准I/O流通过预定义文件指针stdin,stdout和stderr加以引用。
这三个文件指针同样定义在头文件<stdio.h>中。
缓存
标准I/O提供了三种类型的缓存:
(1) 全缓存。在这种情况下,当填满标准I/ 缓存后才进行实际I/ 操作。
(2) 行缓存。在这种情况下,当在输入和输出中遇到新行符时,标准I/O库执行I/O操作。
(3) 不带缓存。标准I/O库不对字符进行缓存。如果用标准I/O函数写若干字符到不带缓存的流中,则相当于用write系统调用函数将这些字符写至相关联的打开文件上。
标准出错流stderr通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新行字符。
缓存特征
ANSI C要求下列缓存特征:
(1) 当且仅当标准输入和标准输出并不涉及交互作用设备时,它们才是全缓存的。
(2) 标准出错决不会是全缓存的。
UNIX缓冲的默认特征:
标准出错是不带缓存的。
如若是涉及终端设备的其他流,则它们是行缓存的,否则是全缓存的。
更改缓存类型
对任何一个给定的流,可调用以下两个函数中的一个更改缓存类型:
#include <stdio.h>
#include <stdio.h>
void setbuf(FILE *fp, char *buf) ;
int setvbuf(FILE *fp, char *buf, int mode, size_t size) ;
返回:若成功则为0,若出错则为非0。
mode
_IOFBF 全缓存
_IOLBF 行缓存
_IONBF 不带缓存
Setbuf和setvbuf的参数
强制刷新一个流:fflush()
功能:强制刷新一个流缓冲区;
方法:
# include <stdio.h>
int fflush(FILE *fp) ;
返回:若成功则为0,若出错则为EOF。
此函数使该流所有未写的数据都被传递至内核。作为一种特殊情形,如若fp是NULL,则此函数刷新所有输出流。
流的打开: fopen()
功能:打开流;
方式:下列三个函数用于打开一个标准I / O流。
#include <stdio.h>
FILE *fopen(const char *pathname, const char *type) ;
FILE *freopen(const char *path name, const char *type, FILE *fp) ;
FILE *fdopen(int filedes, const char *type) ;
返回值:
若成功则为文件指针,若出错则为NULL。
三个函数的区别
(1) fopen打开路径名由pathname 指示的一个文件。
(2) freopen在一个特定的流上(由f p指示)打开一个指定的文件(其路径名由pathname 指示),如若该流已经打开,则先关闭该流。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准出错。
(3) fdopen取一个现存的文件描述符(我们可能从open , dup , dup2 , fcntl或pipe函数得到此文件描述符),并使一个标准的I / O流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数获得的描述符。因为这些特殊类型的文件不能用标准I/O fopen函数打开,首先必须先调用设备专用函数以获得一个文件描述符,然后用f d o p e n使一个标准I / O流与该描述符相结合。
打开标准I / O流的type参数
打开标准I/O流的六种不同的方式
流的关闭: fclose()
功能:关闭一个标准流;
用法:
#include <stdio.h>
int fclose(FILE * fp) ;
返回值:若成功则为0,若出错则为EOF。
说明:在文件被关闭之前,刷新缓存中的输出数据。
缓存中的输入数据被丢弃。
如果标准I / O库已经为该流自动分配了一个缓存,则释放此缓存。
当一个进程正常终止时(直接调用exit函数,或从main函数返回),则所有带未写缓存数据的标准I / O流都被刷新,所有打开的标准I / O流都被关闭。
I/O
一旦打开了流,则可在三种不同类型的非格式化I/O中进行选择,对其进行读、写操作。
(1) 每次一个字符的I/O。一次读或写一个字符,如果流是带缓存的,则标准I / O函数处理所有缓存。
(2) 每次一行的I/O。使用fgets和fputs一次读或写一行。每行都以一个新行符终止。
(3) 直接I/O。fread和fwrite函数支持这种类型的I/O。每次I/O操作读或写某种数量的对象,而每个对象具有指定的长度。这两个函数常用于从二进制文件中读或写一个结构。
字符输入函数
功能:从流中读入一个字符;
用法:
#include <stdio.h>
int getc(FILE *fp) ;
int fgetc(FILE *fp) ;
int getchar(void);
返回值:
若成功则为一个字符,若已到文件尾端或出错则为EOF;
错误判断及清除
错误判断:
#include <stdio.h>
int ferror(FILE *fp) ;
int feof(FILE *fp) ;
若条件为真则为非0,否则为0;
错误清除:
void clearerr(FILE *fp) ;
字符回送:ungtec()
功能:将一个字符回送到流中。
用法:
#include <stdio.h>
int ungetc(int c, FILE *fp) ;
返回值:若成功则为c,若出错则为EOF.
说明:
送回到流中的字符以后又可从流中读出,但读出字符的顺序与送回的顺序相反。
回送的字符,不一定必须是上一次读到的字符。EOF不能回送。但是当已经到达文件尾端时,仍可以回送一字符。下次读将返回该字符,再次读则返回EOF。
当正在读一个输入流,并进行某种形式的分字或分记号操作时,会经常用到回送字符操作。
有时需要先看一看下一个字符,以决定如何处理当前字符。
字符输出函数
功能:向流中输出一个字符
用法:
#include <stdio.h>
int putc(int c , FILE *fp) ;
int fputc(int c, FILE *fp);
int putchar(int c) ;
返回值:若成功则为c,若出错则为EOF
行I/O
功能:行输入;
用法:
#include <stdio.h>
char *fgets(char *buf, int n,FILE *fp) ;
char *gets(char *buf) ;
返回值:
若成功则为buf,若已处文件尾端或出错则为NULL。
功能:行输出:
用法:
#include <stdio.h>
int fputs(const char *str, FILE *fp) ;
int puts(const char *str) ;
返回值:
若成功则为非负值,若出错则为EOF。
二进制I/O
字符I/O只能用于文本文件,而对于二进制文件则会出错。对于二进制文件必须使用二进制I/O。
功能:以二进制方式进行I/O。
用法:
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp) ;
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp) ;
返回:读或写的对象个数。
流定位
有两种方法定位标准I / O流。
(1) ftell和fseek。这两个函数自SYS V7以来就存在了,但是它们都假定文件的位置可以存放在一个长整型中。
(2) fgetpos和fsetpos。这两个函数是新由ANSI C引入的。它们引进了一个新的抽象数据类型fpos_t,它记录文件的位置。在非UNIX系统中,这种数据类型可以定义为记录一个文件的位置所需的长度。
需要移植到非U N I X系统上运行的应用程序应当使用fgetpos和fsetpos。
流定位,可参照系统调用lseek()。
ftell,fseek,rewind
ftell:
功能:确定文件位置。
用法:
#include <stdio.h>
long ftell(FILE *fp) ;
返回:若成功则为当前文件位置指示,若出错则为-1L
fseek:
用法:int fseek(FILE *fp, long offset, int whence) ;
返回值:若成功则为0,若出错则为非0(-1)。
rewind:
功能:定位到文件首
用法:void rewind(FILE *f p) ;
返回值:无
fgetpos, fsetpos
功能:获得或设置文件位置。
用法:
#include <stdio.h>
int fgetpos(FILE *fp, fpos_t *pos) ;
int fsetpos(FILE *fp, const fpos_t *pos) ;
返回值:
若成功则为0,若出错则为非0(-1).
格式化I/O
有两类格式化/O:
格式化输入
格式化输出
传统格式化输出
功能:
格式化输出;
用法:
#include <stdio.h>
int printf(const char *form a t, ...);
int fprintf(FILE *fp, const char *format, ...);
int sprintf(char *buf, const char *format, ...);
返回值:
存入数组的字符数。
可变长度参数格式输出
功能:
可变长度参数格式化输出:
用法:
#include <stdarg.h>
#include <stdio.h>
int vprintf(const char *format, va_list arg) ;
int vfprintf(FILE *fp, const char *format, va_list arg) ;
int vsprintf(char *buf, const char *format, va_list arg) ;
返回值:
若成功则为输出字符数,若输出出错则为负值。
传统格式化输入
功能:格式化输入。
用法:
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
int sscanf(const char *buf, const char *forma t, ...);
返回值:指定的输入项数,若输入出错,或在任意变换前已至文件尾端则为EOF。
获得文件描述符
在系统调用中使用一个整数来描述文件,但在高级I/O中却使用FILE指针来描述文件。
在高级I/O内的FILE结构中,仍然包含有系统调用中使用的整数描述符。FILE结构中的整数描述符可能通过函数fileno( )得到。
功能:获得低级文件描述符。
用法:
#include <stdio.h>
int fileno(FILE *fp) ;
返回:与流相关联的文件整数描述符。
说明:如果要使用dup()或fcntl()对文件进行控制时必须使用整数文件描述符。
临时文件
UNIX提供两个产生临时文件的函数:tmpnam( )和 tmpfile( )。用于临时文件的操作与使用。
tmpnap()
功能:产生临时文件名。
用法:
#include <stdio.h>
char *tmpnam(char *ptr) ;
返回:指向唯一路径名的指针
tmpfile()
功能:打开一个二进制临时文件;
用法:
FILE *tmpfile(void);
返回:若成功则为文件指针,若出错则为NULL。
临时文件的相关说明
使用临时文件时,tmpnam和tmpfile配合使用,先由tmpanm创建文件名,再由tmpfile打开临时文件。
使用临时文件的一些限制在/usr/include/bits/stdio_lim.h中定义。相关值有:
L_tmpnam 临时文件名长度
TMP_MAX 产生不重复临时文件的最大次数
FILENAME_MAX tmpnam 的最大长度
P_tmpdir 临时目录:/tmp
临时文件示例
#include <stdio.h>
main(){
char name[L_tmpnam], line[100];
FILE *fp;
printf("%s\n",tmpnam(NULL)); // 1st tmpnam
tmpnam(name); // 2nd tmpnam
printf("%s\n",name); // Disp 2nd tmpnam
if((fp=tmpfile())==NULL) // create temp file
perror(name);
fputs("One Line of Output!\n",fp); // write to tmpfile
rewind(fp); // to the Beginning
if(fgets(line,sizeof(line),fp)==NULL) perror("fgets");
fputs(line,stdout);
}
tempnam
tempnam是tmpnam的一个变体,它允许调用者为所产生文件名指定目录和前缀。
用法:
#include <stdio.h>
char *tempnam(const *dir, cost char *prefix);
说明:
(1) 如果定义了环境变量TMPDIR, 则用其作为目录。
(2) 如果参数dir 非NULL, 则用其作为目录。
(3) 将<stdio.h > 中的字符串P_tmpdir 用作为目录。
(4) 将本地目录,通常是/tmp , 用作为目录。
如果prefix 非NULL, 则它应该是最多包含5 个字符的字符串,用其作为文件名的头几个字符。
关于标准I/O的说明
标准I/O库并不完善—某些属于基本设计,但是大多数则与各种不同的实现有关。
不同厂家的实现相关很大,可以比较我们的教材和Linux系统的实现。
标准I/O库使用了缓存机制,而这种机制是产生很多问题,引起很多混淆的一个领域。
标准I/O库不能很好的控制屏幕等设备,若想很好地使用格式化输出控制屏幕输出可以使用curses库。