基本上所有的操作系统都实现了标准I/O库。
5.2流和FILE对象
所有的I/O函数都是针对文件描述符的。当文件打开时,就返回一个文件描述符,然后用这个文件描述符做后续的I/O操作。
标准I/O库的操作是围绕流来进行的,当标准I/O库打开一个文件的时候,一个文件已经与一个流关联了。
我们可以用freopen函数来清除一个流定向,然后用fwide函数来设置一个流定向。
int fwide(FIFE *fd, int mode)
5.4缓冲
标准I/O库提供缓冲的目的是尽可能减少使用read和write函数调用的次数。
第一次执行I/O操作时,相关标准I/O函数通常会调用malloc获得需要使用的缓冲区。
缓冲类型:全缓冲,行缓冲,不带缓冲。
一般标准出错时不带缓冲的,打开至终端设备的流是行缓冲的。其他则都是全缓冲的。
可以用下面的函数改变流的缓冲类型:
void setbuf(FIFE *restrict fp, char *restrict buf)
int setvbuf(FIFE *restrict fp, char *restrict buf, int mode, size_t size);
任何时候,我们都可以通过下面的函数强制冲洗一个流:
int fflust(FILE *fp)
该函数导致所有的数据都被传至内核。
5.5打开流
下列的函数可以用于打开一个标准的I/O流:
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);
调用fclose函数关闭一个打开的流。
int fclose(FILE *fp)
在一个文件被关闭前,冲洗刷新区中的输出数据。
5.6读和写流
一旦打开了一个流,就可以用三种类型的I/O进行操作:
1每次一个字符的I/O。
2每次一行的I/O。
3直接I/O。
一下三个函数可以用于一次读一个字符;
int getc(FILE *fp)
int fgetc(FILE *fp)
int getchar(void)
若成功返回下一个字符,若达到文件结尾或者出错返回EOF。
可以用函数ferror来判断流是否出错
int ferror(FILE *fp)
可以用feof函数判断是否到达文件流的结尾。
int feof(FILE *fp)
可以用下面三个函数实现输出功能:
int putc(int c, FILE *fd)
int fputc(int c, FILE *fd)
int putchar(int c)
其中putchar(c) 等效于 putc(c , stdout)
5.7每次一行I/O
下面的函数提供每次输入一行的功能:
char *fgets(char *restrict buf, int n, FILE *restrict fp)
char *getc(char *buf)
两个函数若成功,则返回buf,若已经到达文件结尾或者出错则返回NULL。
buf是存储数据的缓冲区的地址。
fputs和puts提供每次输出一行的功能。
int fputs(const char *restrict str, FILE *restrict fp)
次函数每次将一个null终止符的字符串写到指定的流,尾端的终止符null不写出。
puts函数将一个以null终止的的字符串写入标准输出。
int puts(const char *str)
getc和putc实例:
getctest.c:
#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);
}
运行结果如下:
[root@localhost apue]# vim getctest.c
[root@localhost apue]# gcc -o getctest.out getctest.c
[root@localhost apue]# ./getctest.out
huang
huang
chengdu
chengdu
fgets和fputs函数实例:
fgetstest.c:
#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);
}
运行结果:
[root@localhost apue]# vim fgetstest.c
[root@localhost apue]# gcc -o fgetstest.out fgetstest.c
[root@localhost apue]# ./fgetstest.out
huiang
huiang
gd
gd
5.9二进制I/O
由于前面的I/O函数的缺陷,我们可以用下面的函数做二进制I/O操作。
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)
返回值是读或者写的对象数。
5.10定位流
定位I/O流有留个函数:
第一组:
long ftell(FILE *fp)
int fseek(FILE *fp,long offset, int whence)
第二个参数是偏移量大小,第三个参数是SEEK_SET,SEEK_CUP,SEEK_END中的一个。
第二组:
off_t ftello(FILE *fd)
int fseeko(FILE *fd, off_t offset, int whence)
除了offset是off_t类型以外,ftello函数和ftell相同。fseeko和fseek相同。
第三组:
int fgetpos(FILE *restrict fp, fpos_t *restrict pos)
int fsetpos(FILE *fp, const fpos_t *pos)
如果问了程序的可移植性,最好用第三组。第一个函数把文件的位置存入pos变量。
5.11格式化I/O
格式化输出函数有下面8个,其中后面四个是变体,只是最后一个可变参数形式改变。
int printf(const char *restrict format, ...)
int fprintf( FILE *restrict 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, ...)
四种变体:这四个变体是ISO C标准中关于可变参数的定义实现。在头文件<stdarg.h>中定义。
int vprintf(const char *restrict format, var_list arg)
int vfprintf( FILE *restrict fp, const char *restrict format, var_list arg)
int vsprintf( char *restrict buf, const char *restrict format, var_list arg)
int vsnprintf( char *restrict buf, size_t n, const char *restrict format, var_list arg)
格式化输入有三个scanf函数:
int scanf(const char *restrict format, ...)
int fscanf(FILE *restrict fp, const char *restrict format, ...)
int sscanf(const char *restrict buf, const char *restrict, ...)
三种变体:这三个变体是ISO C标准中关于可变参数的定义实现。在头文件<stdarg.h>中定义。
int scanf(const char *restrict format, va_list arg)
int fscanf(FILE *restrict fp, const char *restrict format, va_list arg)
int sscanf(const char *restrict buf, const char *restrict, va_list arg)
5.12实现细节
在unix系统中,标准I/O库都需要调用第三章中的例程。每个标准I/O流都有一个与其相关联的文件描述符。可以对一个流
调用fileno函数以获得其描述符。
int fileno(FILE *fp)
例程:对每个标准I/O流打印缓冲状态信息。
ioprint.c:
#include "apue.h"
void pr_stdio(const char *,FILE *);
int main(void)
{
FILE *fp;
fputs("enter any character\n", stdout);
if (getchar() == EOF)
err_sys("getchar error");
fputs("one line to standard error\n", stderr);
pr_stdio("stdin", stdin);
pr_stdio("stdout", stdout);
pr_stdio("stderr",stderr);
if ((fp =fopen("/etc/motd", "r")) == NULL)
err_sys("fopen error");
if (getc(fp) == EOF);
err_sys("getc error");
pr_stdio("/etc/motd",fp);
exit(0);
}
void pr_stdio(const char *name, FILE *fp)
{
printf("stream = %s, ",name);
if (fp->_IO_file_flags & _IO_UNBUFFERED)
printf("unbuffered");
else if (fp->_IO_file_flags & _IO_LINE_BUF)
printf("line buffered");
else
printf("fully buffered");
printf(", buffer size = %d\n", fp->_IO_buf_end - fp->_IO_buf_base);
}
运行结果:
[root@localhost apue]# ./ioprint.out
enter any character
nihao
one line to standard error
stream = stdin, line buffered, buffer size = 1024
stream = stdout, line buffered, buffer size = 1024
stream = stderr, unbuffered, buffer size = 1
getc error: Success
5.13临时文件
下面两个函数可以用于帮助键临时文件:
char *tmpnam(char *ptr);
char *tmpfile(void);
第一个函数返回路径名的指针。如果ptr是NULL。则所产生的路径名存放在静态去中。
第二个函数返回文件指针,错误返回NULL。
函数实例:
tmptest.c:
#include "apue.h"
int main(void)
{
char name[L_tmpnam], line[MAXLINE];
FILE *fp;
printf("%s\n", tmpnam(NULL));
tmpnam(name);
printf("%s\n", name);
if ((fp = tmpfile()) == NULL)
err_sys("tmpfile error");
fputs("one line of output\n", fp);
rewind(fp);
if (fgets(line, sizeof(line), fp) == NULL)
err_sys("fgets error");
fputs(line,stdout);
exit(0);
}
运行结果:
[root@localhost apue]# gcc -o tmptest.out tmptest.c
/tmp/ccx3bsSv.o: In function `main':
tmptest.c:(.text+0x299): warning: the use of `tmpnam' is dangerous, better use `mkstemp'
[root@localhost apue]# ./tmptest.out
/tmp/file7mn10s
/tmp/filetrhn25
one line of output
上面的函数一个用于建临时目录,一个用于建临时文件。下面这个函数一次性搞定:
char *tempnam(const char *directory, const char prefix);
第一个参数是目录名,第二个参数是指定文件的前几个字母。
tempnam.c:
#include "apue.h"
int main(int argc,char *argv[])
{
if (argc != 3)
err_quit("usage: a.out <dirname> <prefix>");
printf("%s\n", tempnam(argv[1][0] != ' ' ? argv[1] : NULL, argv[2][0] != ' '? argv[2] : NULL));
exit(0);
}
运行结果:
[root@localhost apue]# gcc tempnam.c
/tmp/ccg6BuVi.o: In function `main':
tempnam.c:(.text+0x305): warning: the use of `tempnam' is dangerous, better use `mkstemp'
[root@localhost apue]# ./a.out
usage: a.out <dirname> <prefix>
[root@localhost apue]# ./a.out /home/huangcd TEMP
/home/huangcd/TEMPzyiJUu
[root@localhost apue]# ./a.out /home/huangcd " "
/home/huangcd/fileJn5HuC
[root@localhost apue]# ./a.out " " TEMP
/tmp/TEMPA04EcN
我们也可以用mkstemp函数。功能类似与tmpfile。但是返回的是文件描述符。
int mkstemp(char *template)
如果成功返回文件描述符,错误返回-1。
此函数创建的文件不可自动删除。