1.流和FILE对象
之前对文件大的操作是针对文件描述符的针对流,而标准I/0库是针对流(stream)进行的,一个文件和一个流相关联。打开一个流时,标准I/O函数fopen返回一个指向FILE对象的指针(文件指针), 该结构包含了管理流所需的所有信息,包括文件描述符、指向缓冲区的指针、缓冲区的长度、当前缓冲区中的字符数以及出错标志等
2. 缓冲
标准I/O库提供缓冲的目的就是尽可能减少使用read和write调用的次数(可以用非标准I/0函数,来试试不同缓冲区长度下,执行I/0所需的CPU时间量,并进行自动管理。I/0库提供以下三种类型的缓冲。
- 全缓冲,I/0缓冲区满时才进行实际的I/O操作,比如文件就是由标准I/O实施全缓冲的。
- 行缓冲,输入输出遇到换行符时才进行I/O操作,如涉及到终端时(标准输入,输出),通常使用行缓冲,两个限制(1.行缓冲区的长度;2.)
- 不带缓冲,如strerr通常是不带缓冲的
3. 打开流
#include <stdio.h>
//打开指定文件
FILE *fopen(const char *path, const char *mode);
//从文件描述符中获取对应文件指针,常用语由创建管道或网络通信通道函数返回的描述符
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *stream);
4. 读和写流
- 每次读写一个字符的I/O
- 每次一行的I/O,如fgets和fputs,
- 直接I/O,fread和fwrite函数支持这种类型的I/O,每次I/O操作都操作某个对象。
以下三个函数一次读一个字符
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void); //等价于getc(stdin)
/*
成功返回下一个字符,若已到达文件结尾或出错则返回EOF
三个函数在返回下一个字符时,会将其unsigned char类型转换为int类型
*/
不管是出错还是到达文件尾端,三个函数都返回同样的值,为了区分情况,必须调用ferror或feof
#include <stdio.h>
int feof(FILE *stream);
int ferror(FILE *stream);
/*以上两个函数返回值:若条件为真则返回非0值,否则返回0*/
void clearerr(FILE *stream);
int ungetc(int c, FILE *stream); //成功返回0,失败出错返回EOF
一般实现中,为每个流在FILE对象中维持了两个标志:出错标志和文件结束标志;调用clearerr清除这两个标志,可以调用ungetc函数将字符压回流,缓冲区中(不能压回EOF),但读出的顺序与压送回的顺序相反。
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);//等效于putc(c,stdout)
/*成功返回c,失败返回EOF*/
5. 每次一行I/O
读一行
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream); //遇到换行符或Null就终止,从指定流读,把换行符放入s中
char *gets(char *s); //从标准输入读,不把换行符放入s中
/*成功返回buf,到达文件尾或出错失败返回NULL*/
gets是一个不推荐的函数,问题在于使用gets时不能指定缓冲区的长度,可能造成缓冲区溢出
输出一行
#include <stdio.h>
int fputs(const char *s, FILE *stream); //遇到换行符或null就终止
int puts(const char *s); //会输出换行符
/*成功返回buf,到达文件尾或出错失败返回NULL*/
若使用fgets和fputs,往往需要自己处理换行符
6. 标准I/O的效率
例子:将标准输入复制到标准输出
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(void)
{
int c;
while ((c=getc(stdin)) != EOF)
if (putc(c,stdout) == EOF)
{
perror("output error");
exit(0);
}
if (ferror(stdin))
{
perror("input error");
exit(0);
}
return 0;
}
7. 二进制I/O
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
/*fread或fwrite返回读或写的对象数*/
常有以下两种用法
读或写一个数组,如将第2~5个元素写至一个文件上
float data[10];
if (fread(&data[2],sizeof(float),4,fp) != 4)
err_sys("fwrite error");
读或写一个结构,其中指定size为结构的长度,nobj为1(要写的对象数)
struct
{
short cnt;
long total;
char name[NAMESIZE];
}item;
if (fwrite(&item,sizeof(item),1,fp) != 1)
err_sys("fwrite error");
结合起来就可以读一个结构体数组,这时size是结构体的sizeof,nobj是数组元素。
8. 定位流
#include <stdio.h>
//成功返回0,出错返回-1,offset和whence的和lseek相同
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream); //若成功返回当前文件位置指示,出错返回-1
void rewind(FILE *stream);
9. 格式化I/0
格式化输出
#include <stdio.h>
/*printf和fprintf两个函数返回值:成功返回输出字符数,出错返回负值*/
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
/*成功返回存入数组的字符数,出错返回负值*/
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
printf将格式化数据写到标准输出,fprintf将写至指定的流,sprintf将格式化的字符送入数组buf中,sprintf在该数组的尾段自动加一个null字节,但该字节不包含在返回值中;sprintf可能造成buf指向的缓冲区溢出。
格式化输入
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
10. 临时文件
#include <stdio.h>
char *tmpnam(char *s); //返回值:指向唯一路径名的指针
FILE *tmpfile(void); //成功返回文件指针,失败返回NULL
tmpfile函数经常调用tmpnam产生一个唯一的路径名,然后,用该路径名创建唯一一个文件,并立即unlink它(对一个文件解除链接并不会删除其内容,关闭该文件时才删除其内容)。
例子
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(void)
{
FILE *fp;
char line[100];
if ((fp=tmpfile()) == NULL)
{
perror("tmpfile");
exit(1);
}
fputs("one line of output\n",fp);
rewind(fp);
if (fgets(line,sizeof(line),fp) == NULL)
{
perror("fgets error");
exit(1);
}
fputs(line,stdout);
exit(0);
}

被折叠的 条评论
为什么被折叠?



