UNIX环境高级编程(五)标准IO库

本文详细介绍了标准IO流的概念,包括流和FILE对象的关系、标准IO的缓冲机制、标准输入输出和标准错误处理等内容。此外还讲解了如何进行读写操作,并提供了具体的函数介绍。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

5.2流和FILE对象

对于标准IO库,它们的操作是围绕流(stream)进行的。当用标准IO库打开或者创建一个文件时,我们已经使一个流与一个文件箱关联

当打开一个流时,标准IO函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了标准IO库管理该流所需要的所有信息,包括:用于实际IO的文件描述符,指向用于该流缓冲区的指针,缓冲区长度,当前在缓冲区的字符数以及出错标志等等。

 

5.3标准输入,标准输出和标准出错

这三个IO经过预定义文件指针stdin,stdout和stderr加以引用。

 

5.4缓冲

标准IO库提供缓冲的目的是减少使用read和write调用的次数。它也对每个IO流自动地进行缓冲管理。

标准IO提供了三种类型的缓冲:
(1)全缓冲。这种情况下,在填满标准IO缓冲区后才进行实际的IO操作。

术语冲洗(flush)说明标准IO缓冲区的写操作。缓冲区可由标准IO例程自动冲洗(例如当填满一个缓冲区时),或者可以调用函数fflush冲洗一个流。

(2)行缓冲。当在输入或输出中遇到换行符时,标准IO库执行IO操作。

当流涉及一个终端时,通常使用行缓冲。

对于行缓冲有两个限制。第一,因为标准IO库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行IO操作。第二,任何时候只要通过标准IO库要求从(a)一个不带缓冲的流,或者(b)一个行缓冲的流(它要求从内核得数据)得到输入数据,那么就会造成冲洗所有行缓冲输出流。在(b)中带了一个在括号中的说明,其理由是,所需的数据可能已经在缓冲区中,它并不要求在需要数据时才从内核读数据。

(3)不带缓冲。

标准出错流stderr通常是不带缓冲的。

ISO C 要求下列缓冲特征:

当且仅当标准输入和标准输出不涉及交互设备时,它们才是全缓冲的。

标准出错决不是全缓冲的

 

系统默认:

标准出错是不带缓冲的

如若是涉及终端设备的其他流,则它们是行缓冲的;否则是全缓冲的。

 

对于一个给定的流,如若我们不喜欢这些系统默认情况,则可以调用下面两个函数更改缓冲类型:

  1: #include<stdio.h>
  2: 
  3: void setbuf(FILE *fp,char *buf);
  4: 
  5: int setvbuf(FILE *fp,char *buf,int mode,size_t size);
  6: 
  7: //返回值:若成功则返回0,若出错则返回非0值

这些函数一定要在流已经被打开后调用。

使用setvbuf,我们可以精确地指定所需缓冲的类型。这是用mode参数实现的:

                             _IOFBF   全缓冲

                             _IOLBF   行缓冲

                             _IONBF    不带缓冲

如果指定一个不带缓冲的流,则忽略buf和size参数。如果指定全缓冲或行缓冲,则buf和size指定一个缓冲区及其长度。如果该流是带缓冲的,而buf是NULL,则标准IO自动的为该流分配适当长度的缓冲区。

 

下图:

要了解,如果在一个函数内分配一个自动变量类的标准I/O缓冲区,则从该函数返回之前,必须关闭该流。另外,有些实现将缓冲区的一部分用于存放它自己的管理操作信息,所以可存放在缓冲区的实际数据字节数少于size。

任何时候,我们可以强制冲刷一个流。

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

5.6读和写流

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

(1)每次一个字符IO

(2)每次一行的IO

(3)直接IO 。有时被称作二进制IO,一次一个对象IO,面向记录的IO或者面向结构的IO。

1.输入函数

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

  1: #include<stdio.h>
  2: int getc(FILE *fp);
  3: int fgetc(FILE *fp);
  4: int getchar(void);    //三个函数返回值:若成功则返回下一个字符,若已经达到文件尾或出错返回EOF
  5: 

函数getchar等价于getc(stdin)。前两个函数的区别是getc可被实现为宏,而fgetc不能实现为宏。这意味着:
(1)getc的参数不应当是具有副作用的表达式

(2)因为fgetc一定是一个函数,所以可以取到其地址。这就允许将fgetc的地址作为一个参数传递给另一个函数。

(3)调用fgetc时间可能长于调用getc,因为调用函数通常所需时间长于调用宏。

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

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

大多数的实现中,为每个流在FILE对象中维持了两个标志:

出错标志

文件结束标志

调用clearerr则清除这两个标志

2.输出函数

对于上述的每个输入函数都有一个输出函数

  1: #include<stdio.h>
  2: int putc(int c,FILE *fp);
  3: int fputc(int c,FILE *fp);
  4: int putchar(int c);    //若成功返回c,出错返回EOF

5.7 每次一行IO

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

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

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

gets与fgets的另一个区别是gets并不将换行符送入缓冲区中,而fgets则保持换行符

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

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

函数fputs将一个以NULL符终止的字符串写到指定的流,尾端的NULL不写出

5.9二进制IO

提供了下面函数执行二进制IO操作

  1: #include<stdio.h>
  2: size_t fread(void *ptr,size_t size,size_t nobj,FILE *fp);
  3: size_t fwrite(const void *ptr,size_t size,size_t nobj,FILE *fp);
  4: //两个函数返回值:读或写的对象数

这些函数有两种常见的用法:
(1)读或写一个二进制数组。例如,为了将一个浮点数组的2——5个元素写到一个文件上,可以编写如下程序

  1: float data[10];
  2: if(fwrite(&data[2],sizeof(float),4,fp)!=4)
  3: //其中,指定size为每个数组元素的长度,nobj为欲写的元素数。
  4: 

(2)读或写一个结构。例如,可以编写如下程序:

  1: struct{
  2: short count;
  3: long total;
  4: char name[NAMESIZE];
  5: }item;
  6: if(fwrite(&item,sizeof(item),1,fp)!=1)

fread和fwrite返回读或写的对象数。对于读,如果出错或达到文件尾端,则此数字可以少于nobj。

5.12实现细节

每一个标准IO流都有一个与其相关联的文件描述符,可以对一个流调用fileno函数以获得其文件描述符。

  1: #include<stdio.h>
  2: int fileno(FILE *fp);
  3: //返回值:与流相关联的文件描述符
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值