abc_begin

导航

PILE读书笔记_标准I/O

在学习和分析标准I/O库的同时, 可以重点与Linux的I/O系统调用进行比较。

stdin、 stdout和stderr都是FILE类型的文件指针, 是由C库静态定义的, 直接与文件描述符0、 1和2相关联, 所以应用程序可以直接使用它们。其中,stdin是不可写的, stdout是不可读的, 而stderr不仅不可读, 且没有缓存。

 

I/O的缓存

C库的I/O接口对文件I/O进行了封装, 为了提高性能, 其引入了缓存机制, 共有三种缓存机制: 全缓存、 行缓存及无缓存。

(1)全缓存一般用于访问真正的磁盘文件。 C库会为文件访问申请一块内存, 只有当文件内容将缓存填满或执行冲刷函数flush时, C库才会将缓存内容写入内核中。

(2)行缓存一般用于访问终端。 当遇到一个换行符时, 就会引发真正的I/O操作。 需要注意的是, C库的行缓存也是固定大小的。 因此, 当缓存已满, 即使没有换行符时也会引发I/O操作。

(3)无缓存, 顾名思义, C库没有进行任何的缓存。 任何C库的I/O调用都会引发实际的I/O操作。

 

下面看一个行缓存的例子:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 int main(void)
 5 {
 6     printf("Hello ");
 7     if (0 == fork()) {
 8         printf("child\n");
 9         return 0;
10     }
11     printf("parent\n");
12     return 0;
13 }

执行结果如下:

之所以是这样的结果, 就是因为背后的行缓存。 执行printf( "Hello") 时, 因为printf是向标准输出打印的, 因此使用的是行缓存。 字符串Hello没有换行符, 所以并没有真正的I/O输出。 当执行fork时,子进程会完全复制父进程的内存空间, 因此字符串Hello也存在于子进程的行缓存中。 故而最后的输出结果中, 无论是父进程还是子进程都有Hello字符串。

 

fopen函数

1 # include<stdio.h>
2 FILE * fopen(const char * path,const char * mode) 

参数说明:

(1)path:需要打开的文件路径

(2)mode:文件打开方式,主要对应下表

modefunction
r 以只读方式打开文件,该文件必须存在。
r+ 以可读写方式打开文件,该文件必须存在。
rb+ 读写打开一个二进制文件,允许读数据。
rt+ 读写打开一个文本文件,允许读和写。
w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留)
wb 只写打开或新建一个二进制文件;只允许写数据。
wb+ 读写打开或建立一个二进制文件,允许读和写。
wt+ 读写打开或着建立一个文本文件;允许读写。
at+ 读写打开一个文本文件,允许读或在文本末追加数据
ab+ 读写打开一个二进制文件,允许读或在文件末追加数据。

 

fdopen函数

1 #include <stdio.h>
2 FILE *fdopen(int fd, const char *mode);

fdopen用于从文件描述符fd生成一个文件流FILE

 

fileno函数

1 #include <stdio.h>
2 int fileno(FILE *stream);

fileno则用于从文件流FILE得到对应的文件描述符

无论是fdopen还是fileno, 关闭文件时, 都要使用fclose来关闭文件, 而不是用close。 因为只有采用此方式, fclose作为C库函数, 才会释放文件流FILE占用的内存。

 

fread函数

1 size_t fread(void *buffer, size_t size, size_t count, FILE *file);

参数说明:

(1)buffer是读取数据后存放地址

(2)size是的块长度

(3)count是块的数量,实际读取长度为size*count,返回值为块成功读取块的count数量

 

fwrite函数

1 size_t fwrite(const void *buffer, size_t size, size_t count, FILE *file);

参数说明:

(1)buffer是写入数据后存放地址

(2)size是的块长度

(3)count是块的数量,实际读取长度为size*count,返回值为块成功写入块的count数量

 

下面看一个读写文件的例子:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 int main(void)
 5 {
 6     char buf[20];
 7     int ret;
 8     FILE *fp = fopen("./tmp.txt", "w+");
 9     if (!fp) {
10     printf("Fail to open file\n");
11     return -1;
12     }
13     ret = fwrite("123", sizeof("123"), 1, fp);
14     printf("we write %d member\n", ret);
15     memset(buf, 0, sizeof(buf));
16     ret = fread(buf, 1, 1, fp);
17     printf("We read %s, ret is %d\n", buf, ret);
18     fwrite("456", sizeof("456"), 1, fp);
19     fclose(fp);
20     return 0;
21 }

运行结果:

为什么fread什么都没有读取到, 返回值是0呢?  因为每一次系统调用的readwrite成功返回后, 文件的偏移量都会被更新 。fwrite和fread操作的是同一个文件指针fp, 也就是对应的是同一个文件描述符。 第一次fwrite后, 在tmp.txt中写入了字符串“123”, 同时文件偏移为3, 也就是到了文件尾。 进行fread操作时, 既然操作的是同一个文件描述符, 自然会共享同一个文件偏移, 那么, 从文件尾自然读取不到任何数据。

 

ferror函数

1 int  ferror(FILE *stream);

若文件流出错则返回非0,否则返回0。ferror()函数常与clearerr()函数一起使用,如果ferror(fp)发现错误,返回一个非0值,那么调用clearerr(fp)后,ferror(fp)的值变为0。

当文件流读到文件尾时, 文件流会被设置上EOF标志。 即使不使用clearerr清除EOF标志, 有新的数据, 也可以读取成功,这是因为:文件流FILE的错误标志位只有在打开IO_DEBUG的情况下才会对后面的I/O调用产生影响: 在有错误标志位的时候, 后面的I/O调用都会直接返回EOF。 而一般情况下, IO_DEBUG这个宏是没有定义的。

 

getc函数 

1 #include <stdio.h>
2 int fgetc(FILE *stream);
3 int getc(FILE *stream);

正常的情况下返回一个char,可以吗?

不可以,这些函数返回的字符是以unsigned char形式保存,然后转换为int的。因此,在int的值域里,有效的返回值总是一个正整数。例如,遇到文件结束时,返回宏EOF。通常EOF定义成-1,在int域里,-1是没有歧义的,我们不会迷惑到底是读进来了一个字符'0xff',还是遇到了EOF。而在char域里,这样的歧义是存在的。

 

posted on 2017-10-15 16:07  LastBattle  阅读(257)  评论(0编辑  收藏  举报