Unix环境高级编程 读书笔记 第五章 标准IO库

FILE结构

C语言将每个文件简单地作为顺序字节流。每个文件用文件结束符结束,或者在特定字节数的地方结束,这个特定的字节数可以存储在系统维护的管理数据结构中。当打开文件时,就建立了和文件的关系。

在开始执行程序的时候,将自动打开3个文件和相关的流:标准输入流、标准输出流和标准错误。流提供了文件和程序的通信通道。例如,标准输入流使得程序可以从键盘读取数据,而标准输出流使得程序可以在屏幕上输出数据。

打开一个文件将返回指向FILE结构(在stdio.h中定义)的指针,它包含用于处理文件的信息,也就是说,这个结构包含文件描述符。文件描述符是操作系统数组(打开文件列表的索引)。每个数组元素包含一个文件控制块(FCB, File Control Block),操作系统用它来管理特定的文件。

C语言的stdio.h头文件中,定义了用于文件操作的结构体FILE。这样,我们通过fopen返回一个文件指针(指向FILE结构体的指针)来进行文件操作。可以在stdio.h(位于visual studio安装目录下的include文件夹下)头文件中查看FILE结构体的定义。

#ifndef _FILE_DEFINED
struct _iobuf {
        char *_ptr;  //文件输入的下一个位置
        int   _cnt;   //当前缓冲区的相对位置
        char *_base;  //文件的起始位置
        int   _flag;  //文件标志
        int   _file;  //文件的有效性验证
        int   _charbuf;//检查缓冲区状况,若无缓冲区则不读取
        int   _bufsiz; //文件的大小
        char *_tmpfname;//临时文件名
        };
typedef struct _iobuf FILE;
#define _FILE_DEFINED
#endif  /* _FILE_DEFINED */

实际上,FILE结构是间接地操作系统的文件控制块(FCB)来实现对文件的操作的。具体的应用程序不需要知道FILE结构的细节。

文件是存放在物理磁盘上的,包括文件控制块(FCB)和数据块。文件控制块通常包括文件权限、日期(创建、读取、修改)、拥有者、文件大小、数据块信息。数据块用来存储实际的内容。

对于打开的文件,系统维护了两张表,一张是系统级打开文件表,一张是进程级打开文件表(每个进程有一个)。

系统级打开文件表复制了文件控制块的信息等,系统级文件表每一项都保存一个计数器,即该文件打开的次数。我们初次打开一个文件时,系统首先查看该文件是否已在系统级文件表中,如果不在,则创建该项信息,否则,计数器加1。当我们关闭一个文件时,相应的计数也会减1,当减到0时,系统将系统级文件表中的项删除。

进程级打开文件表保存了指向系统级文件表的指针及其他信息,进程打开一个文件时,会在进程级文件表中添加一项。每项的信息包括当前文件偏移量(读写文件的位置)、存取权限、和一个指向系统级文件表中对应文件项的指针。系统级文件表中的每一项通过 文件描述符(一个非负整数) 来标识。

FILE结构体中的_file成员应该是指向进程级打开文件表,然后,通过进程级打开文件表可以找到系统级打开文件表,进而可以通过FCB操作物理磁盘上面的文件。FILE中包含fd的信息,而且还包含IO缓冲,所以可以理解为FILE是对fd的封装,是C的标准形式,所以FILE比fd更适合跨平台。

流和FILE对象

对于标准IO库,其操作是围绕流进行的。当使用标准IO库打开或者创建一个文件时,使用一个流与文件进行关联。

流的定向决定了所读写的字符是单字节还是多字节。当一个流被创建时,并没有定向。函数fwide可以用于设置流的定向。

#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode);
/*若流是宽定向,返回正值,若流是字节定向,返回负值,若流是未定向,返回0*/

根据mode参数的不同值,函数fwide执行不同操作:
若mode为负值,fwide试图使指定的流为字节定向。
若mode为正值,fwide试图使指定的流为宽定向。
若mode为0,fwide不设置流的定向,返回标识该流定向的值。
fwide并不改变已经定向的流。

标准输入、标准输出与标准错误

在头文件stdio.h中预定义了文件指针stdin、stdout和stderr,分别表示标准输入、标准输出与标准错误。
注意与文件描述符的区别:STDIN_FILENO、STDOUT_FILENO与STDERR_FILENO。

/* Standard streams.  */
extern struct _IO_FILE *stdin;      /* Standard input stream.  */
extern struct _IO_FILE *stdout;     /* Standard output stream.  */
extern struct _IO_FILE *stderr;     /* Standard error output stream.  */
/* C89/C99 say they're macros.  Make them happy.  */
#define stdin stdin
#define stdout stdout
#define stderr stderr

缓冲

标准IO提供以下3种类型的缓冲:
全缓冲:在全缓冲模式下,填满标准IO缓冲区后才进行实际的IO操作。术语“冲洗”说明标准IO缓冲区的写操作,即将缓冲区的内容写到磁盘上。

行缓冲:当在输入与输出中遇到换行符时,标准IO库才执行IO操作。当流涉及一个终端时,通常使用行缓冲。

不带缓冲:标准IO库不对字符进行缓冲存储。

很多UNIX系统使用如下的缓冲规则:

  1. 标准错误是不带缓冲的;
  2. 若是指向终端设备,则是行缓冲;
  3. 其他的是全缓冲。

更改缓冲的类型,可以使用以下的函数:

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

mode参数的可取值为:

/* The possibilities for the third argument to `setvbuf'.  */
#define _IOFBF 0        /* Fully buffered.  */
#define _IOLBF 1        /* Line buffered.  */
#define _IONBF 2        /* No buffering.  */

这两个函数的选项与动作为:
在这里插入图片描述

一般而言,应该由系统选择缓冲区的长度,并自动分配缓冲区。在这种情况下关闭流时,标准IO库将自动释放缓冲区。

强制冲洗一个流,调用函数:

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

如果fp是NULL,将冲洗所有的输出流。

打开流与关闭流

打开一个标准IO流的函数为:

#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);
/*若成功,返回文件指针,若出错,返回NULL*/

fopen函数打开路径名为pathname的一个指定的文件。
freopen函数在一个指定的流上打开一个指定的文件,若该流已经打开,则先关闭该流,若该流已经定向,则清除该定向。
fdopen取一个已有的文件描述符,使得一个标准IO流与该文件描述符相结合。

type参数指定对IO流的读写方式,具体如下:
在这里插入图片描述
调用函数fclose关闭一个打开的流:

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

在该文件被关闭之前,冲洗缓冲区的输出数据。缓冲区的任何输入数据被丢弃。如果标准IO为流自动分配了缓冲区,则释放该缓冲区。

读和写流

如果打开了流,则对流的读写操作可有3种操作类型:

  1. 每次一个字符的IO,一次读或者写一个字符;
  2. 每次一行的IO,每行都以一个换行符终止;
  3. 直接IO,每次IO操作读写某种数量的对象,每个对象具有指定的长度。

读写流:每次一个字符的IO

以下3个函数可用于一次读取一个字符:

#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
/*若成功,返回下一个字符,若已到达文件尾端或出错,返回EOF*/

函数getchar等同于getc(stdin),即从标准输入读取一个字符。

函数getc与fgetc的区别:getc可以被实现为一个宏,而fgetc不能实现为宏。

以上三个函数在返回下一个字符时,将unsigned char数据类型转换为int数据类型,要求int类型的返回值的理由是,这样可以返回所有可能的字符值,再加上一个已出错或者到达文件尾端的EOF指示值。在stdio.h中EOF被要求是一个负值,通常为-1。

对于以上的3个函数,不管是已经到达文件尾端,还是调用出错,返回值均为EOF,为了区分这两种情况,需调用函数ferror或者feof确认:

#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
/*若条件为真,返回非0,若条件为假,返回0*/

void clearerr(FILE *fp);

在大多数的实现者,为每个流在FILE对象中维护了2个标志:出错标志、文件结束标志。调用clearerr可以清除这2个标志。

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

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

用ungetc压送回字符时,并没有将他们写到底层的文件或者设备中,而是将他们写到标准IO库的流缓冲区中。

对于以上3个读函数,都有对应的写函数,具体如下:

#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不能实现为宏。

读写流:每次一行的IO

下面的2个函数提供每次读取一行IO的功能:

#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个字符。读入的字符被送入buf指向的缓冲区,该缓冲区以null结尾。fgets将换行符也存入缓冲区中。
如果该行包括最后一个换行符超过n-1个字符,则fgets只返回一个不完整的行,缓冲区还是以null结尾。对fgets的下一次调度会继续读取该行字符。

gets函数是一个不推荐使用的函数。

对每次一行的IO的写操作如下:

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

函数fputs将一个以null字节终止的字符串写到指定的流,尾端的null字节不写出。函数puts将一个以null字节终止的字符串写到标准输出,null字节不写出,随后puts将一个换行符写到标准输出。

如果总是使用fgets和fputs,那么就会熟知在每行的终止处,我们必须自己处理换行符。

读写流:二进制IO

如果一次读或者写一个完整的结构,以上介绍的函数均不能满足要求,提供以下函数执行二进制IO操作:

#include <stdio.h>
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);
/*返回读或者写的对象数量*/

例如,读或者写一个结构,可以编写如下程序:

struct{
	short count;
	long total;
	char name[NAMESIZE];
}item;

if(fwrite(&item, sizeof(item), 1, fp) != 1)
	err_sys("fwrite error");

对于fread函数,如果出错,或者到达文件尾端,则返回的数字可能少于nobj,此时应该调用ferror或者feof判断具体是哪一种情况。

对于fwrite函数,如果返回值小于要求的nobj,则出错。

定位流

可以使用函数fgetops与fsetops来定位流。

#include <stdio.h>
int fgetops(FILE *restrict fp, fpos_t *restrict pos);
int fsetops(FILE *fp, const fpos_t *pos);
/*若成功,返回0,若出错,返回非0*/

函数fgetops将文件位置指示器的当前值存入由pos指向的对象中。调用fsetops可以将pos指向的对象中的文件位置值设置到流中。

格式化IO

格式化输出IO函数是由printf系列函数组成:

#include <stdio.h>
int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char *restrict format, ...);
int dprintf(int fd, 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, ...);
/*若缓冲区足够大,返回将要存入数组的字符数,若出错,返回负值*/

函数printf将格式化的数据写到标准输出stdout。
函数fprintf将格式化的数据写至指定的流。
函数dprintf将格式化的数据写至指定的文件描述符。
函数sprintf将格式化的数据写入数组buf中,并在该数组buf的尾端自动添加一个null字节,但是该字节不包括在返回值中。

需要注意,sprintf函数可能会造成buf指向的缓冲区溢出,调用者有责任保证缓冲区足够大。

为了解决缓冲区溢出的问题,函数snprintf将缓冲区长度作为一个显式的参数。超过缓冲区尾端写的所有字符将被丢弃。

参数使用了arg的printf系列函数的变体如下:

#include <stdio.h>
#include <stdarg.h>
int vprintf(const char *restrict format, va_list arg);
int vfprintf(FILE *restrict fp, const char *restrict format, va_list arg);
int vdprintf(int fd, const char *restrict format, va_list arg);
/*若成功,返回输出字符数,若出错,返回负值*/

int vsprintf(char *restrict buf, const char *restrict format, va_list arg);
/*若成功,返回存入数组的字符数,若出错,返回负值*/

int vsnprintf(char *restrict buf, size_t n, const char *restrict format, va_list arg);
/*若缓冲区足够大,返回将要存入数组的字符数,若出错,返回负值*/

格式化输入处理函数是scanf系列函数,如下:

#include <stdio.h>
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, ...);
/*返回赋值的输入项数,若输入出错或者在任一转换前已到达文件尾端,返回EOF*/

对于scanf系列函数也有对应的可变长度参数变体:

#include <stdio.h>
#include <stdarg.h>
int vscanf(const char *restrict format, va_list arg);
int vfscanf(FILE *restrict fp, const char *restrict format, va_list arg);
int vsscanf(const char *restrict buf, const char *restrict format, va_list arg);
/*返回赋值的输入项数,若输入出错或者在任一转换前已到达文件尾端,返回EOF*/

猜你喜欢

转载自blog.csdn.net/jiangzhangha/article/details/85679406