标准I/O
2018-08-06 (星期一)
标准I/O
标准I/O是与平台无关的用户缓冲方案.
文本指针
标准I/O历程不会直接操作文件描述符,而会使用他们自己的标识符,成为"文件指针"(file pointer),在C链接库之内,文件指针会映射到一个文件描述符.文件指针会被表示成一个指向typedef所定义的FILE结构的指针(定义于<stdio.h>)
在标准I/O用语,一个一打开的文件成为"流"(stream).流可能会被打开以供读取(输入流),写入(输出流)或读取(输入/输出流)
打开文件
要打开文件以备通过fopen()读取或写入:
#include <stdio.h> FILE * fopen (const char *path, const char *mode);
此函数会根据所指定的模式打开文件path,并将它关联到一个新的流.
模式
mode参数用来描述如何打开所指定的文件.它的值可能是下面的齐总一个字符串:
r 打开文件以备读取.流位于文件开端
r+ 打开文件以备读取和写入.流位于文件开端.
w 打开文件以备写入,如果文件已存在,他会被截短成零长度;如果文件不存在,则会被创建.流位于文件开端.
w+ 打开文件以备读取和写入.如果文件已存在,他会被截短成零长度;如果文件不存在,则会被创建.流位于文件开端.
a 打开文件已被写入并且进入附加模式.如果文件不存在,则会创建.流位于文件末端,所写入的数据会被附加到文件末端.
a+ 打开文件以备读取和写入并且进入附加模式,如果文件不存在,则会被创建.流位于文件末端.所写入的数据会被附加到文件末端.
另外b模式 是要求fopen()以位二进制模式打开文件(linux总是会忽略此值)
执行成功时,fopen()会返回一个有效的FILE指针;执行失败时,他会返回NULL并且将errno设定为适当的值.
FILE *stream; stream = fopen("/etc/manifest", "r"); if (!stream) /*错误*/
将文件描述符打开流
函数fdopen()可将一个已打开的文件描述符(fd)转化成流:
#include <stdio.h>
FILE *fdopen(int fd, const char *mode);
mode参数的可能值和fopen()一样,并且必须兼容最初打开文件描述符所使用的模式.
文件描述符被转化成流之后,就不应该再以文件描述符进行I/O.请注意,文件描述符并未重复,而只是被关联到一个新的流.关闭流的同事也会关闭文件描述符.
执行成功时,fdopen()会返回一个有效的文件指针;失败时,返回NULL.
FILE *stream; int fd; fd = open("/home/kidd/map.txt", O_RDONLY); if (fd == -1) /* 错误 */ stream = fdopen(fd, "r"); if (!stream) /* 错误 */
关闭流
fclose()函数可用于关闭指定流:
#include <stdio.h>
int fclose(FILE *stream);
任何经缓冲而尚未写入磁盘的文件会首先被刷新(flush)到磁盘.执行成功时,fclose()会返回0;失败时会返回EOF并且讲errno设定为适当的值.
关闭所有的流
fcloseall()函数可用于关闭关联到当前进程的所有流,包括标准输入,标准输出以及标准错误
#define _GNU_SOURCE #include <stdio.h> int fcloseall(void);
关闭之后,所有流会被刷新到磁盘.此函数通常返回0,这是linux特有的.
从流中读取
标准C链接库实现了数个函数(从常见到罕见都有),可用于读取一个已经打开的流.
本届将介绍三种最常见的错做:一次读取一个字符,一次读取一整行以及读取二进制数据.为了从流中读取,必须以适当的模式打开一个流,也就是w或a除外的任何有效方式.
一次读取一个字符
函数fgetc()可用于从一个流中读取一个字符:
#include <stdio.h>
int fgetc(FILE *stream);
此函数会从stream读取下一个字符,并把它从unsigned char转型成int并返回.转型的目的是提供足够的空间给end-of-file或错误通知:返回值为EOF就是这种情况.fgetc()的返回值必须存放在int变量中.将他存放在char变量中是一种常见但危险的错误.
int c; c = fgetc(stream); if (c == EOF) /* 错误 */ else printf("c = %c\n", (char)c);
由stream所指向的流必须被打开以备读取.
2018-08-07 (星期二)
将字符放回
标准I/O提供了一个函数来讲一个字符推回流,让你可以"偷看一下"流,如果他不是你想要的字符,你可以把它换回去:
#include <stdio.h> int ungetc(int c, FILE *stream);
调用此函数就可以把C(会被转型成unsigned char)推回stream中.执行成功时会返回c,执行失败时会返回EOF.随后读取stream会返回c,如果有多个字符被推回,则他们会以相反的顺序被返回----也就是最后被推回的字符会首先被返回.
调用ungetc()之后,如果在发出读取请求之前调用了一个查找函数,将使得被推回的虽有字符被丢弃.此情况会出现在一个进程的各个线程之间,因为这些线程会共享缓冲区.
读取一整行
函数fgets()可用于从所指定的流读取一个字符串:
#include <stdio.h> char * fgets(char *str, int size, FILE *stream);
此函数会从stream读取size-1个字节并且将结果存入str.读进这些字节后,缓冲区会被存进一个null字符(\0).读到一个EOF或一个newline字符后,便会停止读取的动作.如果读到一个newline字符,则\n会被存入str.
执行成功时返回str;执行失败时返回NULL.
char buf[LINE_MAX]; if (!fgets(buf, LINE_MAX, stream)) /* 错误 */
POSIX讲LINE_MAX定义于<limits.h>:他是POSIX行操作借口所能处理的输入行的最大尺寸.linux的C链接库并无此类限制----输入航可以使任何尺寸.具可以执行的程序可以使用LINE_MAX以保安全,在linux上它会被设定为相对较大的值.linux特有的程序并不需要担心输入行的尺寸限制.
读取任意字符串
尽管fgets()所进行的给基于行的读取操作是有用的,但是也经常造成困扰.有时候,开发者可能使用newline以外的定界符(delimiter);有时候开发者可能不想使用任何定界符----而且很少有开发者会想讲定界符存入缓冲区!事实上,在所有返回的缓冲区中存入newline字符极少会出现正确的结果.
将fgets()换成fgetc()并不难.例如下面这段程序代码可以从stream讲n-1个字节读入str,然后附加一个\0字符:
char *s; int c; s = str; while (--n > 0 && (c = fgetc(stream) != EOF) *s ++ = c; *s = '\0';
这段代码还可以扩展成遇到d所指定的定界符(在此例中,他不可以是一个null字符)时停止读取:
char *s; int c = 0; s = str; while (--n > 0 && (c = fgetc(stream)) != EOF && (*s++ = c) != d) ; if (c == d) *--s = '\0'; else *s - '\0';
2018-08-08 (星期三)
读取二进制数据
对于某些应用程序而言,只读取个别的字符或输入行是不够的.有时候开发者会想读写复杂的二进制数据,例如C的结构.为此I/O链接库提供了fread():
#include <stdio.h> size_t fread (void *buf, size_t size, size_t nr, FILE *stream);
fread()可从stream中将数据督导buf所执行的缓冲区,所读取的字节数目由size*nr来决定(数据中共有nr个元素,每个元素具有size个字节).文件指针会前进实际所读取到的字节数.
执行成功时,fread()会返回所读取到元素数目(而不是字节数目).执行失败时,它会返回一个小于nr的值,支出发生了错误或EOF(读取到了文件末端).可惜,若不是用ferror()和feof(),则无法知道所发生的是这两种情况的哪一张.
因为变量大小,对其,补白以及字节顺序的不同,一个应用程序所写入的二进制数据未必可以被不同的应用程序所读取,或者未必可以被不同机器上的相同应用程序所读取.
写入一个流
和读取操作一样,标准C链接库定义了许多函数用于写入一个已打开的流.
写入单一字符
fgetc()的反向操作是fputc();
#include <stdio.h>
int fputc(intc, FILE *stream);
fputc()函数会将参数c(转型成unsigned char)写入stream所指向的流.执行成功时,它会返回c;执行失败时,它会返回EOF并且为errno设定适当的值.
fputc()的用法很简单:
if(fputc('p', stream) == EOF) /* 错误 */
将字符p写入stream(此流必须被打开以备写入).
写入一个字符串
fputs()可用于将一整个字符串写入所指定的流:
#include <stdio.h> int fputs(const char *str, FILE *stream);
fputs() 会将参数str所执行的以null分割的字符串全部写入参数stream所指向的流,执行成功返回一个非负值;失败返回EOF.
FILE *stream; stream = fopen("journal.txt", "a"); if (!stream) /* 错误 */ if (fputs("the ship is made of wood.\n", stream) == EOF) /* 错误 */ if (fclose(stream) == EOF) /* 错误 */
写入二进制数据
当程序需要写入复杂的数据结构时,使用个别的字符以及输入行是无法妥善处理的.要直接存储二进制数据,例如C变量,可以使用标准I/O链接库提供的fwrite():
#include <stdio.h> size_t fwrite(void *buf, size_t size, size_t nr, FILE *stream);
fwrite() 可将参数buf所指向的数据写入参数stream所指向的流,所写入的字节数目有size*nr来决定(数据中共有nr个元素,每个元素有size个字节).文件指针会前进实际所写入的字节数目.
执行成功时,fwrite()会返回所写入的元素数目;执行失败返回一个小于nr的值.
使用缓冲式I/O的简单程序
#include <stdio.h> int main(void) { FILE *in, *out; struct pirate { char name[100]; /*真实姓名*/ unsigned long booty; /* 以英镑为单位 */ unsigned int beard_len; /* 以英寸为单位 */ }p, blackbeard = { "Edward Teach", 950, 48}; out = fopen("data", "w"); if (!out) { perror("fopen"); return 1; } if (!fwrite(&blackbeard, sizeof (struct pirate), 1, out)) { perror ("fwrite"); return 1; } if (fclose(out)) { perror ("fclose"); return 1; } in = fopen("data", "r"); if (!in) { perror("fopen"); return 1; } if (!fread(&p, sizeof (struct pirate), 1, in)) { perror ("fread"); return 1; } if (fclose(in)) { perror ("fclose"); return 1; } printf("name=\"%s\" booty=%lu beard_len=%u\n", p.name, p.booty, p.beard_len); return 0; }