第九章I/O库函数
一.知识点归纳
(一)I/O库函数
系统调用是文件操作的基础,但它们只支持数护据块的读/写。实际上,用户程序可能希
望以最适合应用程序的逻辑单元读/写文件,如行、 字符、结构化记录等,而系统调用不支
持这些逻辑单元。I/O库函数是一系列文件操作函数,既方便用户使用,又提高了整体效率。
(二)库函数与系统调用
-
系统调用函数:open()、 read()、 write()、 lseek()、 close()
-
I/O库函数:fopen()、 fread()、 fwrite()、 flseek()、 fclose()
(三)I/O库函数的算法
-
fread算法
- 第一次调用fread()时,FILE结构体的缓冲区是空的,fread()使用保存的文件描述符fd发一个n = read(fd, fbuffer, BLKSIZE); 系统调用,用数据块填充内部的fbuf[]。然后,它会初始化fbufl]的指针、计数器和状态查量,以表明内部缓冲区中有一个数据块。接着,通过将数据复制到程序的缓冲区,尝满足来自内部缓冲区的 freadO 调用。如果内部缓冲区没有足够的数据,则会再发出一 read0系统调用来填充内部缓冲区,将数据从内部缓冲区传输到程序缓冲区,直到满足所的字节数(或者文件无更多数据)。将数据复制到程序的缓冲区之后,它会更新内部缓冲区的指针、计数器等,为下一个freadO请求做好准备。然后,它会返回实际读取的数据对数量。
- 在随后的每次 fread()调用中,它都尝试满足来自 FILE 结构体内部缓冲区的调用。当缓冲区变为空时,它就会发出read()系统调用来重新填充内部缓冲区。因此,fread()一方面接受来自用户程序的调用,另一方面向操作系统内核发出read()系统调用。除了 read()系统调用之外,所有fread()处理都在用户模式映像中执行。它只在需要时才会进人操作系统内核,并且以一种最高效匹配文件的方式进人。它会提供自动缓冲机制,因此用户程序不必担心这些具体操作。
-
fwrite算法
- fwrite()算法与fread()算法相似,只是数据传输方向不同。最开始,FILE结构体的内缓冲区是空的。在每次调用fwrite()时,它将数据写入内部缓冲区,并调整缓冲区的指针、计数器和状态变量,以跟踪缓冲区中的字节数。 如果缓冲区已满,则发出write()系统调用,将整个缓冲区写入操作系统内核。
-
fclose算法
- 若文件以写的方式被打开,fclose()会先关闭文件流的局部缓冲区。然后,它会发出一个close(fd)系统调用来关闭 FILE 结构体中的文件描述符。最后,它会释放 FILE 结构体,并将FILE指针重置为NULL。
(四)使用I/O库函数或系统调用
据上面所讨论的内容,现在我们可以回答什么时候使用系统调用或库函数进行文件的问题。fread()依赖rad据从内复制到内部缓冲区,然后从内部缓冲区将数据复制到程序的缓冲区。所以,它传输了两次数据。相反,read()将数据从内核直接复制到程的缓冲区,只复制了一次。因此,对于以BLKSIZE单位的读/写数据来说,read()本来比fread()更高效,因为它需要一个而不是两个复制操作。类似表述也适用于write()和 fwrite()。
需要注意,在fread() 和fwriteO的一些实现中,例如在GNUlibc库中,如果请求的大小以BLKSIZE为单位,它们可以使用系统调用将以BLKSIZE为单位的数据直接从内核传输到用户指定的缓冲区。即便如此,使用I/O 库函数仍然需要其他的函数调用。因此,在上面的例子中,使用系统调用的程序实际上比使用IO库函数的程序更高效。但是,如果不是以BLKSIZE 为单位进行读/写,那么fread()和fwrite()可能更高效。例如,如果我们坚持一次读/写一个字节,fread()和fwrite()会好得多,因为它们进入操作系统内核只是为了填充或清除内部缓冲区,并不是逐字节输入。这里,我们曾暗中假设,即进入内核模式比停留在用户模式的代价更高,事实的确如此。
(五)I/O库模式
fopen()中的模式参数可以指定为:"r"、"w"、"a",分别代表读、写、追加。
每个模式字符串可包含一个+号,表示同时读写,或者在写入、追加情况下,如果文件不存在则创建文件。
"r+":表示读/写,不会截断文件。
"w+":表示读/写,但是会先截断文件;如果文件不存在,会创建文件。
"a+":表示通过追加进行读/写;如果文件不存在,会创建文件。
-
字符模式I/O
点击查看代码
int fgetc(FILE *fp); //get a char from fp, cast to int. int ungetc(int c, FILE *fp); //push a previously char got by fgetc() back to stream int fputc(int c, FILE *fp); //put a char to fp
注意,fgetc()返回的是整数,而不是字符。这是因为它必须在文件结束时返回文件结束符。文件结束符通常是一个整数-1,将它与文件流中的任何字符区分开。
对于fp=stdin或stdout,可能会使用c=getchar();sputchar(c);来代替。对于运行时效来说,getchar()和putchar()通常不是getc()和putc()的的缩小版本。相反,可以将它们实现为宏,以避免额外的函数调用。 -
行模式I/O
char *fgets(char buf, int size,FILEfp);从fp中读取最多为一行(以n结尾)的字符。
int fputs(char *buf, FILE *fp);将buf中的一行写入 fp 中。 -
格式化I/O
- 格式化输入:(FMT=格式字符串)
scanf(char *FMT, &items); //from stdin fscanf(fp, char *FMT, &items); // from file stream
- 格式化输出:
printf(char *FMT, &items); //to stdin fprintf(fp, char *FMT, &items); //to file stream
-
内存中的转换函数
sscanf(buf, FMT, &items); // input from buf[ ] in memory sprintf(buf, FMT, items); // print to buf[ ] in memroy
注意,sscanf()和sprintf()并非 I/O 函数,而是内存中的数据转换函数。例如,atoi()是一个标准库函数,将一串ASCII数字转换成整数,但是大多数Unix/Linux系统没有itoA()函数,因为转换可由sprintf()完成,所以不需要它。
-
其他I/O库函数
-
fseek()、 ftell()、 rewind():更改文件流中的读/写字节位置。
-
feof()、 ferr()、 fileno():测试文件流状态。
-
fdopen():用文件描述符打开文件流。
-
freopen():以新名称重新打开现有的流。
-
setbuf()、 setvbuf():设置缓冲方案。
-
popen():创建管道,复刻子进程来调用sh。
-
-
限制混合fread-fwrite
当某文件流同时用于读、写时,就会限制使用混合fread()和fwrite()调用。规范要求每对fread()和fwrite()之间至少有一个fseek()和ftell()。
(六)文件流缓冲
每个文件流都有一个FILE结构中包含个内部缓冲区。对文件流进行读写需要历FE以用三种案中的一种。
- 无缓冲:从非缓冲流中写人或读取的学符将尽快单独传输到文件成从文件中传输。例如,文件流stderr常无缓冲。到stderr的所有输出都会立即发出。
- 行缓冲:遇到换行符时,写人行缓冲流的字符以块的形式传输。例如,文件流stdout通常是行缓冲,逐行输出数据。
- 全缓冲:写人全缓冲流或从中读取的字符以块大小传输到文件或从文件传输。这是文件流的正常缓冲方案。
通过fopen()创建文件流之后,在对其执行任何操作之前,用户均可发出一个
getvbuf(FILE *stream, char *buf, int node, int size)
调用来设置缓冲区(buf)、缓冲区大小(size)和缓冲方案(mode),它们必须是以下一个宏:
- _IONBUF:无缓冲。
- _IOLBUF:行缓冲。
- _IOFBUF:全缓冲。
此外,还有其他的setbuf()函数,是setvbufO的变体。读者可参考setvbuf手册页,以了解更多详细信息。
对于行缓冲流或全缓冲流,可用 fflush(stream)立即清除流的缓冲区。
(七)变参函数
在I/O库函数中,printf()相当独特,因为多种不同类型的可变数量参数可以调用它。这是允许的,因为最初的C语言不是一种类型检查语言。目前,C语言和C++会强执行类型检查,但是为了方便,这两种语言仍然允许参数数量可变的函数。这些函数必须至少个一个参数进行声明,后跟3个点,如
int func(int m, int n...) //n=last specified parameter
在函数内部,可以通过C语言库宏访问参数:
void va_start(va_list ap, last); //start param list from last parameter
type va_arg(va_list ap, type); // type = next parameter type
va_end(va_list ap); //clear parameter list
二.问题与解决思路
问题
myprintf代码能在codeblocks中实现,但不能在Ubuntu终端中运行
解决方式
上网查询资料,更改代码解决
三.实践内容与截图,代码链接