流和FILE对象
对于标准I/O库,它们的操作都是围绕流(stream)进行的,当用标准I/O库打开或创建一个文件时,我们已使一个流与一个文件相关联。
当一个流最初被创建时,它并没有定向,如果在未定向的流上使用一个多字节I/O函数,则将该流的定向设置为宽定向的。如果在未定向的流上使用一个单字节I/O函数,则将该流的定向设置为字节定向的。
freopen函数清除一个流的定向。
fwide函数设置流的定向。
fwide
如何流式宽定向的则返回正值,若流式字节定向的则返回负值,若流未定向返回0。
mode参数值为负:fwide将试图使指定的流是字节定向的。
mode参数值为正:fwide将试图使指定的流是宽定向的。
mode参数值为0:fwide将不试图设置流的定向,但返回标识该流定向的值。
#include <wchar.h>
int fwide(FILE *stream, int mode);
缓冲
标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数,它也对每个I/O流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。
全缓冲
这种情况下,在填满标准I/O缓冲区后才进行实际I/O操作,对于驻留在磁盘上的文件通常是由标准I/O库实施全缓冲的。在一个流上执行第一次I/O操作时,相关标准I/O函数通常调用malloc获得需使用的缓冲区。
冲洗(flush)说明标准I/O库的写操作,缓冲区可由标准I/O例程自动冲洗,或者可以调用函数fflush冲洗一个流。值得引起注意的是在UNIX环境下,flush有两种意思,在标准I/O库方面,flush意味着将缓冲区中的内容写到磁盘上,在终端驱动程序方面,flush表示丢弃已存储在缓冲区中的数据。
行缓冲
在这种情况下,当在输入和输出中遇到换行符时,标准I/O库执行I/O操作,这允许我们一次输出一个字符,但只有在写了一行之后才进行实际I/O操作,当流涉及一个终端时,通常使用行缓冲。
不带缓冲
标准I/O库不对字符进行缓冲存储。
标准出错流stderr通常是不带缓冲的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个换行符。
很多系统默认使用下列类型的缓冲:
- 标准出错是不带缓冲的;
- 如果是涉及终端设备的其他流,则它们是行缓冲的,否则是全缓冲的。
如果想要更改缓冲类型可以通过下面两个方法。如果成功返回0,失败返回非0。
#include <stdio.h>
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
可以使用setbuf函数打开或关闭缓冲机制,为了带缓冲进行I/O操作,参数buf必须指向一个长度为BUFSIZ的缓冲区(该常量在stdio.h中定义),通常在此之后该流就是全缓冲的,但是如果该流与一个终端设备相关,那么某些系统也可以将其设置为行缓冲。为了关闭缓冲,将buf设置为NULL。
使用setvbuf我们可以精确的指定所需的缓冲类型,mode常量(该常量在stdio.h中定义)。
- _IOFBF:全缓冲
- _IOLBF:行缓冲
- _IONBF:不带缓冲
使用fflush函数来强制冲洗一个流。
flush有两种意思
- 在标准I/O库方面,flush意味着将缓冲区中的内容写到磁盘上;‘
- 在终端驱动程序方面,flush表示丢弃已存储在缓冲区中的数据。
#include <stdio.h>
int fflush(FILE *stream);
打开流
#include <stdio.h>
//打开文件,并返回文件指针
FILE *fopen(const char *pathname, const char *mode);
//打开文件,并返回文件指针
FILE *fdopen(int fd, const char *mode);
//清除一个流的定向
FILE *freopen(const char *pathname, const char *mode, FILE *stream);
- fopen打开一个指定的文件;
- freopen在一个指定的流上打开一个指定的文件,若该流已经打开,则先关闭该流。若该流已经定向,则freopen清除该定向。此函数一般用于将一个指定的文件打开为一个预定义的流,标准输入、标准输出或标准出错。
- fdopen获取一个现有的文件描述符,并使一个标准I/O流与该描述符相结合。此函数常用于创建管道和网络通信管道函数返回的描述符。因为这些特殊类型的文件不能用标准I/Ofopen函数打开。
打开标准I/O流的mode参数
- r或rb:为读而打开
- w或wb:将文件截短至0长,或为写而创建
- a或ab:添加,为在文件尾写而打开,或为写而创建
- r+或r+b或rb+:为读和写而打开
- w+或w+b或wb+:把文件截短至0长,或为读和写而打开
- a+或a+b或ab+:为在文件尾读和写而打开或创建
// 打开文件,并返回文件指针
FILE *fp = fopen(F_PATH, "r");
调用close关闭一个打开的流。
#include <unistd.h>
int close(int fd);
在该文件被关闭之前,冲洗缓冲区中的输出数据,丢弃缓冲区中的任何输入数据,如果标准I/O库已经为该流自动分配了一个缓冲区,则释放此缓冲区。
当一个进程正常终止时,则所有带未写缓冲数据的标准I/O都会被冲洗,所有打开的标准I/O都会被关闭。
读和写流
每次一个字符I/O
输入函数
下面三个函数可用于一次读一个字符。若成功返回下一个字符,若已到达文件结尾或出错则返回EOF(-1)。
这三个函数返回下一个字符的时候,会将unsigned char类型转换为int类型。
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
getc可被实现为宏,fgetc则不能实现为宏。
getc的参数不应当时具有副作用的表达式。
因为fgetc一定是一个函数,所以可以得到其地址,这就允许将fgetc的地址作为一个参数传送给另一个函数。
调用fgetc所需的时间很可能长于调用getc,因为函数调用所需的时间通常长于调用宏的时间。
不管是出错还是到达文件尾端,这三个函数都返回同样的值,为了区分这两种不同的情况,必须调用ferror或feof函数;
ferror和feof函数返回值,若条件为真则返回非0值,若条件为假,返回0。
#include <stdio.h>
void clearerr(FILE *stream);
//
int feof(FILE *stream);
int ferror(FILE *stream);
代码示例
#include<stdio.h>
int main(void)
{
FILE *p;
p = fopen("open.txt", "r");
getc(p);
if (feof(p))
{
printf("文件为空。");
}
else
{
rewind(p);//将光标跳回到文件开头
int a;
fscanf(p,"%d",&a);
printf("%d", a);
}
return 0;
}
输出函数
每个输入函数都有一个输出函数。与输入函数一样,putchar©等效于putc(c,stdout),putc可实现为宏,而fputc不能实现为宏。
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
每次一行I/O
输入函数
下面两个函数提供每次输入一行的功能,成功返回buf,若已达到文件结尾或者出错则返回NULL。
#include <stdio.h>
char *gets(char *s);
//size 读取的字节数,实际上读取的字节数为size-1
char *fgets(char *s, int size, FILE *stream);
不推荐使用gets,因为它不能指定缓冲区的长度,容易造成缓冲区溢出。
fgets必须指定缓冲区的长度size,此函数一直读到下一个换行符为止,但是不超过size-1,读入的字符被送入缓冲区,该缓冲区以null字符结尾。如果该行的字符数超过size-1,则fgets只返回一个不完整的行,但是缓冲区总是以null字符结尾。,对fgets的下一次调用会继续读该行。
输出函数
#include <stdio.h>
int fputs(const char *s, FILE *stream);
int puts(const char *s);
fputs将一个以null符终止的字符串写到指定的流,尾端的终止符null不写出。这并不一定是每次输出一行,因为它并不要求在null符之前一定是换行符,通常在null符之前是一个换行符,但并不要求总是如此。
puts将一个以null符终止的字符串写到标准输出,终止符不写出,但是puts然后又将一个换行符写到标准输出。
代码示例
#include <stdio.h>
#define MAXLINE 4096
void main(int argc, char *argv[])
{
char buf[MAXLINE];
while (fgets(buf, MAXLINE, stdin) != NULL)
{
if (fputs(buf, stdout) == EOF)
{
printf("output error");
}
}
if (ferror(stdin))
{
printf("input error");
}
return;
}
二进制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);
常见用法
- 读或写一个二进制数组,size为每个数组元素的长度,nmemb为要写的元素数。
- 读或写一个结构,size为结构的长度,nmemb为要写的对象数
#include <stdio.h>
struct
{
short count;
long total;
char name[100];
} item;
void main(int argc, char *argv[])
{
float data[10];
FILE *fp = open("open.txt", "r");
//写一个二进制数组
if (fwrite(&data[2], sizeof(float), 4, fp) != 4)
{
printf("error");
}
//写一个结构
if (fwrite(&item, sizeof(item), 1, fp) != 1)
{
printf("error");
}
return;
}
二进制I/O的基本问题是,它只能用于读在同一个系统上已写的数据,很多异构系统通过网络相互连接,常常在一个系统上写的数据,要在另一个系统上进行处理,在这种情况下两个函数可能就不能正常工作。
- 在一个结构中,同一成员的偏移量可能因编译器和系统而异。
- 用来存储多字节整数和浮点值的二进制格式在不同的机器体系结构间也可能不同。
定位流
#include <stdio.h>
//成功返回0,出错返回非0值
int fseek(FILE *stream, long offset, int whence);
//若成功返回当前文件位置指示,出错返回-1L
long ftell(FILE *stream);
//将一个流设置到文件的起始为止
void rewind(FILE *stream);
ftell用于二进制文件时,其返回值就是这种字节位置。
fseek必须指定一个字节offset以及解释这种偏移量的方式。
- 若whence是SEEK_SET(0),则将该文件的偏移量设置为距文件开始处offset个字节;
- 若whence是SEEK_CUR(1),则将该文件的偏移量设置为其当前值加offset,offset可正可负;
- 若whence是SEEK_END(2),则将该文件的偏移量设置为文件长度加offset,offset可正可负;
为了定位一个文本文件,whence一定要是SEEK_SET,而且offset只能有两种值0或者对该文件调用ftell所返回的值。
ftello函数与ftell相同,fseeko与fseek相同。
#include <stdio.h>
//成功返回0,出错返回非0
int fseeko(FILE *stream, off_t offset, int whence);
//成功返回当前文件位置指示,出错返回-1
off_t ftello(FILE *stream);
fgetpos将文件位置指示器的当前值存入由pos指向的对象中,在以后调用fsetpos时,可以使用此值将流重新定位至该位置。成功返回0,失败返回非0。
#include <stdio.h>
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
格式化I/O
格式化输出
printf、fprintf成功返回输出字符数,输出错误返回负值。
sprintf、snprintf成功则返回存入数组str的字符数,编码出错则返回负值。
#include <stdio.h>
//将格式化数据写到标准输出
int printf(const char *format, ...);
//写至指定的流
int fprintf(FILE *stream, const char *format, ...);
//将格式化的字符送入数组str中
int sprintf(char *str, const char *format, ...);
//在该数组的尾端自动加一个null字节,但该字节不包括在返回值中
int snprintf(char *str, size_t size, const char *format, ...);
sprintf可能会造成由str指向的缓冲区的溢出,从而引出了snprintf函数,将缓冲区长度作为一个显示参数。
格式说明控制参数编写
%[flags][fldwidth][precision][lenmodifier]convtype
flags
- -:在字段内左对齐输出
- +:总是显示带符号转换的符号
- 空格:如果第一个字符不是符号,则在其前面加上一个空格
- #:指定另一中转换形式,例如十六进制 加0x前缀
- 0:添加前导0进行填充
fldwidth
说明转换的最小字段宽度,如果转换得到的字符较少,则用空格填充。
precision
说明整型转换后最少输出数字位数、浮点转换后小数点后的最少位数、字符串转换后的最大字符数。精度是一个句点(.),后接一个可选的非负十进制数或一个星号(*)。
lenmodifier
- hh:有符号或无符号的char
- h:有符号或无符号的short
- l:有符号或无符号的long或宽字符
- ll:有符号或无符号的long long
- j:intmax_*t或uintmax_*t
- z:size_t
- t:ptrdiff_t
- L:long double
convtype不是可选的
- d、i:无符号十进制
- o:无符号八进制
- u:无符号十进制
- x、X:无符号十六进制
- f、F:double精度浮点数
- e、E:指数格式的double精度浮点数
- g、G:解释为f、F、e、E取决于被转换的值
- a、A:十六进制指数格式的double精度浮点数
- c:字符
- s:字符串
- p:指向void的指针
- n:将到目前为止,所写的字符数写入到指针
- %:%字符
- C:宽字符
- S:宽字符
可变参数表换成了arg
#include <stdarg.h>
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
格式化输入
三个函数的返回值,指定的输入项数,若输入出错或在任意变换前已到达文件结尾则返回EOF。
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
scanf族用于分析输入字符串,并将字符序列转换成指定类型的变量,格式之后的各参数包含了变量的地址,以用转换结果初始化这些变量。
与printf族一样,scanf也支持函数使用由<stdarg.h>说明的可变参数表。
#include <stdarg.h>
int vscanf(const char *format, va_list ap);
int vsscanf(const char *str, const char *format, va_list ap);
int vfscanf(FILE *stream, const char *format, va_list ap);
临时文件
标准I/O库提供了两个函数以帮助创建临时文件。
#include <stdio.h>
//返回指向唯一路径名的指针
char *tmpnam(char *s);
//若成功返回文件指针,失败返回NULL
FILE *tmpfile(void);
tmpnam函数产生一个与现有文件名不同的一个有效路径名字符串,每次调用它时,它都产生一个不同的路径名,最多调用次数是TMP_MAX(在<stdio.h>中定义)。
tempnam是tmpnam的一个变体,它允许调用者为所产生的路径名指定目录和前缀。
- 如果定义了环境变量TMPDIR,则用其作为目录
- 如果参数directory非NULL,则用其作为目录
- 将<stdio.h>中的字符串P_tmpdir用作目录
- 将本地目录用作目录(通常为/tmp)
char *tempnam(const char *dir, const char *pfx);