# 1.1 标准 IO stdio

stdio.h 是 C 语言的标准 I/O 库,用于读取和写入文件,也用于控制台的输入输出。

标准 I/O 是 C 库中提供的文件 IO 接口。使用它可以方便实现不同系统的移植。

标准I/O库及其头文件 stdio.h 为底层I/O系统调用提供了一个通用的接口。

# 1.1.1 流和 FILE *

/usr/include/stdio.h 中有流的定义:

/* 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
1
2
3
4
5
6
7
8

标准 I/O 预定义了三个流,自动地为进程所用。

  • stdin 标准输入流,一般指键盘,行缓冲
  • stdout 标准输出流,一般指终端屏幕,行缓冲
  • stderr 标准错误流,一般指终端屏幕,不缓冲

标准 I/O 库的所有操作都是围绕流来进行的,标准 IO 中,流用 FILE * 描述。

FILE 类型实际是 struct _IO_FILE 类型的别名,在 /usr/include/x86_64-linux-gnu/bits/types/FILE.h 文件中声明

   typedef struct _IO_FILE FILE;

   struct _IO_FILE 类型在 /usr/include/x86_64-linux-gnu/bits/libio.h 文件中定义:

struct _IO_FILE {
  int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

struct _IO_FILE_complete
{
  struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
  _IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
# else
  void *__pad1;
  void *__pad2;
  void *__pad3;
  void *__pad4;
# endif
  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 1.1.2 缓冲分类

缓冲类型

(1)全缓冲,当填满 I/O 缓冲后才进行实际 I/O 操作,默认。

(2)行缓冲,如终端,遇换行符刷新。

(3)不缓冲,如标准错误。

修改缓冲区

void setbuf(FILE *stream, char *buf);                  // 将流 stream 的缓冲区设置为 buf,大小为 BUFSIZ

void setbuffer(FILE *stream, char *buf, size_t size);  // 将流 stream 的缓冲区设置为 buf,大小为 size

void setlinebuf(FILE *stream);                         // 将流 stream 设置为行缓冲

int setvbuf(FILE *stream, char *buf, int mode, size_t size);   // 按 mode 设置 stream 流的缓冲区。

// mode 可取值:

// _IONBF unbuffered      不缓冲

// _IOLBF line buffered   行缓冲

// _IOFBF fully buffered  全缓冲
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

在任何时刻,可以使用 fflush() 强制刷新一个数据流。

Linux 最佳缓冲区是 8192B

# 1.1.3 标准 IO 接口

(1)打开流

// 刷新一个流,此函数使该流所有未写的数据都被传递至内核。
int fflush(FILE *fp);

// 打开标准 I/O 流
FILE *fopen(const char *pathname, const char *mode);
// mode 可以为:r(b),w(b),a(b),当给定“b”参数时,表示以二进制方式打开文件。。
// r+ ,w+,a+ 可读写,r+ 不可创建。
// 打开失败返回 NULL,并将 errno 置为恰当的值,创建的文件权限为 0666

// 关闭一个 I/O 流
int fclose(FILE *stream);
// 成功返回 0,失败返回 EOF ,并置 errno
// 关闭前刷新缓冲
1
2
3
4
5
6
7
8
9
10
11
12
13

(2)读写流

为了区分是错误还是到达文件尾端,需要使用 ferror()feof() 判断。

在大多数的FILE对象的实现中,保留两个标志:

• 出错标志。 • 文件结束标志。

void clearerr(FILE *stream);       // 清除错误或结束标志
int feof(FILE *stream);            // 判断流是否结束
int ferror(FILE *stream);          // 判断流是否出错
1
2
3

非格式化 I/O:

// 每次一个字符的 I/O
int fgetc(FILE *stream);           // 读取一个字符
int getc(FILE *stream);            // 读取一个字符
int getchar(void);                 // 从标准输入流读取一个字符
int fputc(int c, FILE *stream);    // 输出一个字符
int putc(int c, FILE *stream);     // 输出一个字符
int putchar(int c);                // 输出一个字符到标准输出流

// 每次一行的 I/O
char *gets(char *s);        // 不推荐使用
char *fgets(char *s, int size, FILE *stream)
//成功返回 buf,失败返回 NULL,buf 终止符为 NULL,每次读取不超过 n-1.
int puts(const char *s);
int fputs(const char *s, FILE *stream);
// 成功为非负值,出错为 EOF,将一个以NULL终止的字符串写到指定流,NULL 不写出。

//fgetc读取二进制数据,效率底,fputs() 遇到 NULL就会停止,输入数据有 NULL 或 换行符,

//fgets() 不能正确工作。

// 二进制 I/O
// fread 不区分  EOF 和 error,可用 ferror 判断。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
// 返回值:读或写的对象数
//比如,要读取 100 个整数,正确的方式是:
fread(buf, sizeof (int), 100, fp);


//写入不成功  返回 0
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);   // 写数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

二进制 I/O 的使用实例:

/******************************************************************************
 20  *     Copyright:  (C) 2023 shachi
 19  *                 All rights reserved.
 18  *
 17  *      Filename:  ./playground/tmp.c
 16  *   Description:  This file
 15  *
 14  *       Version:  0.1.0
 13  *        Author:  shachi <shachi1758@outlook.com>
 12  *     ChangeLog:  1. 2023-03-15 19:07:39 创建文件
 11  *
 10  *****************************************************************************/

 #include <stdlib.h>
 #include <stdbool.h>
 #include <inttypes.h>
 #include <stdio.h>

 struct st{
     int a;
     int b;
     int c;
 };

 int main(int argc, const char* argv[])
 {
     FILE *fp = fopen("./tmp.txt","w+b");
     if (!fp) {
         perror("打开文件失败!\n");
         exit(EXIT_FAILURE);
    }

     fpos_t st_pos;
     fgetpos(fp,&st_pos);

     struct st sts[2] = {{7,9,3},{4,5,6}};
     int ret = fwrite(&sts,sizeof(struct st),2,fp);
     if(ret == 0) {
         if(feof(fp)) {
             printf("write\n");
         } else {
             printf("写入失败!\n");

              exit(EXIT_FAILURE);
          }

      }

      clearerr(fp);
      fsetpos(fp,&st_pos);
      //rewind(fp);

      struct st new[2];
      int ret2 = fread(&new[0],sizeof(struct st),1,fp);
      if(ret2 != 1) {
          if(feof(fp)) {
              printf("read\n");
          } else if(ferror(fp)){
           printf("read\n");
         } else if(ferror(fp)){
             printf("读取1失败!\n");
             exit(EXIT_FAILURE);
         }
     }
     ret2 = fread(&new[1],sizeof(struct st),1,fp);
     if(ret2 != 1) {
         if(feof(fp)) {
             printf("read\n");
         } else if(ferror(fp)){
             printf("读取2失败!\n");
             exit(EXIT_FAILURE);
         }
     }

      printf("%d,%d,%d\n",new[0].a,new[0].b,new[0].c);

      return EXIT_SUCCESS;
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

格式化 I/O:

printf()
// 失败返回负数
fprintf()
sprintf()
scanf()
sscanf()
// 通常大于等于 0, 不区分 EOF 和 error,用 ferror 判断。
fscanf()
// perror()用于在 stderr 的错误信息之前,添加一个自定义字符串。
void perror(const char *s);
1
2
3
4
5
6
7
8
9
10

(3)定位流

   int fseek(FILE *stream, long offset, int whence);    // 定位文件位置指针到指定的位置
   long ftell(FILE *stream);                            // 返回当前文件位置指针

   void rewind(FILE *stream);                           // 定位到文件头,等价于 fseek(stream, 0, SEEK_SET)
   int fgetpos(FILE *stream, fpos_t *pos);              // 获取文件位置指针到参数 pos 中
   int fsetpos(FILE *stream, const fpos_t *pos);        // 通过参数 pos 修改文件位置指针
   // fgetpos() 和 fsetpos() 引入了新数据类型:fpos_t,记录文件的位置。
   // 成功为 0,出错为 非 0.
1
2
3
4
5
6
7
8

(4)临时文件

函数原型:FILE *tmpfile(void);

tmpfile()创建一个临时二进制文件(类型wb+),在关闭该文件或程序结束时将自动删除这种文件。

#include <stdio.h>

int main(void)
{
    FILE *fp;
    char buf[128] = "";

    if (!(fp = tmpfile())) {
	perror("打开临时文件失败");
	return -1;
    }

    // 写入数据
    fwrite("1234567890", 1, 10, fp);

    // 定位到文件头
    rewind(fp);

    // 读取数据
    fread(buf, 1, sizeof (buf), fp);
    printf("buf:%s\n", buf);

    // 关闭临时文件,会自动删除,也可以不用关闭,进程结束时会自动删除
    fclose(fp);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

(5)sscanf()

函数原型

int scanf( const char *restrict format, ... );
int fscanf( FILE *restrict stream, const char *restrict format, ... );
int sscanf( const char *restrict buffer, const char *restrict format, ... );
int scanf_s(const char *restrict format, ...);
int fscanf_s(FILE *restrict stream, const char *restrict format, ...);
int sscanf_s(const char *restrict buffer, const char *restrict format, ...);
1
2
3
4
5
6

(6)sprintf()

函数原型

int printf( const char          *format, ... );
int printf( const char *restrict format, ... );
// 写入错误返回 负数。
int fprintf( FILE          *stream, const char          *format, ... );
int fprintf( FILE *restrict stream, const char *restrict format, ... );
int sprintf( char          *buffer, const char          *format, ... );
int sprintf( char *restrict buffer, const char *restrict format, ... );
int snprintf( char *restrict buffer, size_t bufsz,
              const char *restrict format, ... );
int printf_s( const char *restrict format, ... );
int fprintf_s( FILE *restrict stream, const char *restrict format, ... );
int sprintf_s( char *restrict buffer, rsize_t bufsz,
               const char *restrict format, ... );
int snprintf_s( char *restrict buffer, rsize_t bufsz,
                const char *restrict format, ... );
fflush(fp)//用于清空缓存区。它接受一个文件指针作为参数,将缓存区内容写入该文件。
fflush(NULL); // 不保存缓冲区内容
perror(str);  // perror()用于在 stderr 的错误信息之前,添加一个自定义字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18