UNIX环境高级编程-第五章

1.引言:本章主要讲述标准I/O库
2.流和FILE对象

在第3章中,所有I/O函数都是围绕文件描述符的。当打开一个文件时,即返回一个文件描述符,然后该文件描述符就用于后续的IO操作。而对于标准IO库,它们的操作是围绕流(stream)进行的。当用标准IO库打开或创建一个文件时,我们已使一个流与一个文件相关联。
对于ASCII字符集,一个字符用一个字节表示。对于国际字符集,一个字符可用多个字节表示。标准IO文件流可用于单字节或多字节字符集。流的定向决定了所读,所写的字符是单字节还是多字节。当一个流最初被创建时,它并没有定向。如若在未定向的的流上使用一个多字节IO函数,则将该流的定向设置位宽定向的。如若在未定向的流上使用一个单字节IO函数,则将该流的定向设为字节定向的。只有两个函数可改变流的定向。freopen函数清除一个流的定向;fwide函数可用于设置流的定向。

#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp,int mode);

根据mode参数的不同值,fwide函数执行不同的工作。

(1)如若mode参数值为负,fwide将试图使指定的流是字节定向的。
(2)如若mode参数值为正,fwide将试图使指定的流是宽定向的。
(3)如若mode参数值是0,fwide将不试图设置流的定向,但返回标识该流定向的值。

注意,fwide并不改变已定向流的定向。还应注意的是,fwide无出错返回。试想,如若流是无效的,那么将发生什么呢?我们唯一可以依靠的是,在调用fwide前清除errno,从fwide返回时检查errno的值。在本书的其余部分,我们只涉及字节定向流。
当打开一个流时,标准IO函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了标准IO库为管理该流需要的所有信息,包括用于实际IO的文件描述符,指向用于该流缓冲区的指针,缓冲区的长度,当前在缓冲区中的字符数以及出错标志等。
应用程序没有必要检验FILE对象。为了引用一个流,需将FILE指针作为参数传递给每一个标准IO函数。在本书中,我们称指向FILE对象的指针为文件指针。
在本章中,我们UNIX系统环境中说明标准标准IO库,正如前述,此标准库已移植到UNIX之外的很多系统中,但是为了说明该库实现的一些细节,我们将讨论其在UNIX系统上的典型实现。

3.标准输入,标准输出和标准错误

对一个进程预定义了3个流,并且这3个流可以自动被进程使用,它们是:标准输入,标准输出和标准错误。这些流引用的文件与在3.2节中提到文件描述符STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO所引用的相同。
这3个标准IO流通过预定义文件指针stdin,stdout和stderr加以引用。这3个文件指针定义在头文件<stdio.h>

4.缓冲

标准IO库提供缓冲的目的是尽可能减少使用read和write调用的次数。它也对每个IO流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。遗憾的是,标准IO库最令人迷惑的也是它的缓冲。
标准IO提供了以下3种类型的缓冲。

(1)全缓冲。在这种情况下,在填满标注IO缓冲区后才进行实际IO操作。对于驻留在磁盘上的文件通常是由标准IO库实施全缓冲的。在一个流上执行第一次IO操作时,相关标准IO函数通常调用malloc获得需要使用的缓冲区。
术语冲洗(flush)说明标准IO缓冲区的写操作。缓冲区可在标准IO例程自动地冲洗,或者可以调用函数fflush冲洗一个流。值得注意的是,在UNIX环境中,flush有两种意思。在标准IO库方面,flush(冲洗)意味着将缓冲区中的内容写到磁盘上。在终端驱动程序方面,flush标识丢弃已存储在缓冲区中的数据。
(2)行缓冲。在这种情况下,当在输入和输出中遇到换行符时,标准IO库执行IO操作。这允许我们一次输出一个字符(用标准IO函数fputc),但只有在写了一行之后才进行实际IO操作。当流涉及一个终端时,通常使用行缓冲。
对于行缓冲有两个限制。第一,因为标准IO库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行IO操作。第二,任何时候只要通过标准IO库要求从(a)一个不带缓冲的流,或者(b)一个行缓冲的流得到输入数据,那么就会冲洗所有行缓冲输出流。在(b)中带了一个在括号中的说明,其理由是,所需要的数据可能已在该缓冲区中,它并不要求一定从内核读数据。很明显,从一个不带缓冲的流中输入需要从内核获得数据。
(3)不带缓冲。标准IO库不对字符进行缓冲存储。例如,若用标准IO函数fputs写15个字符到不带缓冲的流中,我们就期望这15个字符能立即输出,很可能使用write函数将这些字符写道相关联的打开文件中。
标准错误流stderr通常是不带缓冲的,这就使得出错信息可以尽快显现出来,而不管它们是否含有一个换行符。

很多系统默认:标准错误是不带缓冲的,若是指向终端设备的流,则是行缓冲的;否则是全缓冲的。
对于任何一个给定的流,如果我们并不喜欢这些系统默认,则可以调用下列两个函数中的一个更改缓冲类型。

#include<stdio.h>
void setbuf(FILE *restrict fp,char *restrict buf);
int setvbuf(FILE *restrict fp,char *restrict buf,int mode,size_t size);

这些函数一定要在流已被打开后调用,而且也应在对该流执行任何一个其他操作之前调用。
可以使用setbuf函数打开或关闭缓冲机制。为了带缓冲进行IO,参数buf必须指向一个长度为BUFSIZE的缓冲区(该常量定义在<stdio.h>中)。通常在此之后该流就是全缓冲的,但是如果该流与一个终端设备相关,那么某些系统也可将其设置为行缓冲的。为了关闭缓冲将buf设置为NULL。
使用setvbuf,我们可以精确地说明所需的缓冲类型。这是用mode参数实现的:

_IOFBF 全缓冲
_IOLBF 行缓冲
_IONBF 不带缓冲

如果指定一个不带缓冲的流,则忽略buf和size参数。如果指定全缓冲或行缓冲,则buf和size可选择地指定一个缓冲区及其长度。如果该流是带缓冲的,而buf是NULL,则标准IO库将自动地为该流分配适当长度的缓冲区。适当长度指的是由常量BUFSIZE所指定的值。
图5-1列出了这两个函数的动作,以及它们的选项。
在这里插入图片描述

要了解,如果在一个函数内分配一个自动变量类的标准IO缓冲区,则从该函数返回之前,必须关闭该流。另外,其些实现将缓冲区的一部分用于存放它自己的管理操作信息,所以可以存放在缓冲区中的实际数据字节数少于size。一般而言,应由系统选择释放缓冲区的长度,并自动分配缓冲区。在这种情况下关闭此流时,标准IO库将自动释放缓冲区。
任何时候,我们都可以强制冲洗一个流。

#include <stdio.h>
int fflush(FILE *fp);

此函数使该流所有未写的数据都被传送至内核。作为一种特殊情形,如若fp使NULL,则此函数将导致所有输出流被冲洗。

5.打开流

下列3个函数打开一个标准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);

这3个函数的区别如下:

(1)fopen函数打开路径名为pathname的一个指定的文件。
(2)freopen函数在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。若该流已经定向,则使用freopen清除该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入,标准输出或标准错误。
(3)fdopen函数取一个已有的文件描述符(我们可能从open,dup,dup2,fcntl,pipe,socket,socketpair或accept函数得到此文件描述符),并使一个标准的IO流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数返回的描述符。因为这些特殊类型的文件不能用标准IO函数fopen打开,所以我们必须先调用设备专用函数以获得一个文件描述符,然后用fdopen使一个标准IO流与该描述符相结合。

type参数指定对该IO流的读写方式,ISO C规定type参数可以有15种不同的值,如图5-2所示。
在这里插入图片描述

使用字符b作为type的一部分,这使得标准IO系统可以区分文本文件和二进制文件。因为UNIX内核并不对这两种文件进行区分,所以在UNIX系统环境下指定字符b作为type的一部分实际上并无作用。
对于fdopen,type参数的意义稍有区别。因为该描述符已被打开,所以fdopen为写而打开并不截断该文件。另外,标准IO追加写方式也不能用于创建该文件(因为如果一个描述符引用一个文件,则该文件一定已经存在)。
当用追加写类型打开一个文件后,每次写都将数据写到文件的当前尾端处。如果有多个进程用标准IO追加写方式打开同一文件,那么来自每个进程的数据都将正确地写到文件中。
当以读和写类型打开一个文件时(type中+号),具有下列限制。

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

对应于图5-2,图5-3中列出了打开一个流的6种不同方式。
在这里插入图片描述

注意在指定w或a类型创建一个新文件时,我们无法说明该文件的访问权限位。
除非流引用终端设备,否则按系统默认,流被打开时是全缓冲的。若流引用终端设备,则该流是行缓冲的。一旦打开了流,那么在对该流执行任何操作之前,如果希望,则可使用前节所述的setbuf和setvbuf改变缓冲的类型。
调用fclose关闭一个打开的流。

#include <stdio.h>
int fclose(FILE *fp);

在该文件被关闭之前,冲洗缓冲区种的输出数据。缓冲区中的任何输入数据被丢弃。如果标准IO库已经为该流自动分配了一个缓冲区,则释放此缓冲区。
当一个进程正常终止时,则所有带未写缓冲数据的标准IO流都被冲洗,所有打开的标准IO流都被关闭。

6.读和写流

一旦打开了流,则可在3种不同类型的非格式化IO种进行选择,对其进行读,写操作。

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

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

int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);

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

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

这3个函数在返回下一个字符时,将其unsigned char类型转换为int类型。说明为无符号的理由是,如果最高位为1也不会使返回值为负。要求整型返回值的理由是,如果最高位为1也不会使返回值为负。要求整型返回值的理由是,这就可以返回所有可能的字符值再加上一个已出错或已到达文件尾端的指示值。在<stdio.h>中的常量EOF被要求是一个负值,其值通常是-1.这就意味着不能将3个函数的返回值存放在一个字符变量中,以后还要将这些函数的返回值与常量EOF比较。
注意不管是出错还是到达文件尾端,这3个函数都返回同样的值。为了区分这两种不同的情况,必须调用ferror或feof

#include<stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
void clearerr(FILE *fp);

在大多数实现中,为每个流在FILE对象中维护了两个标志:出错标志和文件结束标志
调用clearerr可以清除这两个标志。
从流中读取数据以后,可以调用ungetc将字符再压送回流中。

#include<stdio.h>
int ungetc(int c,FILE *fp);

压送回到流中的字符以后又可以从流中读出,但读出字符的顺序与压送回的顺序相反。应当了解,虽然ISO C允许实现支持任何次数的回送,但是它要求实现提供一次只回送一个字符。我们不能期望一次能回送多个字符。
回送的字符,不一定是上一次读到的字符。不能回送EOF。但是当已经到达文件尾端时,仍可以回送一个字符。下次读将返回该字符,再读则返回EOF。之所以能这样做的原因是,一次成功的ungetc调用会清除该流的文件结束标志。
当正在读一个输入流,并进行某种形式的切词或记号切分操作时,会经常用到回送字符操作。有时需要先看一看下一个字符,以决定如何处理当前字符。然后就需要方便地将刚查看的字符回送,以便下一次调用getc时返回该字符。如果标准IO库不提供回送能力,就需要将该字符存放到一个我们自己的变量中,并设置一个标志一判别在下一次需要一个字符时调用getc,还是从我们自己的变量中取出这个字符。

输出函数:

#include <stdio.h>
int 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值