目录
接口
打开文件
关闭文件
读写
每次一个字符的读写标准 IO 函数接口
每次一行的读写标准 IO 函数接口
每次读写若干数据块的标准 IO 函数接口
获取或设置文件当前位置偏移量
标准格式化 IO 函数
系统 IO 的最大特点一个是更具通用性,不管是普通文件、管道文件、设备节点文件、 套接字文件等等都可以使用,另一个是他的简约性,对文件内数据的读写在任何情况下都是 不带任何格式的,而且数据的读写也都没有经过任何缓冲处理,这样做的理由是尽量精简内 核 API,而更加丰富的功能应该交给第三方库去进一步完善
标准 C 库是最常用的第三方库,而标准 IO 就是标准 C 库中的一部分接口,这一部分 接口实际上是系统 IO 的封装,他提供了更加丰富的读写方式,比如可以按格式读写、按 ASCII 码字符读写、按二进制读写、按行读写、按数据块读写等等,还可以提供数据读写 缓冲功能,极大提高程序读写效率
接口
打开文件
返回的文件指针是一种指向结构体 FILE{}的指针,该结构体在标准 IO 中被定义
typedef struct _IO_FILE FILE; // 定义 FILE 等价于 _IO_FILE
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. 251 char* _IO_read_ptr;
char* _IO_read_end;
char* _IO_read_base;
char* _IO_write_base;
char* _IO_write_ptr;
char* _IO_write_end;
char* _IO_buf_base;
char* _IO_buf_end;
/* The following fields are used to support backing up and undo. */
char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; // 文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset;
#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
};
数据将会先存储在一个标准 IO 缓冲区中,而后在一定条件下才被一并 flush(冲洗,或称刷新)至内核缓冲区,而不是像 系统 IO那样,数据直接被flush至内核
标准 IO 函数 fopen( )实质上是系统 IO 函数 open( )的封装,他们是一一对 应的,每一次 fopen( )都会导致系统分配一个 file{ }结构体和一个 FILE{}来保存维护该 文件的读写信息,每一次的打开和操作都可以不一样,是相对独立的,因此可以在多线程或 者多进程中多次打开同一个文件,再利用文件空洞技术进行多点读写
关闭文件
该函数用于释放由 fopen( )申请的系统资源,包括释放标准 IO 缓冲区内存,因此 fclose( )不能对一个文件重复关闭
读写
每次一个字符的读写标准 IO 函数接口
- fgetc( )、getc( )和 getchar( )返回值是 int,而不是 char,原因是因为他们在出 错或者读到文件末尾的时候需要返回一个值为 -1 的 EOF 标记,而 char 型数据有可能因 为系统的差异而无法表示负整数。
- 当 fgec( )、getc( )和 getchar( )返回 EOF 时,有可能是发生了错误,也有可能 是读到了文件末尾
- getchar( )缺省从标准输入设备读取一个字符。
- putchar( )缺省从标准输出设备输出一个字符。
- fgetc( )和 fputc( )是函数,getc( )和 putc( )是宏定义。
- 两组输入输出函数一般成对地使用,fgetc( )和 fputc( ),getc( )和 putc( ), getchar( )和 putchar( ).
普通文件的拷贝操作
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdbool.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <strings.h>
7 #include <errno.h>
8
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <fcntl.h>
12
13 int main(int argc, char **argv)
14 {
15 if(argc != 3)
16 {
17 printf("Usage: %s <src> <dst>\n", argv[0]);
18 exit(1);
19 }
20
21 // 分别以只读和只写模式打开源文件和目标文件
22 FILE *fp_src = fopen(argv[1], "r");
23 FILE *fp_dst = fopen(argv[2], "w");
24
25 // 如果返回 NULL 则程序出错退出
26 if(fp_src == NULL || fp_dst == NULL)
27 {
28 perror("fopen()");
29 exit(1);
30 }
31
32 int c, total = 0;
33 while(1)
34 {
35 c = fgetc(fp_src); // 从源文件读取一个字符存储在变量 c 中
36
37 if(c == EOF && feof(fp_src)) // 已达文件末尾
38 {
39 printf("copy completed, " 40 "%d bytes have been copied.\n", total);
41 break;
42 }
43 else if(ferror(fp_src)) // 遇到错误
44 {
45 perror("fgetc()");
46 break;
47 }
48
49 fputc(c, fp_dst); // 将变量 c 中的字符写入目标文件中
50 total++; // 累计拷贝字符个数
51 }
52
53 // 正常关闭文件指针,释放系统资源
54 fclose(fp_src);
55 fclose(fp_dst);
56
57 return 0;
58 }
每次一行的读写标准 IO 函数接口
- fgets( )跟 fgetc( )一样,当其返回 NULL 时并不能确定究竟是达到文件末尾还是 碰到错误,需要用 feof( )/ferror( )来进一步判断。
- fgets( )每次读取至多不超过 size 个字节的一行,所谓“一行”即数据至多包含一 个换行符’\n’。
- gets( )是一个已经过时的接口,因为他没有指定自定义缓冲区 s 的大小,这样很容 易造成缓冲区溢出,导致程序段访问错误。
- fgets( )和 fputs( ),gets( )和 puts( )一般成对使用,鉴于 gets( )的不安全性, 一般建议使用前者。
普通文件拷贝
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdbool.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <strings.h>
7 #include <errno.h>
8
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <fcntl.h>
12
13 #define BUFSIZE 100
14
15 int main(int argc, char **argv)
16 {
17 if(argc != 3)
18 {
19 printf("Usage: %s <src> <dst>\n", argv[0])
20 exit(1);
21 }
22
23 // 分别以只读和只写模式打开源文件和目标文件
24 FILE *fp_src = fopen(argv[1], "r");
25 FILE *fp_dst = fopen(argv[2], "w");
26
27 // 如果返回 NULL 则程序出错退出
28 if(fp_src == NULL || fp_dst == NULL)
29 {
30 perror("fopen()");
31 exit(1);
32 }
33
34 char buf[BUFSIZE]; // 自定义缓冲区
35 int total = 0;
36 while(1)
37 {
38 bzero(buf, BUFSIZE);
39 if(fgets(buf, BUFSIZE, fp_src) == NULL) // 从源文件读数据
40 {
41 if(feof(fp_src)) // 已达文件末尾
42 {
43 printf("copy completed, %d bytes"
44 " have been copied.\n", total);
45 break;
46 }
47 else if(ferror(fp_src)) // 遇到错误
48 {
49 perror("fgetc()");
50 break;
51 }
52 }
53
54 fputs(buf, fp_dst); // 将自定义缓冲区中的数据写入目标文件
55 total += strlen(buf); // 累计拷贝的字节个数
56 }
57
58 // 正常关闭文件指针,释放系统资源
59 fclose(fp_src);
60 fclose(fp_dst);
61
62 return 0;
63 }
每次读写若干数据块的标准 IO 函数接口
这一组标准 IO 函数被称为“直接 IO 函数”或者“二进制 IO 函数”,因为他们对数 据的读写严格按照规定的数据块数和数据块的大小来处理,而不会对数据格式做任何处理, 而且当数据块中出现特殊字符,比如换行符’\n’、字符串结束标记’\0’等时不会受到影响
- 如果 fread( )返回值小于 nmemb 时,则可能已达末尾,或者遇到错误,需要借助 于 feof( )/ferror( )来加以进一步判断。
- 当发生上述第 1 种情况时,其返回值并不能真正反映其读取或者写入的数据块数, 而只是一个所谓的“截短值”,比如正常读取 5 个数据块,每个数据块 100 个字节,在执行成功的情况下返回值是 5,表示读到 5 个数据块总共 500 个字节,但是如果只读到 499 个数据块,那么返回值就变成 4,而如果读到 99 个字节,那么 fread( )会返回 0。因此当发生返回值小于 nmemb 时,需要仔细确定究竟读取了几个字节,而不能直接从返回值确定。
获取或设置文件当前位置偏移量
- fseek( )的用法基本上跟系统 IO 的 lseek( )是一致的。
- rewind(fp)相等于 fseek(fp, 0L,
标准格式化 IO 函数
格式化 IO 函数中最常用的莫过于 printf( )和 scanf( )了,但从上表中可以看到,他 们其实各自都有一些功能类似的兄弟函数可用
- fprintf( )不仅可以像 printf( )一样向标准输出设备输出信息,也可以向由 stream 指定的任何有相应权限的文件写入数据。
- sprintf()和 snprintf()都是向一块自定义缓冲区写入数据,不同的是后者第二个参 数提供了这块缓冲区的大小,避免缓冲区溢出,因此应尽量使用后者,放弃使用前者。
- fscanf( )不仅可以像 scanf( )一样从标准输入设备读入信息,也可以从由 stream 指定的任何有相应权限的文件读入数据。
- sscanf( )从一块由 s 指定的自定义缓冲区中读入数据。
- 最重要的一条:这些函数的读写都是带格式的
假设有一个文件存储了班级学生的姓名、性别、年龄和身高,需要将这个文件读入程序,再将之打印到屏幕 显示出来
Mike M 18 167.2
Lucy F 17 155.3
Jack M 22 171.0
Joe M 19 175.1
Rose F 21 169.1
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdbool.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <strings.h>
7 #include <errno.h>
8
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <fcntl.h>
12
13 #define NAMELEN 20
14
15 struct student // 用来存放一个带有一定数据格式的学生节点
16 {
17 char name[NAMELEN];
18 char sex;
19 int age;
20 float stature;
21
22 struct student *next; // 链表
23 };
24
25 struct student *init_list(void) // 初始化一个空链表
26 {
27 struct student *head = malloc(sizeof(struct student));
28 head->next = NULL;
29
30 return head;
31 }
32
33 // 将新节点 new 添加到链表 head 中
34 void add_student(struct student *head, struct student *new)
35 {
36 struct student *tmp = head;
37
38 while(tmp->next != NULL)
39 tmp = tmp->next;
40
41 tmp->next = new;
42 }
43
44 // 显示链表中的所有节点
45 void show_student(struct student *head)
46 {
47 struct student *tmp = head->next;
48
49 while(tmp != NULL)
50 {
51 fprintf(stdout, "%-5s %c %d %.1f\n", 52 tmp->name, tmp->sex, tmp->age, tmp->stature);
53
54 tmp = tmp->next;
55 }
56 }
57
58 int main(int argc, char **argv)
59 {
60 FILE *fp = fopen("format_data", "r");
61
62 // 创建一个用来保存学生节点的空链表
63 struct student *head = init_list();
64
65 int count = 0;
66 while(1)
67 {
68 struct student *new = malloc(sizeof(struct student));
69
70 // 从文件 fp 中按照格式读取数据,并将之填充到 new 中
71 if(fscanf(fp, "%s %c %d %f",
72 new->name, &(new->sex),
73 &(new->age), &(new->stature)) == EOF)
74 {
75 break;
76 }
77
78 // 将新节点 new 加入到链表 head 中
79 add_student(head, new);
80 count++;
81 }
82
83 printf("%d students have been added.\n", count);
84 show_student(head); // 打印所有的节点
85
86 fclose(fp); // 关闭文件指针,释放系统资源
87
88 return 0;
89 }