# 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
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
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
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
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
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
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
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
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
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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18