发现自己在写代码的时候老是要去重复的查询各种I/O函数的原型,在这里把unix环境下经常用到的I/O函数进行总结
unix环境下I/O函数分三个级别,第一级别是最底层的brk()与sbrk()函数,用的较少,这里不做多的讨论;
第二级别的是linux提供的系统调用,操作主要面向文件描述符,如open(),read(),write(),close()函数,不带缓冲;包含头文件为<fcntl.h>与<unistd.h>
int open(const char* path,int oflag, ... /*mode_t mode*/);成功返回文件描述符,失败返回-1。
在系统中定义了很多oflag常量,常用的有O_CREAT,O_RDONLY,O_WRONLY,O_RDWR,O_EXEC,O_APPEND,O_EXCL等等,其中需要注意的是O_CREAT|O_EXCL这个组合,表示如果文件已经存在,则出错,不存在则创建新的文件,这个主要非常常用。
ssize_t read(int fd,void* buf,size_t nbytes);返回成功读取的字节数,到达文件尾返回0,失败返回-1。
ssize_t write(int fd,void* buf,size_t nbytes);返回成功写入的字节数,到达文件尾返回0,失败返回-1。
第三个级别便是c语言自带的标准I/O库,而标准库操作面向流。我认为流就是经过包装的文件描述符,带有缓冲区,等等。通常采用read()和write()函数来实现,带缓冲,分为全缓冲,行缓冲;通常每次标准库刷新缓冲区的时候都会调用一次write()邓处理数据。
标准IO库分为每次一个字符的I/O,主要函数:
输入:
int getc(FILE* fp); int fgetc(FILE* fp); int getchar(); 函数若成功,把下一个字符转换为int并返回, 若失败则返回EOF。这里需要特别说明下的是GETC()通常被实现为宏,则他的速度更快。
这里为了区别是错误返回还是到达文件末尾,提供了两个函数: int ferror(FILE* fp) 和 int feof(FILE* fp),条件真返回非0,else返回0。
同时标准库还提供了函数int ungetc(int c,FILE* fp) 可以将从文件流中读取的字符重新押送回文件流中。该函数在我们需要取出一个字符判断是否有用,再对其进行处理的时候非常方便。
同样的与输入相对应的有3个字符输出函数:int putc(int c, FILE* fp)(通常采用宏实现), int fputc(int c, FILE* fp), int putchar()。
第二种则是每次一行I/O:
输入: char* fgets(char* buf,int n,FILE* fp); char* gets(char* buf); gets()容易产生缓冲区溢出,通常不推荐使用。成功函数返回buf, 失败或者达到尾端返回NULL。
fgets以换行符标识结尾,最多读入n-1个字符,并且在末尾加上null。
输出: int* fputs(char* str,FILE* fp); int puts(const char* str)
fputs将一个以null结尾的字符写入文件流,不包括结尾的NULL,需要注意的是puts函数通常会在函数末尾多加一个额外的换行符。
第三种则是二进制I/O:
size_t fread(void* ptr,size_t size,size nobj,FILE* fp);
size_t fwrite(void* ptr,size_t size,size_t nobj,FILE* fp);
这两个函数的返回值都是成功读写的对象的个数,比如一个结构体等等。通常如果返回值小于nobj,则读写错误。
通常输出size_t 格式的数据要用%zd。
读写二进制数据可能存在不同系统之间的数据不兼容的情况,如系统的大端小端存储问题等等。
第四种是格式化I/O:
这边是最常用的prinf与scanf()系列函数;这里我主要介绍:
int fprintf(FILE *fp, const char* format, ...);
int fdprintf(int fd,const char* format);
int sprintf(char* buf,const char* format, ... );
int snprintf(char* buf,size_t n,const char* format);
这些函数中,sprintf()同样由于缓冲区溢出的危险被弃用,推荐使用snprintf()。需要注意的是printf函数是有返回值的,返回成功操作的字符数,或负值(失败)。这里有一点我常搞混,就是这fprintf()的参数,FILE指针通常是放在第一个,而fread()与fwrite()函数则把函数指针放在最后,比较蛋疼,老是记不清。
同时,还有比较繁琐的就是关于printf的个是控制符,需要知道printf()默认右对齐,在类型标识符前有4个控制字段%[flags][fldwidth][precision][lendmodifier]convtype具体比较难说,可以查阅相关文档。
int fscanf(FILE* fp, const char* format, ... );
int sscanf(const char* buf, const char* format, ... );同样需要注意返回值,成功返回输入的项数,失败返回EOF。
这里关于scanf()有一个个人觉得比较重要的应用就是清楚输入缓冲区。scanf("%*[]");这里'*'表示抑制转换,即接受输入,但是并不对输入的数据赋值到任何参数。[]表示接受匹配括号内部的正则表达式的任何值。
接下来比较重要的还有关于文件偏移量的一些函数,下篇文章介绍。