C标准I/O库

  标准I/O库

(带缓冲的,库函数,针对FILE*文件指针,高级输入输出

 

1. 流和FILE对象

 

  标准输入输出库的操作围绕流(stream)进行,标准I/O库打开或创建一个文件时,已使一个流与一个文件相关联。标准I/O库适用于单字节和多(宽)字节字符集。流的定向(stream’s orienta)决定了所读,所写的字符是单字符还是多字符

  当一个流最初创建时它没有定向。若在未定向的流上使用一个多字节I/O函数(<wchar.h>),则将该流的定向设置为宽定向,若是使用单字节I/O函数,则该流的定向设置为单字节定向的。

  freopen函数清除一个流的定向,fwide函数设置流的定向

  #include <stdio.h>

  #include <wchar.h>

  int fwide(FILE *fp, int mode);

    返回值:若流是宽定向的返回正直,若流单字节定向返回负值,若流未定向返回0

  mode:

  负值:fwide试图使指定的流为单字节定向的。

  正值:….宽字节定向的。

  0:   fwide将不试图设置流的定向,但返回标识该流的值。

  注意:fwide不改变已定向流的定向,无出错返回。

 

  FILE对象是一个结构体:包含标准I/O库管理该流所需要的所有信息:用于实际I/O的文件描述符,指向用于该流缓冲区的指针缓冲区长度当前在缓冲区中的字符数以及出错标志等等。

   <stdio.h>中定义了三个文件指针,stdin,stdout, stderr:标准输入,标准输出,标准出错,与文件描述符STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO所引用文件相同。

 

2. 缓冲

 

  目的:尽可能减少调用read和write的次数。也对每个I/O流自动进行缓冲管理,从而避免应用程序需要考虑这一点带来的麻烦。

 

  有三种缓冲类型:

  (1)  全缓冲:在填满标准I/O缓冲区后才进行实际I/O操作。

对于驻留在磁盘上的文件通常由I/O库实施全缓冲。在一个流上第一次I/O操作时,相关标准I/O函数通常调用malloc获得所需要的缓冲区

      冲洗(flush):说明标准I/O缓冲区的写操作。

缓冲区可由标准I/O例程自动冲洗(如缓冲区已满),或由函数fflush冲洗一个流

       flush两种含义:(1)标准I/O库方面,flush(冲洗)意味着将缓冲区的内容写到磁盘上(缓冲区可能未满)。(2)在终端驱动程序方面(如tcflush函数),flush(刷请)表示丢弃已存储在缓冲区的数据

 

       (2)行缓冲:在输入输出遇到换行符时,标准I/O库执行I/O操作。

这允许一次输出一个字符(fputc函数),但只有在写了一行之后才进行实际I/O操作。

       当流涉及到一个终端时(标准输入输出),通常行缓冲

       行缓冲限制:(1)由于标准I/O库每一行缓冲区的长度是固定的,当缓冲区满时,即使没有一个换行符,也进行I/O操作。(2)任何时候只用通过标准I/O库,要求从a:一个不带缓冲的流,或b:一个行缓冲的流(它要求从内核得到数据)得到输入数据,那么就会造成冲洗所有行缓冲输出流。

  b中括号说明的原因是所有的数据可能已在该缓冲区中,它不要求需要数据时才从内核读数据。a中要求当时从内核得到数据。

 

  (3)不带缓冲。标准I/O库不对字符进行缓冲存储。就是缓冲区长度为1

       标准出错流stderr通常不带缓冲,这使出错信息尽快显示出来,而不管它们是否有一个换行符。

 

  ISO C要求:

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

  (2)    标准出错却不会是全缓冲。

  很多系统默认:

  (1)    标准出错不带缓冲。

  (2)    若是涉及到终端设备的其它流,则它们是行缓冲的,否则是全缓冲的。

 

  更改缓冲区类型:

  #include<stdio.h>

  void setbuf(FILE *restrict fp, char *restrict buf);

  int servbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);

                            返回值:成功0,出错非0值

  setbuf函数打开或关闭缓冲机制。

  带缓冲I/O:buf指向一个长度为BUFSIZ(<stdio.h>中)缓冲区。通常之后该流是全缓冲的,但如果该流与一个终端设备相关,某些系统也可将其设置为行缓冲。

  关闭缓冲:buf设为NULL

 

  setvbuf精确指定所需缓冲区类型。

  mode:    _IOFBF              全缓冲

               _IOLBF              行缓冲:

                 _IONBF         不带缓冲

  不带缓冲:忽略buf和size。

  全缓冲或行缓冲:buf和size指定一个缓冲区及其长度。

  流带缓冲:buf=NULL,则标准I/O库将自动的为该流分配适当长度(常量BUFSIZ指定)缓冲区。

 

  强制冲刷一个流:

  #include <stdio.h>

  int fflush(FILE *fp);

              返回值:成功0,出错EOF

  此函数将该流所有未写的数据送至内核。当fp=NULL,导致所有流被冲洗

 

3. 打开流

 

  #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 filedes, const char *type);

               返回值:成功文件指针,出错NULL

  区别:

  (1)    fopen打开一个指定流。

  (2)    freopen在一个指定流上打开一个指定的文件,若该流已经打开,则先关闭该流。若该流已经定向,则freopen清除该定向。

      此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入,标输出,标准出错。

  (3)    fdopen获取一个现有的文件描述符,并使一个标准的I/O流与该描述符相结合。仅有POSIX.1有此函数,ISO C 没有。

      此函数常用于创建管道网络通信通道函数返回的描述符。

 

  调用fclose关闭一个打开的流

  #include <stdio.h>

  int fclose(FILE *fp);

             返回值:成功0,出错EOF

      在文件被关闭之前冲洗缓冲区的输出数据,丢弃缓冲区的任何输入数据。若标准I/O库已经为该流自动分配了一个缓冲区,则释放该缓冲区。

       当进程正常终止时(直接调用exit函数或从main返回),所有带未写缓冲数据的标准I/O流都会被冲洗,所有打开的标准I/O流都会被关闭

 

4. 非格式化I/O

 

4.1. 每次一个字符的I/O

 

  一次读或写一个字符,如果流带缓冲的,则标准I/O函数会处理所有缓冲。

  一次读一个字符

      #include<stdio.h>

      int getc(FILE*fp);

      int fgetc(FILE*fp);

      int getchar(void);

                返回值:成功返回下一个字节,若已达文件结尾或出错返回EOF

  getchar() = getc(stdin);

  getc()可被实现为宏,参数不应当是具有副作用的表达式。

  fgetc不能为宏,一定是一个函数,可以得到其地址。

  调用fgetc时间可能长于调用getc,函数调用时间通常长于宏。

  EOF是一个负值,通常为-1,因此返回值类型为int,不能为无符号。

 

      由于出错或到达文件结尾都返回EOF,可以调用ferror和feof加以区分:

      #include <stdio.h>

  int ferror(FILE *fp);

      int feof(FILE *fp);

                     返回值:条件为真非0值,否则0

 

      void clearerr(*fp);

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

  (1)出错标志

      (2)文件结束标志

      clearerr清除这两个标志

 

      ungetc将字符再压送回流中

      #include <stdio.h>

      int ungetc(int c, FILE *fp);

                     返回值:成功c,出错EOF

 

      一次输出一个字符

      #include <stdio.h>

      int putc(int c, FILE *fp);

      int fputc(int c, FILE *fp);

      int putchar(int c);

              返回值:成功c,出错EOF

      putchar(c)=putc(c,stdout),putc可为宏fputc为函数

 

4.2. 每次一行的输入输出

 

  每次读取一行

      #include <stdio.h>

      char *fgets(char *restrict buf, int n, FILE*fp);

      char *gets(char *buf);

              返回值:成功返回buf,已达文件结尾出错返回NULL

 

      fgets():必须指定缓冲区长度n,一直读到下一个换行符为止,但不超过n-1个字符

  若该行(包括最后一个换行符)字符数超过n-1,只返回一个不完整的行,但是缓冲区总是以null结尾,对fgets的下一次调用会继续读该行。

  特点:(1)读取换行符到缓冲区。(2)缓冲区结尾总是自动添加null字符。

 

  gets():不推荐使用函数,它不能指定缓冲区长度,可能造成缓冲区溢出,写到缓冲区之后的空间,造成不可预料的结果。并且不将换行符存入缓冲区。所以应该使用fgets()函数。

 

  每次输出一行

      #include <stdio.h>

      int fputs(const char *str, FILE *fp);

      int puts(const char *str);

                     返回值:成功非负值,出错EOF

      fputs():将一个null终止的字符串写到指定的流,尾端null不写出不一定要每次输出一行,因为不要求null之前一定是换行符。

      puts():将一个null终止的字符串写到标准输出,null不写出。然后再写一个换行符到标准输出。

  puts不像gets那样不安全,但我们应该避免使用,以免需要记住它在最后是否添加了一个换行符。应总是使用fgets和fputs,因为我们总是自己要处理换行符

 

5. 二进制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”);
  }

  ptr:&data[2]起始读取数据的地址

  size:sizeof(float)以多大为一个单位

  nobj:4要写的个数

  FILE*:fp要写的位置

 

  (3)    读或写一个结构。

  struct {

    short count;

    long total;

    char name[NAMESIZE];
  } item;

   if (fwrite(&item, sizeof(item), 1, fp) != 1){

    err_sys(“fwrite error”);
   }

      使用二进制I/O的基本问题:它只能用于读同一个系统已写的数据。由于同一结构中同一成员在不同机器上偏移量不同,机器大小端问题。

 

6. 定位流

 

  三种方法定位标准I/O流:

  (1)ftell和fseek:都假定文件的偏移量可以存放在一个长整型中。

       #include <stdio.h>

       long ftell(FILE *fp);

                     返回值:成功返回当前文件位置指示,出错-1L

       int fseek(FILE *fp, long offset, int whence);

                     返回值:若成功0,出错非0

       void rewind(FILE *fp);

                     返回值:成功0,出错非0

 

  (2) ftello和fseeko函数:SUS引入的,可以是文件偏移量不必是长整型

               用off_t(可长于32位)代替长整型

 

  除了offset的类型是off_t而非long,ftello与ftell相同,fseeko与fseek相同。

  #include <stdio.h>

  off_t  ftello(FILE*fp);

                     返回值:成功返回当前文件位置指示,出错返回-1

  int fseeko(FILE *fp, off_t offset, int whence);

                     返回值:成功0,出错非0

  (3)fgetpos和fsetpos函数:ISO C引入,使用抽象数据类型fpos_t记录文件位置。这种数据类型可以定义为记录一个文件位置所需的长度。

       #include <stdio.h>

       int fgetpos(FILE *fp, fops_t *pos);

   int fsetpos(FILE *fp, const fops_t *pos);

                     返回值:成功0,出错非0

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

 

7. 格式化I/O

 

7.1. 格式化输出

 

  #include <stdio.h>

  int printf(const char *format, …);

  int fprintf(FILE *fp, const char *format, …);

              返回值:成功返回输出字符数,出错返回负值

  int sprintf(char *buf, const char *restrict format, …);

  int snprintf(char *buf, size_t n, const char format, …);

                     返回值:成功返回存入数组中字符数,出错返回负值

  sprintf和snprintf都会在数组末尾自动添加一个null字节,该字节不包括返回值中。

      sprintf可能会造成buf溢出。应使用snprintf超过缓冲区尾端写的字符会被丢弃,若缓冲区足够大snprint返回写入缓冲区的字符数。若返回值小于n,没有截短。若发生一个编码错误,返回负值。

 

  4个sprintf变体,用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);

  int vsnprintf(char *buf, size_t n, const char *format, va_list arg);

              返回值:成功返回存入数组字符数,编码出错返回负值

 

7.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

 

       三个变体

       #include <stdarg.h>

       #include <stdio.h>

       int vscanf(const char *format, va_list arg);

       int vfscanf(FILE *fp, const char *fomat, va_list arg);

       int vsscanf(const char *buf,const char *format, va_list arg);

         返回值:指定的输入项数,若输入出错或在任一变换前已达到文件结尾则返回EOF

 

8. 实现细节

 

  在UNIX中标准I/O库最终都要调用系统调用的I/O例程文件I/O中read,write等)。

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

       #include <stdio.h>

        int fileno(FILE *fp);

              返回值:与该流相关联的文件描述符

       函数不是ISO C标准部分,是POSIX.1支持的扩展。

 

9. 临时文件

 

  ISO C标准I/O库:

  #include <stdio.h>

  char *tmpnam(char *ptr);

          返回值:指向唯一路径名的指针。

  FILE *tmpfile(void);

          返回值:成功返回文件指针,出错返回NULL

 

 

 

 

posted on 2014-04-16 13:01  hancmhi  阅读(397)  评论(0编辑  收藏  举报