本章主要内容是I/O库函数,主要介绍了: 1. I/O库函数的作用和它对于系统调用的对比和优势; 2. I/O库函数的算法及应用,包括fread、fwrite和fclose这三个基础文件编辑函数; 3. I/O库函数的不同模式及相关操作实践; 4. 文件流缓冲方案; 5. 关于有不同参数的函数以及如何使用stdarg宏来访问参数。
I/O库函数与系统调用
系统调用是文件操作的基础,但是它只支持对于数据块的读和写操作,并不能满足用户对于数据的结构化操作的需求,而I/O库函数就是为了满足该需求而生的,I/O库函数的使用提高了对数据的处理效率。
系统函数: open(),用于创建一个新的文件描述符; read(),读取文件,从文件描述符 fildes 相关联的文件里读入 nbytes 个字节的数据,并把它们放到数据区 buffer 中; write(),把缓冲区 buffer 的前 nbytes 个字节写入与文件描述符 fildes 关联的文件中; lseek(),用于改变读写操作时的位置指针; close(),终止文件描述符 fildes 与其对应文件之间的关联。 I/O库函数: fopen(),主要用于对文件和终端的输入输出; fread(),主要作用是从一个文件流里读取数据,数据从stream读到由ptr指定的数据缓冲区里面; fwirte(),从stream获取数据记录写到ptr中,返回值是成功写入的记录个数; fseek(),在文件流里面为下一次读写指定位置; fclose(),关闭指定的文流stream,使所有未写出的内容全部写出。 可以看出来,二者之间的函数功能基本可以一一对应,但是在系统调用中,使用了整数型变量文件描述符fd,而在I/O库函数中则使用了文件流指针fp;系统调用的buffer最多包含4KB的字符,并且一般使用while循环一次一个字节的写入数据,效率十分低下。而I/O库函数可以使用fgetc(fp)将文件流中的所有数据读取,十分高效。
I/O库函数算法
1. 算法之fread() 函数原型:
#include <stdio.h> size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
ptr:数据读到ptr指定的缓冲区里面。 size:每个数据记录的长度(类似与char,int,long,float之类,指代每次读取块的大小) nitems:传输的记录个数 stream:指定要读取的数据缓冲区 返回值:读到缓冲区的记录个数(非字节),如果读到文件尾,其返回值可能小于nitems,甚至可以是0(读取文件为空)。 2. 算法之fwrite() 函数原型:
#include <stdio.h> size_t fwrite(const void *ptr, size_t size, size_t items, FILE *stream);
函数中的参数和fread()类似,可以一起看。 3. 算法之fclose() 函数原型:
#include <stdio.h> int fclose(FILE *stream);
函数参数: stream:指定要关闭的文件流stream 返回值:如果成功返回0,失败返回EOF,同时会向全局变量errorn写入错误信息码。
其它的算法见:
常用标准I/O库函数总结
在fopen()函数中,有模式参数"r","w"和"a",分别代表了读、写和追加三种模式。每个模式字符串可以添加一个+号,表示可以同时读写或者追加写入,如果文件不存fopen()函数会创建这个文件。
字符模式I/O(重点)
函数原型:
int fgetc( File *fp); int unget( int c ; FILE *fp); int fputc( int c , FILE *fp);
这三个函数均是按字符读取文件内容,可以实现对文件的按字符读写(其中fgetc实现按字符读操作,而ungetc和fputc则是按字符写入操作)
行模式I/O(重点)
函数原型:
char *fgets(char *buf , int size_z , FILE *fp); int fputs(char *buf , FILE *fp);
fgets()函数从指定的流 fp 读取一行,并把它存储在 buf 所指向的字符串内。当读取 (size_z-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止。 参数解析: * buf -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。 * size_z -- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。 * fp -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。 fputs()函数则是将 buf 指向的字符串按行输入到 FILE 指针指向的对象文件中去,其参数与fgets()函数类似,可以以它作为参考。
1.格式化输入 scanf() 和 fscanf(),格式化的输入; 函数原型:
scanf(char *FMT , &items); fscanf(FILE *fp , char *FMT , &items);
scanf()从标准输入 stdin 读取格式化输入;fscanf()从流 fp 读取格式化输入; 参数解析: 二者功能类似,参数可互相参考 fp -- 指向 FILE 对象的指针, FILE 对象标识了 fp 流; FMT -- 表示C字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符。
format说明符见 :
C 库函数 - fscanf()
2.格式化输出 printf()和fprintf(),格式化的输出; 函数原型:
printf(char *FMT , &items); fprintf(FILE *fp , char *FMT , &items);
printf()发送格式化输出到标准输出stdout;fprintf()发送格式化输出到流 fp 中; 参数可以参考格式化输入。
1.sscanf(buf , FMT , &items);从字符串读取格式化输入; 2.sprintf(buf , FMT , items);发送格式化输出到 str 所指向的字符串;可以完成ASCII数字和整数之间的转换。
PS:
可以实现整型与字符串类型转换的两个函数:
itoa()(integer to alphanumeric,整型数转字符串)和 atoi()(alphanumeric to integer,字符串转整型数)
1、itoa()
它可以实现整型数向字符串的类型转变;
2、atoi()
它可以实现把字符串转换成整型数的操作;
函数原型如下:
char* itoa(int value ,char *string ,int radix); int atoi(const char *nptr);
其参数如下:
itoa(): value: 要转换的整数; string: 转换后的字符串; radix: 转换进制数,如2,8,10,16 进制等; atio(): nptr:要转换的字符串。
其它的I/O数据库函数
限制混合fread()和fwrite()
当文件流要同时使用 fread()函数和fwrite()函数时,会限制混用这两个函数的调用,要求编程者在二者之间至少要有一个fseek()和ftell()
每一个文件流都有一个FILE结构体,它包含了一个内部的缓冲区。想要对文件流进行读写就需要对该缓冲区进行遍历。所以才会有文件的流缓冲。主要有以下三种方式。
无缓冲:标准I/O不对字符进行缓冲处理,即对所有的从非缓冲流中写入或者读取的字符将马上被单独传送到文件中去或者从文件中传输出来。如stder函数;
行缓冲:当在输入和输出遇到换行符时,标准I/O执行I/O操作。如stdout函数;
全缓冲:在填满I/O缓冲区后再进行实际的I/O操作。一般的文件流缓冲方式。
在fopen()创建文件流之后,在对它进行任何操作之前,都可以发送以下函数来选择采用何种缓冲方式:
int setvbuf(FILE *stream, char *buf, int mode, int size)
其中参数如下:
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
buf -- 这是分配给用户的缓冲。如果设置为 NULL,该函数会自动分配一个指定大小的缓冲。
mode -- 这指定了文件缓冲的模式;
size --这是缓冲的大小,以字节为单位。
模式必须为以下三者之一:
_IOFBF/(F=full)全缓冲:对于输出,数据在缓冲填满时被一次性写入。对于输入,缓冲会在请求输入且缓冲为空时被填充;
_IOLBF/(L=line)行缓冲:对于输出,数据在遇到换行符或者在缓冲填满时被写入,具体视情况而定。对于输入,缓冲会在请求输入且缓冲为空时被填充,直到遇到下一个换行符;
_IONBF 无缓冲:不使用缓冲。每个 I/O 操作都被即时写入。buffer 和 size 参数被忽略。
参数数量可变的函数,比如printf()函数。其基本格式如下:
int func(int n , int m , ...);