流是磁盘或其他外围设备中存储的数据的源点或终点。尽管在一些系统中(如在著名的UNIX系统中),文本流和二进制流是没有差别的,但是标准库中还是提供了这两种流。
文本流是由文本行组成的序列,每一行有 0个或多个字符并以 \n
结尾。在某些环境中也许需要把文本流转换成其他表示形式(例如把 \n
映射成回车符和换行符)或从其他表示形式转换过来。二进制流是未经处理的字节组成的序列,这些字节记录着内部数据,具有如下性质:如果在同一系统中把写出的二进制流再读入进来,读入的和写出的内容是完全相同的。
通过打开一个流可以将该流与一个文件或设备关联起来,这一关联可以通过关闭流而终止。打开一个文件将返回一个指向 FILE 类型对象的指针,该指针中记录有用于控制该流的所有必要的信息。在不会引起歧义性的情况下,我们在下文中将不再区分“文件指针”和“流”。
在程序开始执行时,stdin、stdout 和 stderr 这三个流已经被打开。
printf 类函数用于提供格式化的输出转换。
格式化字符串由两种类型的对象组成:普通字符(它们被拷贝到输出流)与转换规格说明(它们决定参数的转换和输出格式)。每个转换规格说明均以字符 %
开头,以转换字符结束。
选项 | 描述 | 举例 |
---|---|---|
# | 八进制前面加0(转换字符为o),十六进制前面加0x(转换字符为x)或0X(转换字符为X)。 | printf("%#x", 0xff) 打印0xff ,printf("%x", 0xff) 打印ff 。 |
- | 格式化后的内容居左,右边可以留空格。 | 见下面的例子 |
宽度 | 用一个整数指定格式化后的最小长度,如果格式化后的内容没有这么长,可以在左边留空格,如果前面指定了-号就在右边留空格。宽度有一种特别的形式,不指定整数值而是写成一个 * 号,表示取一个int型参数作为宽度。 | printf("-%10s-", "hello") 打印-␣␣␣␣␣hello- ,printf("-%-*s-", 10, "hello") 打印-hello␣␣␣␣␣- 。 |
. | 用于分隔上一条提到的最小长度和下一条要讲的精度。 | 见下面的例子 |
精度 | 用一个整数表示精度。精度也可以不指定整数值而是写成一个 * 号,表示取下一个int型参数作为精度。 | printf("%.4s", "hello") 打印hell ,printf("-%6.4d-", 100) 打印-␣␣0100- ,printf("-%*.*f-", 8, 4, 3.14) 打印-␣␣3.1400- 。 |
字长 | 对于整型参数,hh、h、l、ll分别表示是char、short、long、long long型的字长,至于是有符号数还是无符号数则取决于转换字符;对于浮点型参数,L表示long double型的字长。 | printf("%hhd", 255) 打印-1 。 |
在 %
与转换字符这二者之间依次可以有以下转换字符:
格式码 | 参数类型:转换效果 |
---|---|
d , i |
int: 有符号十进制表示法 |
o |
int;无符号八进制表示法(无前导 0) |
x , X |
int;无符号十六进制表示法(无前导 0 X和0x),对0x用abcdef,对0X用ABCDEF |
u |
int;无符号十进制表示法 |
c |
int;单个字符,转换为 unsigned char类型后 |
s |
char *;输出字符串直到遇到 \0 或者已达到由精度指定的字符数 |
f |
double;形如[-] mmm.ddd的十进制浮点数表示法,d的数目由精度确定。缺省精度为6位,精度为0时不输出小数点 |
e , E |
double;形如[-]m.d ddddde±xx或[-] m .dddd ddE±xx的十进制表示法。d的数目由精度确定,缺省精度为6位。精度为0时不输出小数点 |
g , G |
double;当指数值小于-4或大于等于精度时,采用 %e 或%E 格式;否则采用 %f 的格式。尾部的0与小数点不打印 |
p |
void *;输出指针值(具体表示与实现有关) |
n |
int *;到目前为止以此格式调用 printf 输出的字符的数目将被写入到相应参数中。 |
% |
不进行参数转换;输出符号 % |
fprintf函数用于按照 format 说明的格式把参数列表中参数的内容进行转换,并写入stream指向的流。返回值是实际写入的字符数。若出错则返回一个负值。
1 | int fprintf(FILE *stream, const char *format, ...) |
printf(...)
函数等价于 fprintf(stdout, ...)
1 | int printf(const char *format, ...) |
sprintf 函数与 printf 函数的功能基本相同,但输出到数组 s 中,并以 \0
结束。 s必须足够大,以便能装下输出结果。该函数返回实际输出的字符数,不包括 \0
。
1 | int sprintf(char *str, const char *format, ...); |
sprintf并不打印到文件,而是打印到用户提供的缓冲区str中并在末尾加’\0’,由于格式化后的字符串长度很难预计,所以很可能造成缓冲区溢出,用snprintf更好一些,参数size指定了缓冲区长度,如果格式化后的字符串长度超过缓冲区长度,snprintf就把字符串截断到size-1字节,再加上一个’\0’写入缓冲区,也就是说snprintf保证字符串以’\0’结尾。snprintf的返回值是格式化后的字符串长度(不包括结尾的’\0’),如果字符串被截断,返回的是截断之前的长度,把它和实际缓冲区中的字符串长度相比较就可以知道是否发生了截断。
1 | int snprintf(char *str, size_t size, const char *format, ...); |
vprintf、vfprintf 与 vsprintf 这几个函数与对应的 printf 函数等价,但参数列表由 arg 代替。 arg由宏 va_start
初始化,由 va_arg
调用。
1 | vprintf(const char *format, va_list arg) |
示例:实现格式化打印错误的err_sys函数
1 | #include <stdio.h> |
scanf 类函数用于提供格式化输入转换。
格式字符串 format 通常包含有用于指导输入转换的转换规格说明。格式字符串中可以包含:
转换规格说明决定了输入字段的转换方式。通常把结果保存在由对应参数指向的变量中。然而,如果转换规格说明中包含有赋值屏蔽字符“*”,例如% *s
,那么就跳过对应的输入字段,不进行赋值。输入字段是一个由非空白符组成的字符串,当遇到空白符或到达最大字段宽(如果有的话)时,对输入字段的读入结束。这意味着 scanf 函数可以跨越行的界限来读入其输入,因为换行符也是空白符(空白符包括空格、横向制表符、纵向制表符、换行符、回车符和换页符)。
转换字符说明了输入字段的解释含义,对应的参数必须是指针。合法的转换符如下表所示。
字符 | 输入数据;参数类型 |
---|---|
d |
十进制整数; int * |
i |
整数;int * 。该整数可以是以 0开头的八进制数,也可以是以 0x 或 0X 开头的十六进制数 |
o |
八进制整数(可以带或不带前导 0);int * |
u |
无符号十进制整数; unsigned int * |
x |
十六进制整数(可以带或不带前导 0x 或 0X);int * |
c |
字符;char * 。按照字段宽的大小把读入的字符保存在指定的数组中,不加入字符 \0 。字段宽的缺省值为 1。在这种情况下,不跳过空白符 ;如果要读入下一个非空白符,使用 %1s |
s |
由非空白符组成的字符串(不包含引号); char*。该参数指针指向一个字符数组,该字符数组有足够空间来保存该字符串以及在末尾添加的 \0 |
e 、f 、g |
浮点数;float *。float 浮点数的输入格式为:一个任选的正负号,一串可能包含小数点的数字和一个任选的指数字段。指数字段由字母 e 或 E 以及后跟的一个可能带正负号的整数组成 |
p |
用printf("%p") 函数调用输出的指针值; void * |
n |
将到目前为止此调用所读的字符数写入参数; int * 。 |
[...] |
不读入输入字符。不增加转换项目计数用方括号括起的字符集中的字符来匹配输入,以找到最长的非空字符串; char *。在末尾添加字符\0 。格式 []...] 表示字符集中包含字符 ] |
[^...] |
用不在方括号括起的字符集中的字符来匹配输入,以找到最长的非空字符串; char *。在末尾添加字符\0 。格式 [ ^ ]. ..] 表示字符集中包含字符 ] |
% |
字面值%,不进行赋值 |
1 | int fscanf(FILE *stream, const char *format, ...) |
fscanf 函数用于在格式串 format 的控制下从流stream中读入字符,把转换后的值赋给后续各个参数,在此每一个参数都必须是一个指针。当格式串 format 结束时函数返回。如果到达文件的末尾或在参数被转换前出错,那么该函数返回 EOF;否则返回实际被转换并赋值的输入项的数目。
scanf(...)
函数等价于fscanf(stdin, ...)
1 | int scanf(const char* format, ...) |
1 |
|
sscanf(s, ...)
函数与scanf (... )
等价,不同的是前者的输入字符来源于 s。
1 | int sscanf(char *s,const *format, ... ) |
1 | FILE *fopen(const char *filename, const char *mode) |
fopen函数用于打开以filename所指内容为名字的文件,返回与之相关联的流。如果打开操作失败,那么返回 NULL。
访问方式mode的合法值如下:
值 | 用途 |
---|---|
“r” | 打开文本文件用于读。 |
“w” | 创建文本文件用于写,并删除已存在的内容(如果有的话)。 |
“a” | 添加;打开或创建文本文件用于在文件末尾写。 |
“r+” | 打开文本文件用于更新(即读和写)。 |
“w+” | 创建文本文件用于更新,并删除已存在的内容(如果有的话)。 |
“a+” | 添加、打开或创建文本文件用于更新和在文件末尾写。 |
后三种方式允许对同一文件进行读和写。在读和写的交替过程中,必须调用 fflush 函数或文件定位函数。如果方式值在上述字符之后再加上 “b” ,如 “rb” 或 "w+b "等,那么表示文件是二进制文件。文件名 filename 长度的最大值为 FILENAME_MAX
个字符。一次最多可打开 FOPEN_MAX
个文件。
在打开一个文件时如果出错,fopen将返回NULL并设置errno,errno稍后介绍。在程序中应该做出错处理,通常这样写:
1 | if ( (fp = fopen("/tmp/file1", "r")) == NULL) { |
1 | FILE *freopen(const char *filename, const char *mode, FILE *stream) |
freopen 函数用于以参数 mode 指定的方式打开参数 filename 指定的文件,并使该文件与参数 stream 指定的流相关联。它返回指向 stream 的指针;若出错则返回 NULL。freopen 函数一般用于 stdin、stdout 和 stderr 等标准流的重定向。
用户程序调用fputc通常只是写到I/O缓冲区中,这样fputc函数可以很快地返回,如果I/O缓冲区写满了,fputc就通过系统调用把I/O缓冲区中的数据传给内核,内核最终把数据写回磁盘。有时候用户程序希望把I/O缓冲区中的数据立刻传给内核,让内核写回设备,这称为Flush操作,对应的库函数是fflush,fclose函数在关闭文件之前也会做Flush操作。
1 | int fflush(FILE *stream) |
对输出流, fflush 函数用于将已写到缓冲区但尚未写出的全部数据都写到文件中。对输入流,其结果是未定义的。如果写的过程中发生错误则返回 EOF ,否则返回 0。fflush(NULL)函数用于刷新所有的输出流。
C标准库的I/O缓冲区有三种类型:全缓冲、行缓冲和无缓冲。当用户程序调用库函数做写操作时,不同类型的缓冲区具有不同的特性。
1 | int fclose(FILE *stream) |
fclose函数用于刷新 stream 的全部未写出数据,丢弃任何未读的缓冲区内的输入数据并释放自动分配的缓冲区,最后关闭流。若出错则返回 EOF,否则返回 0。
fgetc 函数用于以 unsigned char 类型返回 stream 流中的下一个字符(转换为 int 类型)。如果到达文件的末尾或发生错误,则返回 EOF。
1 | int fgetc(FILE *stream) |
对于fgetc函数的使用有以下几点说明:
fputc函数用于把字符 c(转换为 unsigned char类型)输出到流 stream 中。它返回所写的字符,若出错则返回EOF。
1 | int fputc(int c, FILE *stream) |
对于fputc函数的使用也要说明几点:
getc函数等价于 fgetc,不同之处为:当 getc 函数被定义为宏时,它可能多次计算 stream 的值。
1 | int getc(FILE *stream) |
getchar函数等价于 getc(stdin)。
1 | int getchar(void) |
putc 函数等价于 fputc,不同之处为:当 putc 函数被定义为宏时,它可能多次计算 stream 的值。
1 | int putc(int c, FILE *stream) |
putchar© 函数等价于 putc(c, stdout)。
1 | int putchar(int c) |
ungetc用于把字符 c(转换为 unsigned char类型)写回到流 stream 中,下次对该流进行读操作时,将返回该字符。对每个流只能写回一个字符,且此字符不能是 EOF。ungetc函数返回被写回的字符;如果发生错误,那么它返回 EOF。
1 | int ungetc(int c, FILE *stream) |
fgets函数用于读入最多 n-1 个字符到数组 s 中。当遇到换行符 \n
时,把换行符保留在数组s中,读入不再进行。数组 s以 \0
结尾。 fgets 函数返回数组 s,如果到达文件的末尾或发生错误,则返回 NULL。
1 | char *fgets(char *s, int n, FILE *stream) |
\n
是一个特别的字符,而 \0
并无任何特别之处,如果读到 \0
就当作普通字符读入。如果文件中存在 \0
字符(或者说0x00字节),调用fgets之后就无法判断缓冲区中的 \0
究竟是从文件读上来的字符还是由fgets自动添加的结束符,所以fgets只适合读文本文件而不适合读二进制文件,并且文本文件中的所有字符都应该是可见字符,不能有 \0
。
fputs函数用于把字符串 s(不包含字符 \n
)输出到流 stream 中;它返回一个非负值,若出错则返回EOF。
1 | int fputs(const char *s, FILE *stream) |
gets函数用于把下一个字符串读入到数组 s 中,并把读入的换行符替换为字符 \0
。它返回数组s,如果到达文件的末尾或发生错误则返回 NULL。
1 | char *gets(char *s) |
puts函数用于把字符串 s 和一个换行符输出到 stdout。如果发生错误,那么它返回 EOF;否则返回一个非负值。
1 | int puts(const char *s) |
fread函数用于从输入流 stream 中读入最多 nobj 个长度为 size 的对象到 ptr 指向的数组中,并返回所读入的对象数,此返回值可能小于 nobj。实际执行状态可用函数 feof 或 ferror 得到。
1 | size_t fread(void *ptr, size_t size, size_t nobj, FILE *stream) |
fwrite 函数用于把 ptr 所指向的数组中 nobj 个长度为 size 的对象输出到流 stream 中,并返回被输出的对象数。如果发生错误,则返回一个小于 nobj 的值。
1 | size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *stream) |
1 | /* writerec.c */ |
1 | /* readrec.c */ |
1 | $ gcc writerec.c -o writerec |
1 | int fseek(FILE *stream, long offset, int whence) |
fseek函数用于给与stream流相关的文件定位,随后的读写操作将从新位置开始。对于二进制文件,此位置被定位在由 whence 开始的 offset 个字符处。 whence的值可能为:
SEEK_SET
:文件开始处SEEK_CUR
:当前位置SEEK_END
:文件结束处。offset可正可负,负值表示向前(向文件开头的方向)移动,正值表示向后(向文件末尾的方向)移动,如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸,从原来的文件末尾到fseek移动之后的读写位置之间的字节都是0。
1 |
|
1 | long ftell(FILE *stream) |
ftell 函数用于返回与 stream 流相关的文件的当前位置。在出错时返回 -1L。
rewind(fp)函数等价于 fseek(fp,0L,SEEK_SET) 与 clearerr(fp) 这两个函数顺序执行的效果。
1 | void rewind(FILE *stream) |
1 | int fgetpos(FILE *stream, fpos_t *ptr) |
fgetpos函数用于把 stream流的当前位置记录在 *ptr中,供随后的 fsetpos 函数调用时使用。若出错则返回一个非 0 值。
fsetpos函数用于将流 stream 的位置定位在 ptr 所指向的位置。 * ptr的值是在函数 fgetpos 调用时记录下来的。若出错则返回一个非 0 值。
1 | int fsetpos(FILE *stream, const fpos_t *ptr) |
当发生错误或到达文件末尾时,标准库中的许多函数将设置状态指示符。这些状态指示符可被显式地设置和测试。另外,很多系统函数在错误返回时将错误原因记录在libc定义的全局变量errno中,每种错误原因对应一个错误码。请查阅errno(3)的Man Page了解各种错误码,errno在头文件errno.h中声明,是一个整型变量,所有错误码都是正整数。
clearerr 函数用于清除与流 stream 相关的文件结束指示符和错误指示符。
1 | void clearerr(FILE *stream) |
feof函数用于在与stream流相关的文件结束指示符被设置时返回一个非0值。
1 | int feof(FILE *stream) |
ferror函数用于在与stream相关的文件出错指示符被设置时返回一个非0值。
1 | int ferror(FILE *stream) |
perror(s) 函数用于输出字符串 s 以及与全局变量 errno 中的整数值相对应的出错信息,具体出错信息的内容依赖于实现。该函数的功能类似于 fprintf(stderr, "%s: %s\n", s, "出错信息")
1 | void perror(const char *s) |
如果在程序中打印错误信息时直接打印errno变量,打印出来的只是一个整数值,仍然看不出是什么错误。比较好的办法是用perror或strerror函数将errno解释成字符串再打印。
1 |
|
如果文件abcde不存在,fopen返回-1并设置errno为ENOENT,紧接着perror函数读取errno的值,将ENOENT解释成字符串No such file or directory并打印,最后打印的结果是Open file abcde: No such file or directory。虽然perror可以打印出错误原因,传给perror的字符串参数仍然应该提供一些额外的信息,以便在看到错误信息时能够很快定位是程序中哪里出了错,如果在程序中有很多个fopen调用,每个fopen打开不同的文件,那么在每个fopen的错误处理中打印文件名就很有帮助。