标准IO函数库隐藏了buffer大小和分配的细节,使得我们可以不用关心预分配的内存大小是否正确的问题。
虽然这使得这个函数库很容易用,但是如果我们对函数的原理不熟悉的话,也容易遇到很多问题。
前面的章节中,IO集中在文件描述符,每一个打开的文件都对应一个文件描述符,通过文件描述符对文件进行操作。
现在使用了标准IO库,讨论的重点集中在流(streams)。
简要了解一下流:
只有两个函数可以修改流的orientation:
fwide函数声明:
#include <stdio.h>
#include <wchar.h>
int fwide(FILE* fp, int mode);
函数返回值:
mode取值的不同决定函数fwide的不同的行为:
当我们打开一个流,函数fopen返回一个指向FILE对象的指针。FILE对象通常是一个结构体,包含所有控制流所需要的信息,包括:
缓存(buffering)的作用是为了尽可能少地调用read和write系统调用。
标准IO库提供三种类型的buffering:
完全缓存(Fully buffered):在这种缓存机制中,实际的IO操作发生在缓存被写满时。正在写入硬盘的文件被完全缓存在buffer中。缓存空间往往在第一次IO操作时通过调用malloc函数获取;
行缓存(Line buffered):在这种缓存机制中,实际的IO操作发生在新的一行字符被读入或者输出时,所以允许每一次只输出一个字符。行缓存有两点需要注意:buffer的大小是固定的,所以即使当前行没有读入或输出结束,依然可能发生实际的IO,当buffer被写满时;一旦有输入(从无缓存流或者行缓存流中输入)发生,所以已在buffer中缓存的输出流都会被立刻输出(flush)。
flush:标准IO缓存中内容立刻写入硬盘或者输出。在终端设备中,flush的作用也可能是丢弃缓存中得数据。
无缓存(Unbuffered):不缓存输入或输出内容。例如,如果我们使用fputs函数输出15个字符,那么我们希望这15个字符尽可能快地被打印出来。如标准错误输出就要求是无缓存输出。
ISO C标准要求下面的缓存特性:
上面的标准显然没有具体说明各种情况,一般来说:
我们可以使用函数setbuf和setvbuf函数更改流的缓存机制。
函数声明:
#include <stdio.h>
void setbuf(FILE* restrict fp, char* restrict buf);
int servbuf(FILE *restrict fp, char* restrict buf, int mode, size_t size);
函数返回值:
这些函数必须在流打开之后,其他流操作执行之前被调用。
函数作用:
setbuf可以打开或关闭缓存,打开缓存时,buf指向一个大小为BUFSIZ(stdio.h中定义的宏)的buffer,通常打开的时完全缓存,如果当前流关联的是终端设备,有的系统也会使用行缓存;
servbuf可以指定打开哪种类型的缓存。mode的参数可以取如下的值,如果指定为无缓存,则参数buf和size都会被忽略。
函数行为总结如下表所示:
通常来说,我们应该让系统自己选择buffer大小并自动分配,这样标准IO库会在关闭流时自动释放该内存。
flush函数。
函数声明:
#include <stdio.h>
int fflush(FILE *fp);
函数作用:
使得该流的所有缓存中未写入硬盘的数据传入内核中。
一种特殊情况是,如果fp为NULL,fflush会使得所有缓存的数据都被flush。
函数fopen、freopen和fdopen函数用来打开一个标准输入输出流。
函数声明:
#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 fd, const char *type);
函数细节:
参数type取值如下表所示,一共有15种取值,有得取值作用相同:
表格说明:
当打开一个流对文件进行读写时,有两个限制:
六种方式打开一个流总结如下表所示:
需要注意的一点是,当以w和a模式创建一个新文件时,并不能像open或create函数一样指定文件的权限标志位。
一种解决方法是通过调整我们的umask。
打开的流默认的是完全缓存,如果该流关联的是终端设备,则是行缓存。
像之前提到的那样,我们打开了一个流,并在其他操作之前,可以调用setbuf或setvbuf函数修改缓存方式。
关闭流
函数声明:
#include <stdio.h>
int fclose(FILE* fp);
函数细节,关闭流之前:
当我们打开一个流,我们有三种读写方式可供选择:
函数声明:
#include <stdio.h>
int getc(FILE* fp);
int fgetc(FILE* fp);
int getchar(void);
函数返回值:
函数细节:
函数声明:
#include <stdio.h>
int ferror(FILE* fp);
int feof(FILE* fp); // Both return: nonzero(true) if condition is true, 0(false) otherwise
void clearerr(FILE* fp);
在大多的实现中,FILE对象中会维护两个flag:
这两个flag都可以通过调用clearerr清空。
读取一个流后,我们可以调用函数ungetc压回读出来的字符。
函数声明:
#include <stdio.h>
int ungetc(int c, FILE* fp);
函数返回值:c if OK, EOF on error
函数细节:只支持单个个字符的压回。
使用场景:
压回操作常使用在下面的场景:对于一个输入流,我们需要根据下一个字符来判断该如何处理当前的字符。
输出函数和我们讨论过的输入函数一一对应,不再赘述。
函数声明:
#include <stdio.h>
int putc(int c, FILE* fp);
int fputc(int c, FILE* fp);
int putchar(int c);
函数fgets和gets提供了逐行输入功能。
函数声明:
#include <stdio.h>
char *fgets(char* restrict buf, int n, FILE* restrict fp);
char *gets(char* buf);
函数细节:
函数fputs和puts提供了逐行输出的功能。
函数声明:
#include <stdio.h>
int fputs(const char* restrict str, FILE* restrict fp);
int puts(const char* str);
函数细节:
比较标准:
将一定量的数据从标准输入拷贝到标准输出,计算这一过程所需要的
Code:
使用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);
}
使用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);
}
测试数据:95.8M 3百万行
测试结果(和第三章中的数据进行了对比,之前跳过了该章节,可以自行查看一下):
结果说明:
标准IO函数库分为两篇来介绍,本篇是第一篇,主要介绍了
参考资料:
《Advanced Programming in the UNIX Envinronment 3rd》