5.1 流和 FILE 对象

  • 对于标准 I/O 库,它们的操作则是围绕流(stream)进行的。
  • 当打开一个流时,标准 I/O 函数 fopen 返回一个指向 FILE 对象的指针。
  • 为了引用一个流,需要将 FILE 指针作为参数传递给每个标准 I/O 函数

5.2 标准输入、标准输出和标准出错

​ 对一个进行预定义了三个流,他们自动地可为进程使用:标准输入(STDIN_FILENO),标准输出(STDOUT_FILENO),标准出错(STDERR_FILENO)。

5.3 缓存

​ 标准 I/O 提供缓存地目的是尽可能减少使用 read 和 write 调用的数量(减少系统调用的数量,以减少系统开销)。

  1. 全缓存。当填满标准 I/O 缓存后才进行实际 I/O 操作。对于驻在磁盘上的文件通常是有标准 I/O 库实施全缓存的。在一个刘尚执行第一次 I/O 操作时,相关标准 I/O 函数通常调用 malloc 获得需使用的缓存。
  2. 行缓存。当在输入和输出中遇到新行符时,标准 I/O 库执行 I/O 操作。当涉及一个终端时(例如标准输入和标准输出),典型地使用行缓存。
  3. 不带缓存。相当于用 write 系统调用函数将这些字符写至相关联的打开文件上。标准出错流 stderr 通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新行字符。

​ ANSI C 要求下列缓存特征:

  1. 当且仅当标准输入和标准输出并不涉及交互作用设备时,它们才是全缓存的。
  2. 标准出错决不会是全缓存的。

​ SVR4 和 4.3+BSD 的系统默认使用下列类型的缓存:

  1. 标准出错是不带缓存的。
  2. 如若是涉及终端设备的其他流,则它们是行缓存的;否则是全缓存的。

​ 对任何一个给定的流,如果我们并不喜欢这些系统默认,则可调用下列两个函数中的一个 更改缓存类型:

#include <stdio.h>
void setbuf(FILE *fp, char *buf) ;
int setvbuf(FILE *fp, char *buf, int mode, size_t size)
// 返回:若成功则为 0,若出错则为非 0

​ 可以使用 setbuf 函数打开或关闭缓存机制。关闭缓存应将 buf 设置为 NULL。

​ 使用 setvbuf,可以精确地说明所需地缓存类型。这是 mode 参数实现地:

  • _IOFBF 全缓存
  • _IOLBF 行缓存
  • _IONBF 不带缓存

​ 下标列出了这两个函数地动作,一级它们地各个选项。

​ 任何时候可以强制刷新一个流。

#include <stdio.h>
int fflush(FILE *fp)
// 返回:若成功则为 0,若出错则为 EOF

​ 如若 fp 是 NULL,则此函 数刷新所有输出流。

5.4 打开流

​ 下列三个函数可用于打开一个标准 I/O 流。

#include <stdio.h>
FILE *fopen(const char *pathname, const char *type) ;
FILE *freopen(const char *pathname, const char *type, FILE *fp) ;
FILE *fdopen(int filedes, const char *type)
// 三个函数地返回:若成功则为文件指针,若出错则为 NULL

​ 三个函数的区别是:

  1. fopen 打开路径名由 pathname 指示的一个文件。
  2. freopen 函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准出错。
  3. fdopen 取一个现存的文件描述符,并使一个标准的 I/O 流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数获得的描述符。因为这些特殊类型的文件不能用标准 I/O fopen 函数打开,首先必须先调用设备专用函数以获得一个文件描述符,然后用 fdopen 使一个标准 I/O 流与该描述符相结合。

​ type 参数指定对流 I/O 流的读写方式,ANSI C 规定 type 参数可以有 15 种不同的值,见下表。

​ 对于 fdopen,type 参数的意义则稍有区别。因为该描述符已被打开,所以 fdopen 为写而打开并不截短该文件。

​ 当以读和写类型打开一文件时(type 中 + 号),具有下列限制:

  • 如果中间没有 fflush、fseek、fsetpos 或 rewind,则在输出的后面不能直接跟随输入。
  • 如果中间没有 fseek、fsetpos 或 rewind,或者一个输出操作没有到达文件尾端,则在输入操作之后不能直接跟随输出。

​ 下标列出了打开一个流的六种不同的方式。

​ 调用 fclose 关闭一个打开的流。

#include <stdio.h>
int fclose(FILE *fp);
// 返回:若成功则为0,若出错则为EOF

在该文件被关闭之前,刷新缓存中的输出数据。缓存中的输入数据被丢弃。如果标准 I/O 库已经为该流自动分配了一个缓存,则释放此缓存。

​ 当一个进程正常终止时(直接调用 exit 函数,或从 main 函数返回),则所有带未写缓存数据 的标准 I/O 流都被刷新,所有打开的标准 I/O 流都被关闭。

5.5 读和写流

​ 一旦打开了流,则可在三种不同类型的非格式化 I/O 中进行选择,对其进行读、写操作。

  1. 每次一个字符的 I/O。一次读或写一个字符,如果流是带缓存的,则标准 I/O 函数处理所有缓存。
  2. 每次一行的 I/O。使用 fgets 和 fputs 一次读或写一行。每行都以一个新行符终止。当调用 fgets 时,应说明能处理的最大行长。
  3. 直接 I/O。fread 和 fwrite 函数支持这种类型的 I/O。每次 I/O 操作读或写某种数量的对象,而每个对象具有指定的长度。这两个函数常用于从二进制文件中读或写一个结构。

5.5.1 输入函数

​ 以下三个函数可用于一次读一个字符。

#include <stdio.h>
int getc(FILE *fp) ;
int fgetc(FILE *fp) ;
int getchar(void);
// 三个函数的返回:若成功则为下一个字符,若已处文件文件尾端或出错则为 EOF

​ 函数 getchar 等同于 getc(stdin)。前两个函数的区别是,getc 可被实现为宏,而 fgetc 不能实现为宏。这意味着以下几点。

  1. getc 的参数不应当是具有副作用的表达式,因为它可能会被计算多次。
  2. 因为 fgetc 一定是个函数,所以可以得到其地址。着就允许将 fgetc 的地址作为一个参数传送给另一个函数。
  3. 调用 fgetc 所需时间很可能比调用 getc 要长,因为调用函数所需的时间通常长于调用宏。

​ 注意,不管是出错还是到达文件尾端,这 3 个函数都返回同样的值。为了区分这两种不同的情况,必须调用 ferror 或 feof。

#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
// 两个函数返回值:若条件为真,返回非 0(真);否则,返回 0(假)void clearerr(FILE *fp);

​ 在大多数实现种,为每个流在 FILE 对象种维护了两个标志:

  • 出错标志;
  • 文件结束标志。

​ 调用 clearerr 可以消除这两个标志。

​ 从流种读取数据以后,可以调用 ungetc 将字符再压送回流中。

#include <stdio.h>
int ungetc(int c, FILE *fp);
// 返回值:若成功,返回 c;若出错,返回 EOF

5.5.2 输出函数

​ 对应于上面所述的每个输入函数都有一个输出函数。

#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
// 三个函数返回值:若成功,返回 c;若出错,返回 EOF

​ 与输入函数一样,putchar© 等同于 putc(c, stdout),putc 可被实现为宏,而 fputc 不能实现为宏。

5.6 每次一行 I/O

​ 下面两个函数提供每次输入一行的功能。

#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
// 两个函数返回值:若成功,返回 buf;若已到达文件尾端或出错,返回 NULL

​ 这两个函数都指定了缓冲区的地址,读入的行将送入其中。gets 从标准输入读,而 fgets 则从指定的流读。

​ 对于 fgets,必须指定缓冲的长度 n。此函数已知读到下一个换行符为止,但是不超过 n-1 个字符,读入的字符被送入缓冲区。该缓冲区以 null 字节结尾。如若该行包括最后一个换行符的字符数超过 n-1,则 fgets 只返回一个不完整的行,而且缓存总是以 null 字符结尾。对 fgets 的下一次调用会继续读该行。

​ gets 是一个不推荐使用的函数。问题是调用者在使用 gets 时不能指定缓存的长度。这样就可能造成缓存越界(如若该行长于缓存长度),写到缓存之后的存储空间中,从而产生不可预料的后果。

​ 虽然 ANSI C 要求提供 gets,但请不要使用它。

​ fputs 和 puts 提供每次输出一行的功能。

#include <stdio.h>
int fputs(const char *str, FILE *fp);
int puts(const char *str);
// 两个函数返回:若成功则为非负值,若出错则为 EOF

​ 函数 fputs 将一个以 null 符终止的字符串写到指定的流,终止符 null 不写出。

​ puts 将一个以 null 符终止的字符串写到标准输出,终止符不写出。但是,puts 然后又将一个新行符写到标准输出。

​ puts 并不像它所对应的 gets 那样不安全。但是我们还是应避免使用它,以免需要记住它在最后又加上了一个新行符。

5.7 标准 I/O 的效率

​ 使用 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);
}

​ 可以用 fgetc 和 fputx 改写该程序,这两个一定是函数,而不是宏。

​ 使用 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);
}

​ 以上两个程序没有显式地关闭标准 I/O 流。我们知道 exit 函数将回刷新任何未写的数据,然后关闭所有打开的流。

​ 以下是标准 I/O 例程得到的时间结果。

​ 系统 CPU 时间相同的原因是因为所有这些程序对内核提出的读、写请求数相同。注意,使用标准 I/O 例程的一个优点是无需考虑缓存及最佳 I/O 长度的选择。

​ 最后一列是每个 main 函数的文本空间字节数(由 C 编译产生的机器指令)。从中可见,使用 getc 的版本在文本空间中作了 getc 和 putc 的宏代换,所以它所需使用的指令数超过了调用 fgetc 和 fputc 函数所需指令数。观察 getc 版本和 fgetc 版本在用户 CPU 时间方面的差别,可以看到在程序中作宏代换和调用两个函数在进行本测试的系统上并没有造成多大差别。

​ 使用每次一行 I/O 版本其速度大约是每次一个字符版本的两倍(包括用户 CPU 时间和时钟时间)。

5.8 二进制 I/O

​ 如果为二进制 I/O,那么我们更愿意一次读或写整个结构。为了使用 getc 或 putc做到这一点,必须循环通过整个结构,一次读或写一个字节。因为 fputs 在遇到 null 字节时就停止,而在结构中可能含有 null 字节,所以不能使用每次一行函数实现这种要求。相类似,如果输入数据中包含有 null 字节或新行符,则 fgets 也不能正确工作。因此,提供了下列两个函数以执行二进制 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);
// 两个函数的返回:读或写的对象数

​ 这些函数有两个常见的用法:

  1. 读或写一个二进制数组。例如,将一个浮点数组的第 2 至第 5 个元素写至一个文件上,可以写作:

    float data[10];if (fwrite(&data[2], sizeof(float), 4, fp) != 4)err_sys("fwrite error");
    

    其中,指定 size 为每个数组元素的长度,nobj 为预写的元素数。

  2. 读或写一个结构。例如,可以写作:

    struct {short count;long total;char name[NAMESIZE];
    } item;if (fwrite(&item, sizeof(item), 1, fp) != 1)err_sys("fwrite error");
    

​ 其中,指定 size 为结构的长度,nobj 为 1(要写的对象数)。

​ fread 和 fwrite 返回读或写的对象数。对于读,如果出错或到达文件尾端,则此数字可以少于 nobj。在这种情况,应调用 ferror 或 feof 以判断究竟是那一种情况。对于写,如果返回值少于所要求的 nobj,则出错。

​ 使用二进制 I/O 的基本问题是,它只能用于读已写在同一系统上的数据。对于通过网络互联的异构系统,在一个系统上写的数据,在另一个系统上处理,这两个函数不能正常工作,其原因是:

  1. 在一个结构中,同一成员的位移量可能随编译程序和系统的不同而异(由于不同的对准要求)。
  2. 用来存储多字节整数和浮点值的二进制格式在不同的系统结构间也可能不同。

​ 在不同系统之间交换二进制数据的实际解决方法是使用较高层次的协议。

5.9 定位流

​ 有两种方法定位标准 I/O 流。

  1. ftell 和 fseek。这两个函数自 V7 以来就存在了,但是它们都假定文件的位置可以存放在一个长整型中。
  2. fgetpos 和 fsetpos。这两个函数是新由 ANSI C 引入的。它们引进了一个新的抽象数据类型 fpos_t,它记录文件的位置。

​ 需要移植到非 UNIX 系统上运行的应用程序应当使用 fgetpos 和 fsetpos。

#include <stdio.h>
long ftell(FILE *fp); // 返回:若成功则为当前文件位置指示,若出错则为 -1L
int fseek(FILE *fp, long offset, int whence); // 返回:若成功则为0,若出错则为非 0
void rewind(FILE *fp);

​ 对于一个二进制文件,其位置指示器是从文件起始位置开始度量,并以字节为计量单位的。ftell 用于二进制文件时,其返回值就是这种字节位置。为了用 fseek 定位一个二进制文件,必须指定一个字节 offset,以及解释这种位移量的方式。whence:SEEK_SET 表示从文件的起始位置开始,SEEK_CUR 表示从当前文件位置,SEEK_END 表示从文件的尾端。

​ 对于文本文件,它们的文件当前位置可能不以简单的字节位移量来度量。

​ 下列两个函数是 C 标准新引进的。

#include <stdio.h>
int fgetpos(FILE *fp, fpos_t *pos);
int fsetpos(FILE *fp, const fpos_t *pos);
// 两个函数返回:若成功则为 0,若出错则为非 0

​ fgetpos 将文件位置指示器的当前值存入由 pos 指向的对象中。在以后调用 fsetpos 时,可以使用此值将流重新定位至该位置。

5.10 格式化 I/O

5.10.1 格式化输出

​ 执行格式化输出处理的是三个 printf 函数。

#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *fp, const char *format, ...);
// 两个函数返回:若成功则为输出字符数,若输出出错则为负值int sprintf(char *buf, const char *format, ...);
// 返回:存入数组的字符数

​ printf 将格式化数据写到标准输出,fprintf 写至指定的流,sprintf 将格式化的字符送入数组 buf 中。 sprintf 在该数组的尾端自动加一个 null 字节,但该字节不包括在返回值中。

​ 下列三种 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);
// 返回:存入数组的字符数

5.10.2 格式化输入

​ 执行格式化输入处理的是三个 scanf 函数。

#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
int sscanf(const char *buf, const char *format, ...);
// 三个函数返回:指定的输入项数,若输入出错,或在任意变换前已至文件尾端则为 EOF

5.11 实现细节

​ 每个 I/O 流都有一 个与其相关联的文件描述符,可以对一个流调用 fileno 以获得其描述符。

#include <stdio.h>
int fileno(FILE *fp);
// 返回:与该流相关联的文件描述符

​ 如果要调用 dup 或 fcntl 等函数,则需要此函数。

​ 对各个标准 I/O 流打印缓存状态信息。

#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->_flags & _IONBF)printf("unbuffered");else if (fp->_flags & _IOLBF)printf("line buffered");elseprintf("fully buffered");printf(", buffer size = %d\n", fp->_freeres_buf);
}

​ 从中可见,该系统的默认是:当标准输入、输出连至终端时,它们是行缓存的。行缓存的长度是 128 字节。

5.12 临时文件

​ 标准 I/O 库提供了两个函数以帮助创建临时文件。

#include <stdio.h>
char *tmpnam(char *ptr);
// 返回:指向一唯一路径名的指针FILE *tmpfile(void);
// 返回:若成功则为文件指针,若出错则为 NULL

​ tmpnam 产生一个与现在文件名不同的一个有效路径名字符串。每次调用它时,它都产生一个不同的路径名,最多调用次数是 TMP_MAX。

​ tmpfile 创建一个临时二进制文件(类型 wb+),在关闭该文件或程序结束时将自动删除这种文件。

​ tmpnam 和 tmpfile 函数实例。

#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);
}

​ tempnam 是 tmpnam 的一个变体,它允许调用者为所产生的路径名指定目录和前缀。

#include <stdio.h>
char *tempnam(const char *directory, const char *prefix);
// 返回:指向一唯一路径名的指针

​ 对于目录有四种不同的选择,并且使用第一个为真的作为目录:

  1. 如果定义了环境变量 TMPDIR,则用其作为目录。
  2. 如果参数 directory 非 NULL,则用其作为目录。
  3. 将 <stdio.h> 中的字符串 P_tmpdir 用作为目录。
  4. 将本地目录,通常是 /tmp,用作为目录。

​ 如果 prefix 非 NULL,则它应该是最多包含 5 个字符的字符串,用其作为文件名的头几个字符。

​ tempnam 函数的应用。

#include "apue.h"int main(int argc, char* argv[])
{if (argc != 3)err_quit("usage: a.out <directory> <prefix>");printf("%s\n", tempnam(argv[1][0] != ' ' ? argv[1] : NULL,argv[2][0] != ' ' ? argv[2] : NULL));exit(0);
}

UNIX 环境高级编程总结——第五章 标准I/O 库相关推荐

  1. 《Unix环境高级编程》Note——第一章基础知识

    文章目录 第一章 Unix基础知识 1.引言 2.Unix体系结构 3.登录 4.文件和目录 5.输入和输出 6.程序和进程 7.出错处理 8.用户标识 9.信号 10.时间值 11.系统调用和库函数 ...

  2. 《Unix环境高级编程》学习笔记:从点到面

    以前在课堂上学习过<Unix初级教程(第四版)>,对于Unix有了一点了解.由于以后使用的需要,要对它进行比较深入的学习,为此需要阅读不少的书籍,这本<Unix环境高级编程>便 ...

  3. Unix环境高级编程 笔记

    Unix环境高级编程(第二版)学习笔记 这是一次较长时间的整理,然而跳跃了一些章节和很多知识点,仍然是很不完善很不全面的. 前言 操作系统某些问题 严格意义上,可将操作系统定义为一种软件,它控制计算机 ...

  4. 《UNIX环境高级编程(第3版)》——1.7 出错处理

    本节书摘来自异步社区<UNIX环境高级编程(第3版)>一书中的第1章,第1.7节,作者:[美]W. Richard Stevens , Stephen A.Rago著,更多章节内容可以访问 ...

  5. 开发日记-20190822 关键词 读书笔记《Unix环境高级编程(第二版)》《掌控习惯》DAY 2

    Preface 话说,昨天开始尝试着去改变自己,从基础的习惯开始,11:30准时睡觉,平时差不多12:30才睡觉.按理说,比平时早了一个小时睡觉吧,然后我就把闹钟提前了45分钟,想着还能比平常多睡15 ...

  6. UNIX环境高级编程笔记之文件I/O

    一.总结 在写之前,先唠几句,<UNIX环境高级编程>,简称APUE,这本书简直是本神书,像我这种小白,基本上每看完一章都是"哇"这种很吃惊的表情.其实大概三年前,那会 ...

  7. (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  8. unix环境高级编程基础知识之第二篇(3)

    看了unix环境高级编程第三章,把代码也都自己敲了一遍,另主要讲解了一些IO函数,read/write/fseek/fcntl:这里主要是c函数,比较容易,看多了就熟悉了.对fcntl函数讲解比较到位 ...

  9. 《UNIX环境高级编程(第3版)》——2.6 选项

    本节书摘来自异步社区<UNIX环境高级编程(第3版)>一书中的第2章,第2.6节,作者:[美]W. Richard Stevens , Stephen A.Rago著,更多章节内容可以访问 ...

最新文章

  1. 2013——M笔试南京——程序
  2. html 判断页面支持canvas,HTML5 Canvas之测试浏览器是否支持Canvas的方法
  3. [计算机网络 谢希仁] 第一章
  4. 关于javascript中私有作用域的预解释
  5. 男朋友出的性格测试题
  6. ssm使用全注解实现增删改查案例——web.xml
  7. netbeans7.4_NetBeans 7.2 beta:更快,更有用
  8. 流量专家为114搜索提供权威流量访问统计
  9. vue 功能模块后台可配置_Github14k的Springboot后台管理系统
  10. php输出位置最右,php动态读取数据清除最右边距的方法
  11. SQL中GROUP BY的理解
  12. Django笔记 —— 表单(form)
  13. [转]良好用户体验的网站主页需具备12个特征
  14. Excel表格合并单元格丢失边框
  15. fresco android 6.0 不支持 gif,Fresco Gif加载优化
  16. bash-記錄修改OFS和ORS讓$0重新計算
  17. android svg图片使用
  18. 算法与数据结构学习资料及面试
  19. win10安装quicktime
  20. 《CCNA学习指南:Cisco网络设备互连(ICND2)(第4版)》——1.3节通过EtherChannel 改进冗余及增加带宽...

热门文章

  1. Blue Indian's Puzzler Keygenme
  2. 【织梦总结】织梦常用调用标签总结
  3. oracle通过UTL_SMTP包发送邮件
  4. java css js 合并_java Web程序使用wro4j合并、压缩js、css等静态资源
  5. 想学游戏建模要从哪里开始?列出这些工作量,7年建模师想转行了!
  6. Python编程 | 系统编程 | 脚本运行上下文 | 标准流
  7. vue echarts 地图立体 并设置tooltip属性背景图片
  8. Gartner研究副总裁关于商务智能(BI)的三大趋势分析的讲话(录音翻译)
  9. Java 计算任意两天之间相隔的天数
  10. 大数据早报:搜狐《数字之道》,召唤新势力 十一数据观:钱和人都去哪了?(10.10)