AUPE学习笔记之标准I/O

前面部分,我们介绍的所有I/O函数都是围绕文件描述符的,当打开一个文件时,即返回一个文件描述符,然后该文件描述符就用于后续的I/O操作。

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

  1. 标准输入、标准输出和标准错误
    这3个标准I/O流通过预定义文件指针stdin, stdout, stderr加以引用。这3个文件指针定义在头文件<stdio.h>中。
  2. 缓冲
    (1)标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数。它对每个I/O流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。
    (2)标准I/O提供了以下3中类型的缓冲:
    1)全缓冲。在这种情况下,在填满I/O缓冲区后才进行实际I/O操作。对于主流在磁盘上的文件通常是由标准I/O库实施全缓冲的。在一个流上执行第一次I/O操作是,相关标准I/O函数通常会调用malloc获得使用的缓冲区。
    2)行缓冲。在这种情况下,当输入和输出中遇到换行符时,标准I/O库执行I/O操作。这允许我们一次输出一个字符(用标准I/O函数fputc),但只有在写了一行之后才进行实际I/O操作。当流涉及一个终端时(如标准输入和标准输出),通常使用行缓冲。
    3)不带缓冲。标准I/O库不对字符进行缓冲存储。例如,若用标准I/O函数fputs写15个字符到不带缓冲的流中,我们就期望这15个字符能立即输出。
    I/O缓冲的一些惯例:标准错误是不带缓冲的;打开至终端设备的流是行缓冲的,其他流是全缓冲的。
    (3)对任何一个给定的流,可调用下列两个函数中的一个更改缓冲类型:

    1)可以使用setbuf函数打开或关闭缓冲机制。
    2)使用setvbuf,我们可以精确地说明所需的缓冲类型,这是用mode参数实现的:_IOFBF(全缓冲)、_IOLBF(行缓冲)、_IONBF(不带缓冲)。
    (4)任何时候,我们都可强制冲洗一个流:

    此函数使该流所有未写的数据都被传送至内核。作为一种特殊情形,若fp是NULL,则此函数将导致所有输出流被冲洗。
  3. 打开流
    (1)下列3个函数打开一个标准I/O流:

    3个函数有如下区别:
    1)fopen函数打开路径名为pathname的一个指定的文件
    2)freopen函数在一个指定的流上打开一个指定的文件,若该文件已经打开,则先关闭该流。若该流已经定向,则使用freopen清楚该定向。
    3)fdopen函数取一个已有的文件描述符,并使一个标准的I/O流与该描述符相结合。
    (2)调用fclose关闭一个打开的流
  4. 读和写流
    (1)一旦打开了流,则可在3种不同类型的非格式化I/O中进行选择,对其进行读、写操作:
    1)每次一个字符的I/O。
    2)每次一行的I/O。如果想要一次读或写一行,则使用fgets和fputs。每行都以一个换行符终止。
    3)直接I/O。fread和fwrite函数支持这种类型的I/O
    (2)输入函数
    一下3个函数可用于一次读一个字符:

    getchar等同于getc(stdin)。前两个函数的区别是:getc可被实现宏,而fgetc不能实现为宏。这意味着以下几点:
    1)getc的参数不应当是具有副作用的表达式,因为它可能会被计算多次。
    2)因为fgetc一定是个函数,所有可以得到其地址。这就允许将fgetc的地址作为一个参数传递给另一个函数。
    3)调用fgetc所需时间很可能比调用getc要长,因为调用函数所需的时间通常长于调用宏。
    (3)输出函数
    对应于上面的输入函数都有一个输出函数:

    1)putchcar(c)等同于putc(stdout), putc可被实现为宏,而fputc不能实现为宏。
  5. 每次一行I/O
    (1)下面两个函数提供每次输入一行的功能:

    1)两个函数都指定了缓冲区的地址,读入的行将送入其中。gets从标准输入读,而fgets则从指定的流读。
    2)对于fgets,必须指定缓冲的长度n。
    3)gets是一个不推荐使用的函数。其问题是调用者在使用gets时不能指定缓冲区的长度。这样可能造成缓冲区溢出(如若该行长于缓冲区长度),写到缓冲区之后的存储空间中,从而产生不会预料的后果
    (2)fputs和puts提供每次输出一行的功能:

    1)函数fputs将一个以null字节终止的字符串写到指定的流,尾端的终止符null不写出。
    2)puts将一个以null字节终止的字符串写到标准输出,终止符不写出。但是,puts随后又将一个换行符写到标准输出。
    3)puts并不像它所对应的gets那样不安全。但是我们还是应避免使用它。
  6. 二进制I/O
    如果二进制I/O操作,那么我们更愿意一次读写一个完整的结构。因此,提供了下列两个函数以执行二进制I/O操作。

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

    (2)读或写一个结构。例如:

    fread和fwrite返回读或写的对象数。对于读,如果出错或达到文件尾端,则此数字可以少于nobj。这种情况,应调用ferror或feof以判断究竟是哪一种情况;对于写,如果返回值少于所要求的nobj,则出错。
  7. 定位流
    有3种方法定位标准I/O流
    (1)ftell和fseek函数。这两个函数自V7以来就存在了,但它们都假定文件的位置可以存放在一个长整型中。

    1)对于一个二进制文件,其文件位置指示器是从文件起始位置开始度量,并以字节为度量单位的。ftell用于二进制文件时,其返回值就是这种字节位置。
    2)fseek定位一个二进制文件,必须指定一个字节offset,以及解释这种偏移的方式。whence的值与前面介绍的lseek函数的相同。
    3)rewind函数也可将一个流设置到文件的起始位置。
    (2)除了偏移量的类型是off_set而非long以外,ftello函数与ftell相同,fessko函数与fseek相同。

    (3)fgetpos和fsetpos两个函数是ISO C标准引入的:

    fgetpos将文件位置指示器的当前值存入由pos指向的对象中。在以后调用fsetpos时,可以使用此值将流重新定位至该位置。
  8. 格式化I/O
    (1)格式化输出,格式化输出是由5个printf函数来处理的:

    1)printf将格式化数据写到标准输出,fprintf写至指定的流,dprintf写至指定的文件描述符,sprintf将格式化的字符送入数组buf中。sprintf在该数组的尾端自动加一个null字节,但该字符不包括在返回值中。
    2)sprintf函数可能会造成由buf指向的缓冲区的溢出。为了解决这种缓冲区溢出问题,引入了snprintf函数。该函数中,缓冲区是一个显示参数,超过缓冲区尾端写的所有字符都被丢弃。
    (2)格式化输入,3个scanf函数:

    scanf族用于分析输入字符串,并将字符序列转换成指定类型的变量。在格式之后的各参数包含了变量的地址,用转换结果对这些变量赋值。

发表评论

电子邮件地址不会被公开。 必填项已用*标注