4读和写流
有三种不同类型的非格式化I/O和几种格式化I/O可以用于对一个打开的流进行读写。
每次一个字符的I/O
int getc(FILE *fp);
int getchar(void);
int fgetc(FILE *fp);
成功返回读到的字符,读到文件尾端或者出错返回EOF。
1)函数getchar等同于getc(stdin)。
2)这三个函数以unsigned char类型转换为int的方式返回读到的字符。说明为不带符号是为了,即使最高位为1,也不会使返回值为负。使用整型返回值是为了可以返回所有可能的字符值,再加上一个已发生错误或已到达文件尾端的指示值。EOF常数在stdio.h中定义为-1,这就意味着不能将这三个函数的返回值存放在一个字符变量中,因为以后还要用这些函数的返回值与常数EOF相比较。
3)在FILE对象中,为每个流保持了两个标志:出错标志和文件结束标志。为了区分出错还是到达文件尾,需要调用ferror或者feof。
函数clearerr清除这两个标志。
int ferror(FILE *fp); int feof(FILE *fp); 条件为真,返回非0,否则返回0。
void clearerr(FILE *fp);
4)从一个流读之后,可以调用ungetc将字符再送回流(先进后出)。EOF不能回送,但ungetc会清除该流的文件结束指示。 int ungetc(int c, FILE *fp); 成功返回c,出错返回EOF。
5)每次一个字符的I/O输出,包括:
int putc(int c, FILE *fp); int putchar(int c); int fputc(int c, FILE *fp);
putchar(c)等同于putc(c, stdout)。putc可以实现为宏,fputc则不可以。
每次一行的I/O
输入函数:
char *fgets(char *buf, int n, FILE *fp);
char *gets(char *buf);
gets从标准输入读,fgets从指定的流中读。
gets不推荐使用,因为它不指定缓存的长度,可能会造成缓存越界。fgets读到一个新行符为止,但不超过n-1个字符。
输出函数:
int fputs(char *buf, FILE *fp);
char *puts(char *buf);
二进制I/O
二进制文件中可能含有null字符和新行符,所以fputs和fgets不能正确工作,
而getc和putc需要循环通过整个结构,一次读或写一个字符,效率比较低,
所以使用fread和fwrite函数以执行二进制I/O操作。
size_t fread(void *ptr, size_t size, size_t num, FILE *fp);//从fp中读num个size大小的结构到ptr所指的缓存中。
size_t fwrite(void *ptr, size_t size, size_t num, FILE *fp);//从ptr所指缓存中取num个size大小的结构写到fp中。
使用二进制I/O的基本问题是它只能用于读写在同一个系统中的数据。原因是:
1)在一个结构中,同一成员的位移量可能随编译程序和系统的不同而异,因为不同的对齐要求。编译程序有选择项,
允许紧密包装结构(节省存储空间,但会降低运行性能),
或者准确对齐(耗费存储空间,但运行时易于存取结构的各个成员)。
2)用来存储多字节整数和浮点值的二进制格式在不同的系统结构间也可能不同。
3)在不同系统之间交换二进制数据的实际解决方法是使用较高层次的协议。
5.定位流
有两种方法定位标准I/O流:
1)ftell和fseek(V7系统),它们假定文件的位置可以存放在一个长整型中。
long ftell(FILE *fp); 成功返回当前文件位置指示,出错返回-1L。
int fseek(FILE *fp, long offset, int whence); //参数whence的值有:SEEK_SET、SEEK_CUR和SEEK_END
void rewind(FILE *fp); 将一个流设置到文件的起始位置。
对于一个二进制文件,其位置指示器是从文件起始位置开始度量,并以字节为计量单位的,例如ftell。
对于文本文件,它们的文件当前位置可能不是以简单的字节位移量来度量,在非UNIX系统中,它们可能以不同的格式存放文本文件,比如UTF-8格式,UNCODE格式等。为了定位一个文本文件,whence一定要是SEEK_SET,而且offset只能有两种值:0(反绕文件至其起始位置),或是对该文件的ftell所返回的值。
2)fgetpos和fsetpos(ANSI C系统),
它们引进了一个新的抽象数据类型fpos_t,它记录文件的位置。在非UNIX系统中,这种数据类型可以定义为记录一个文件的位置所需的长度。
int fgetpos(FILE *fp, fpos_t *pos);
int fsetpos(FILE *fp, fpos_t *pos);
fgetpos将文件位置指示器的当前值存入由pos指向的对象中,在以后调用fsetpos时,可以使用此值将流重新定位至该位置。
需要移植到非UNIX系统的应用程序应该使用fgetpos和fsetpos。
6.格式化I/O
格式化输出
int printf(const char *format, ...);
int fprintf(FILE *fp, const char *format, ...);
int sprintf(char *buf, const char *format, ...);
三个函数分别将格式化数据写到标准输出、指定的流fp和数组buf中,成功返回输出或者存入的字符数,出错返回负值。
三种printf族的变体类,可变参数表(...)变为了arg。
#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);
格式化输入
执行格式化输入的三个scanf函数
int scanf(const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
int sscanf(char *buf, const char *format, ...);
同样,它们也有可变长度参数表类型的变体函数:vscanf, vfscanf, vsscanf。
这些函数成功返回指定的输入项数,如果输入出错,或者在任意变换前以至文件尾端则为EOF。
7.临时文件
char *tmpnam(char *ptr);
产生一个与现存文件名不同的一个有效路径名字符串,返回指向该唯一路径名的指针。
如果ptr是NULL,则所产生的路径名存放在一个静态区中,指向该静态区的指针作为函数值返回。
下一次再调用tmpnam时,会重写该静态区。所以如果我们调用tmpnam多次,我们应该保存该路径名的副本,而不是指针的副本。
如果ptr不为NULL,则认为它指向长度至少是L_tmpnam个字符的数组。L_tmpnam定义在stdio.h文件中。
FILE *tmpfile(void); 成功返回文件指针,出错返回NULL。
tmpfile创建一个临时二进制文件(类型wb+),在关闭该文件或程序结束时将自动删除这种文件。
tmpfile函数的实现使用的标准UNIX技术就是先调用tmpnam产生一个唯一的路径名,然后立即unlink它。
对一个文件解除连接并不删除其内容,关闭该文件时才删除其内容。
char *tempnam(const char *directory, const char *prefix);
tempnam函数是tmpnam的一个变体,它允许调用者为所产生的路径名指定目录和前缀。
对于目录有四种不同的选择,并且按顺序检查每个选择,使用第一个为真的作为目录。
该函数同时也检查相应的目录是否有意义,如果目录不存在或者没有写许可权,则跳过这个选择,检查下一个选择:
1)检查是否定义了环境变量TMPDIR。
2)检查参数directory是否不为NULL。
3)将<stdio.h>中的字符串P_tmpdir作为目录。
4)将本地目录,通常是/tmp,作为目录。
如果prefix不为NULL,则它应该是最多包含5个字符的字符串,用其作为文件名的头几个字符。
因为5个字符长的前缀,4个字符长的进程内唯一标识,再加上5个字符长的系统内唯一标识(进程ID号),
正好组成14位的UNIX传统文件长度限制。
tempnam函数内部调用malloc分配动态存储区,用其存放所构造的路径名。
所以当不再使用tempnam返回的路径名时,应该要手工调用free函数释放该存储区。
1.