标准I/O库处理很多细节,如缓冲区分配、以优化的块长度执行I/O等。这些处理使用户不必担心如何选择使用正确的长度。本章深入了解I/O库函数的操作。
对于所有I/O函数(见第3章)都围绕文件描述符的。当打开一个文件时,即返回一个文件描述符,然后该文件描述符就用于后续的I/O操作。
对于标准I/O库,其操作是围绕流进行的。当用标准I/O库打开或创建一个文件时,已使一个流与一个文件相关联。
流的定向决定了所读、写的字符是单字节还是多字节的。当一个流最初创建时,它并没有定向。如若在未定向的流上使用一个多字节I/O函数(见
两个函数,freopen清除一个流的定向;fwide函数可用于设置流的定向。
#include
#include
int fwide(FILE* fp, int mode);
//返回值:若流是宽定向的,返回正值;若流是字节定向的,返回负值;若流是未定向的,返回0
根据mode参数的不同值,fwide函数执行不同的工作。
若mode参数值为负,fwide将试图使指定的流是字节定向的。
若mode参数值为正,fwide将试图使定的流是宽定向的。
若mode参数值为0,fwide将不试图设置流的定向,但返回标识该流定向的值。
注意:fwide并不改变已定向流的定向。同时,fwide无出错返回,所以需要利用全局变量errno来检测错误。
当打开一个流时,标准I/O函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了标准I/O库为管理该流需要的所有信息,包括用于实际I/O的文件描述符、指向用于流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等。类型FILE*称为文件指针。
3个流可以自动被进程使用,分别是标准输入(stdin)、标准输出(stdout)、标准错误(stderr),这三个文个把指针定义在头文件
i、缓冲类型
全缓冲:在填满标准I/O缓冲区后才进行实际I/O操作。(驻留在磁盘上的文件通常是由标准I/O库实施全缓冲的)
冲洗意味着将缓冲区中的内容写至磁盘上(该缓冲区可能只是部分填满)。刷清表示丢弃已存储在缓冲区中的数据。
行缓冲:当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。(当流涉及一个终端时(如标准输入和标准输出),通常使用行缓冲)
使用行缓冲的两个限制:一、只要填满缓冲区,即使还没有写一个换行符,也进行I/O操作。二、任何时候只要通过标准I/O库要求从一个不带缓冲的流,或一个行缓冲的流得到输入数据,那么就会冲洗所有行缓冲输出流。
不带缓冲:标准I/O库不对字符进行缓冲存储。(标准错误流stderr通常是不带缓训的)
ii、对任何一个给定的流,可通过以下两个函数更改缓冲类型。
#include
void setbuf(FILE* restrict fp, char* restrict buf);//设置缓冲
int setvbuf(FILE* restrict fp, char * restrict buf, int mode, size_t size);//设置缓冲,并指定缓冲类型
//返回值:若成功,返回0;若出错,返回非0
上述函数一定要在流已被打开后调用(因为每个函数都要求一个有效的文件指针作为它们的第一个参数),而且也应在对该流执行任何一个其他操作之前调用。
setbuf所设置的缓冲类型通常由系统决定,而使用setvbuf,可以精确说明所需的缓冲类型。通过mode参数实现,具体说明见下。
_IOFBF 全缓冲
_IOLBF 行缓冲
_IONBF 不带缓冲
iii、使用fflush函数可认强制冲洗一个流。
#include
int fflush(FILE* fp);
//返回值:若成功,返回0;若出错,返回EOF
此函数使该流所有未写的数据都被传送至内核。作为一种特殊情形,如若fp是NULL,则此函数将导致所有输出流被冲洗。
以下3个函数打开一个标准I/O流。
#include
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 fd, const char* type);
//返回值:若成功,返回文件指针;若出错,返回NULL
3个函数的区别如下:
a、fopen函数打开路径名为pathname的一个指定的文件。
b、freopen函数在一个指定的流上打开一个指定的文件,若该流已经打开,则先关闭访该流。若该流已经定向,则使用freopen清除该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准错误。
c、fdopen函数取一个已有的文件描述符,并使一个标准的I/O流与该描述符相结合。
type参数指定对该I/O流的读、写方式,ISO C规定type参数可以有15种不同的值,如下表所示。
type | 说明 | open标志 |
r或rb | 为读而打开 | O_RDONLY |
w或wb | 把文件截断至0长,或为写而创建 | O_WRONLY|O_CREAT|O_TRUNC |
a或ab | 追加;为在文件尾写而打开,或为写而创建 | O_WRONLY|O_CREAT|O_APPEND |
r+或r+b或rb+ | 为读和写而打开 | O_RDWR |
w+或w+b或wb+ | 把文件截断至0长,或为读和写而打开 | O_RDWR|O_CREAT|O_TRUNC |
a+或a+b或ab+ | 为在文件尾读和写而打开或创建 | O_RDWR|O_CREAT|O_APPEND |
当以读和写类型打开一个文件时(type中+号),具有下列限制。
i、如果中间没有fflush、fseek、fsetpos或rewind,则在输出的后面不能直接跟随输入。
ii、如果中间没有fseek、fsetpos或rewind,或者一个输入操作没有到达文件尾端,则在输入操作之后不能直接跟随输出。
此处这点,理解的不是很透彻,难道是说,fseek->write->fseek->read这种情况下才能循环交替写、读操作????
调用fclose关闭一个打开的流。
#include
int fclose(FILE* fp);
//返回值:若成功,返回0;若出错,返回EOF
在该文件被关闭之前,冲洗缓冲中的输出数据。缓冲区中的任何输入数据被丢弃。如果I/O库已经为该流自动分配了一个缓冲区,则释放此缓冲区。(自动分配的缓冲区,自动回收。)
a、读和写流
对于打开的流,可在3种不同类型的非格式化I/O中进行选择,对其进行读、写操作。
i、每次一个字符的I/O。一次读或写一个字符,如果流是带缓冲的,则标准I/O函数处理所有缓冲。(以字符为单位)
ii、每次一行的I/O。如果想发一次读或写一行,则使用fgets和fputs。每行都以一个换行符终止。当调用fgets时,应说明能处理的最大行长。(以行为单位)
iii、直接I/O。fread和fwrite函数支持此类型的I/O。每次I/O操作读或写某种数量的对象,而每个对象具有指定的长度。此两函数常用于从二进制文件中每次读或写一个结构。
针对输入函数
以下3个函数可用于一次读或写一个字符。
#include
int getc(FILE* fp);
int fgetc(FILE* fp);
int getchar(void);
//返回值:若成功,返回下一个字符;若已到达文件尾端或出错,则返回EOF
号外号外,返回值是下一个字符的asci码的整型值哈!这是有原因的。(整型返回值,这样就可以返回所有可能的字符值再加上一个已出错或已到达文件尾端的指示值。在
前两个函数的主要区别:getc可被实现为宏,而fgetc不能实现为宏。以下几点需要注意。
i、getc的参数不应当是具有副作用的表达式,因为它可能会被计算多次。(也要防止脑惨替换带来的影响)
ii、因为fgetc一定是个函数,所以可以等到其地址。于是允许fgetc的地址作为一个参数传送给另一个函数。
iii、调用fgetc所需时间很可能比调用getc要长,因为调用函数所需的时间通常长于调用宏。
从流中读取数据以后,可以调用ungetc将字符再压送回流中。
#include
int ungetc(int c, FILE* fp);
////返回值:若成功,返回c;若出错,返回EOF
压送回到流中的字符以后又可从流中读出。但读出字符的顺序与压送回的顺序相反。同时要求实现提供一次只回送一个字符。不能期望一次能回送多个字符。
用ungetc压送回字符时,并没有将它们写到底层文件中或设备上,只是将它们写回标准I/O库的流缓冲区中。
针对输出函数
#include
int putc(int c, FILE* fp);
int fputc(int c, FILE* fp);
int putchar(int c);
//返回值:若成功,返回c;若出错,则返回EOF
与输入函数一样,putchar(c)等同于putc(c,stdout),putc可被实现为宏,而fputc不能实现为宏。(需要注意点和getc/fgetc/getchar一样)
以下代码是getc和putc函数应用的举例。
#include "apue.h"
int
main(void)
{
int c;
while ((c = getc(stdin)) != EOF)
if (putc(c, stdout) == EOF)
err_sys("output error");
if (ferror(stdin))
err_sys("input error");
exit(0);
}
b、每次一行I/O
针对输入函数(每次输入一行)
#include
char* fgets(char* restrict buf, int n, FILE* restrict fp);//将读入的行,存放在buf中,并自动添加null字节作为结尾符
//返回值:若成功,返回buf;若已到达文件尾端或出错,返回NULL
fgets,必须指定缓冲的长度n。此函数一直读到下一个换行符为止,但是不超过n-1个字符,读入的字符被送入缓冲区。该缓冲区以null字节结尾。若该行包括最后一个换行符的字符数超过n-1,则fgets只返回一个不完整的行,但是,缓冲区总是以null字节结尾。对fgets的下一次调用会继续该行。
针对输出函数(每次输出一行)
#include
int fputs(const char* restrict str, FILE *restrict fp);//输出以null字节为终止标志而不是以换行符
//返回值:若成功,返回非负值;若出错,返回EOF
fputs将一个以null字节终止的字符串写到指定的流,尾端的终止符null不写出。值得注意,并不一定是每次输出一行,因为字符串不需要换行符作为最后一个非null字节。通常,在null字节之前是一个换行符,但并不要求总是如此。
以下代码是fgets和fputs函数应用的举例。
#include "apue.h"
int
main(void)
{
char buf[MAXLINE];
while (fgets(buf, MAXLINE, stdin) != NULL)
if (fputs(buf, stdout) == EOF)
err_sys("output error");
if (ferror(stdin))
err_sys("input error");
exit(0);
}
以下函数可以一次性对结构类型进行读、写操作。
#include
size_t fread(void* restrict ptr, size_t size, size_t nobj, FILE* restrict fp);
size_t fwrite(const void* restrict ptr, size_t size, size_t nobj, FILE* restrict fp);
//两个函数的返回值:读或写的对象数
常见用法如下举例。
i、读或写一个二进制数组。例如,为了将一个浮点数组的第2-5个元素写至一文件上,可如下实现:
float data[10];
if (fwrite(&data[2], sizeof(float), 4, fp) != 4)
err_sys("fwrite error");
其中,指定size为每个数组元素的长度,nobj为欲写的元素个数。
ii、读或写一个结构。例如,可以编写如下程序:
struct {
short count;
long total;
char name[NAMESIZE];
}item;
if (fwrite(&item, sizeof(item), 1, fp) != 1)
err_sys("fwrite error");
其中,指定size为结构的长度,nobj为1(要写的对象个数)。
将以上两例子结合起来就可读或写一个结构数组。为此,size应当是该结构的sizeof,nobj应是该数组中的元素个数。
a、格式化输出函数(老生常谈,此处只列举,不再作进一步解释)
#include
int printf(const char *restrict format, ...);//写出到标准输出
int fprintf(FILE* restrict fp, const char* restrict format, ...);//写出到指定流
int dprintf(int fd, const char * restrict format, ...);//写出指定文件描述符
//3个函数返回值:若成功,返回输出字符数;若输出出错,返回负值
int sprintf(char *restrict buf, const char *restrict format, ...);//格式化的字符串输出到指定buffer
//返回值:若成功,返回存入数组的字符数;若编码出错,返回负值
int snprintf(char* restrict buf, size_t n, const char* restrict format, ...);
//返回值:若缓冲区足够大,返回将要存入数组的字符数;若编码出错,返回负值
b、格式化输入函数(同样,不再作进一步解释)
#include
int scanf(const char* restrict format, ...);
int fscanf(FILE* restrict fp, const char *restrict format, ...);
int sscanf(const char* restrict buf, const char *restrict format, ...);
//3个函数返回值:赋值的输入项数;若输入出错或在任一转换前已到达文件尾端,返回EOF
ISO C标准I/O库提供了两函数创建临时文件。
#include
char* tmpnam(char* ptr);
//返回值:指向唯一路径名的指针
FILE* tmpfile(void);//产生的临时文件在关闭文件或程序结束时,自动删除该文件
//返回值:若成功,返回文件指针;若出错,返回NULL
tmpnam函数产一个与现有文件名不同的一个有效路径名字符串。每次调用它时,都产生一个不同的路径名。最多调用次数是TMP_MAX。TMP_MAX定义在
tmpfile创建一个临时二进制文件(类型wb+),在关闭该文件或程序结束时将自动删除这种文件。
以下代码是上述两函数应用的举例。
#include "apue.h"
int
main(void)
{
char name[L_tmpnam], line[MAXLINE];
FILE *fp;
printf("%s\n", tmpnam(NULL)); /* first temp name */
tmpnam(name); /* second temp name */
printf("%s\n", name);//输出与前一个输出不一样,name也作为函数值返回
if ((fp = tmpfile()) == NULL) /* create temp file */
err_sys("tmpfile error");
fputs("one line of output\n", fp); /* write to temp file *///将内容写入fp所代表的文件中
rewind(fp);//目的是将流设置到文件起始位置 /* then read it back */
if (fgets(line, sizeof(line), fp) == NULL)//从fp所代表的文件中获取内容
err_sys("fgets error");
fputs(line, stdout);//将内容写出到标准输出上 /* print the line we wrote */
exit(0);
}
以下是另外两个处理临时文件的函数。
#include
char* mkdtemp(char* template);
//返回值:若成功,返回指向目录名的指针;若出错,返回NULL
int mkstemp(char* template);
//返回值:若成功,返回文件描述符;若出错,返回-1
mkdtemp函数创建一个目录,该目录有一个唯一的名字;(访问权限为S_IRUSR|S_IWUSR|S_IXUSR)
mkstemp函数创建一个文件,该文件有一个唯一的名字。与tempfile不同,mkstemp创建的临时文件并不会自动删除。如果希望从文件系统命名空间中删除该文件,需要主动调用unlink函数解除连接。(访问权限为S_IRUSR|S_IWUSR)
以下代码是mkstemp函数应用的举例。
#include "apue.h"
#include
void make_temp(char *template);
int
main()
{
char good_template[] = "/tmp/dirXXXXXX"; /* right way */
char *bad_template = "/tmp/dirXXXXXX"; /* wrong way*/
printf("trying to create first temp file...\n");
make_temp(good_template);
printf("trying to create second temp file...\n");
make_temp(bad_template);
exit(0);
}
void
make_temp(char *template)
{
int fd;
struct stat sbuf;
if ((fd = mkstemp(template)) < 0)
err_sys("can't create temp file");
printf("temp name = %s\n", template);
close(fd);
if (stat(template, &sbuf) < 0) {
if (errno == ENOENT)
printf("file doesn't exist\n");
else
err_sys("stat failed");
} else {
printf("file exists\n");
unlink(template);
}
}