对于关联到终端的输入流,如果读完了缓冲区中最后一个字节,并尝试继续读取,则程序阻塞,等待终端输入数据并刷新缓冲区。
格式化输入输出
关于格式控制的文档或者教程网络上随处可见,我就不再赘述,将直接跳过这些内容。
fprintf
int fprintf(FILE * restrict stream,
const char * restrict format, ...);
fprintf
函数把字符写入流中,数据由格式字符串及后面的参数提供。
函数返回实际写入的字符数,如果发生错误则返回一个负数。
如果所给参数多于格式控制需要的参数,则多余的参数会被忽略。如果少于格式控制需要的参数,行为是未定义的。
fprintf
是一个可变参数函数。对于参数列表以...
结尾的可变参数函数,或者以传统方式申明而不是以函数原型申明的函数,调用函数时存在默认参数提升。所有长度小于int
的整数都被提升为int
,所有的float
都被提升成double
。输出整数或浮点数时,函数传值而不是传地址,再经过参数提升,不需要关心原类型的长度。这就是为什么float
和double
都使用%f
格式,而char
、short
等都可以使用%d
格式,连警告都不会有。
fscanf
int fscanf(FILE * restrict stream,
const char * restrict format, ...);
fscanf
从流中读取字符,并根据格式控制把转换后的数据写入后面参数指向的位置。
函数返回成功写入的参数个数,可能为0或任意正整数。如果在成功读取任何字符前直接遇到文件末尾或错误,返回EOF。
如果所给参数多于格式控制需要的参数,则多余的参数会被忽略。如果少于格式控制需要的参数,行为是未定义的。
格式字符串后面传递的参数应该都为指针,函数处理时会把它们的值都当作指针对待。从流中读取数据存在一个解析的过程,解析按流方向依次进行,如果输入的内容符合格式控制,满足写入条件时就把字符转换成对应数据写入参数指向的位置;如果从某个字符开始不符合格式控制,则解析失败,函数终止,所有已经写入的数据会被保留,未处理的参数会被忽略。解析失败不会产生错误,文件位置指示器会被置于解析失败的字符处。
fscanf
也是可变参数函数。但与fprintf
不同,fscanf
几乎只接受指针,默认参数提升对它几乎没有影响。如果要在某块内存写入数据,就必须明确数据类型与数据长度,所以fscanf
严格区分float
和double
,char
、short
、int
等也不能混用。
printf
int printf(const char * restrict format, ...);
printf
函数同fprintf
函数,只不过默认流是stdout。
scanf
int scanf(const char * restrict format, ...);
scanf
函数同fscanf
函数,只不过默认流是stdin。
snprintf
int snprintf(char * restrict s, size_t n,
const char * restrict format, ...);
snprintf
函数类似于fprintf
函数,但是数据不写入流,而是写入字符数组中。
s表示接受输出的数组,n表示数组大小。如果输出内容多于n-1个字节,则多余的内容会被丢弃。snprintf
会在写入数组的数据末尾加一个空字符'\0'
。
如果s是空指针或者n为0,则不会写入任何内容。
因为数组不带有缓冲区,所以写入的内容会立刻出现在数组中。
snprintf
函数返回实际写入的字符数(不包括末尾的空字符),如果发生错误返回一个负数。
sprintf
int sprintf(char * restrict s,
const char * restrict format, ...);
sprintf
函数类似于snprintf
函数,但没有数组大小限制。
sscanf
int sscanf(const char * restrict s,
const char * restrict format, ...);
sscanf
函数类似于fscanf
函数,但是不读取流,而是读取字符串。
sscanf
函数会把字符串末尾的空字符'\0'
当作文件末尾。由于字符串不是流,不存在文件位置指针或缓冲区,每次以相同方式读取同一个字符串的结果是相同的。
函数返回成功写入的参数个数。如果在成功读取任何字符前直接遇到空字符或错误,返回EOF。
字符输入输出
fputc
int fputc(int c, FILE *stream);
fputc
函数将一个字节写入流中。注意参数是int
类型,写入时会转换成unsigned char
类型。
函数返回它实际写入的内容,返回值类型也是int
。发生错误返回EOF。
putc
int putc(int c, FILE *stream);
putc
函数同fputc
函数,但是一般实现为宏。所以参数不应该带有副作用。
putchar
int putchar(int c);
putchar
函数同putc
函数,只不过默认流是stdout。
fgetc
int fgetc(FILE *stream);
fgetc
函数从流中读取一个字节,然后从unsigned char
类型转换成int
类型并返回。
函数返回读取的内容,如果遇到文件末尾或错误,返回EOF。
getc
int getc(FILE *stream);
getc
函数同fgetc
函数,但是一般实现为宏。所以参数不应该带有副作用。
getchar
int getchar(void);
getchar
函数同getc
函数,只不过默认流是stdin。
fgets
char *fgets(char * restrict s, int n,
FILE * restrict stream);
fgets
函数从流中读取最多n-1个字节,存入数组s中。如果遇到换行符'\n'
或者文件末尾,则停止读取(换行符会被读取并存入)。fgets
会在存入数组的数据末尾加一个空字符'\0'
。
函数执行成功返回参数s。如果在成功读取任何字符前直接遇到文件末尾或错误,返回空指针。
fputs
int fputs(const char * restrict s,
FILE * restrict stream);
fputs
函数将字符串s的内容写入流中,遇到空字符'\0'
则停止,空字符不会被写入。
函数执行成功返回一个非负整数,失败返回EOF。
puts
int puts(const char *s);
puts
函数类似于fputs
,不过默认流是stdout,并且在结尾输出一个换行符'\n'
。
由于存在安全问题,
gets
函数已经在C11标准中被删除,本文不再讨论它。
ungetc
int ungetc(int c, FILE *stream);
ungetc
函数将一个字符(由int
类型转换为unsigned char
类型)放回流中,如果继续进行读操作,则按照放回顺序的逆序读取这些字符。
函数执行成功返回实际放回的字符,失败返回EOF。
ungetc
函数的表现就好像是逆向写入并覆盖了原来流中的数据,它每次写入当前位置的前一个字节,并递减文件位置指示器。因此,成功执行ungetc
函数后,文件位置指示器始终指向刚刚放回的字符。
ungetc
函数并没有真正将字符放入流中,而是存入了另一个临时的缓冲区。任何ungetc
函数放入的字符都不会被保存到文件中,并且一旦调用fseek
、fsetpos
、rewind
等设置文件位置指针的函数,所有没有被读取的由ungetc
函数放回的字符都会被丢弃。在很多实现里,执行写操作也会丢弃ungetc
函数放回的字符。
如果参数c是EOF,则函数失败返回。如果函数执行成功,将会重置流的EOF标志。
ungetc
函数的设计和机制比较诡异,应谨慎使用。
直接输入输出
直接输入输出多用于二进制文件的读写。
fread
size_t fread(void * restrict ptr,
size_t size, size_t nmemb,
FILE * restrict stream);
fread
函数从流中读取nmemb个元素,每个元素size个字节,读取的内容依次写入ptr指向的内存区域。
函数返回成功读取的元素个数,即小于或等于nmemb的非负整数。
fread
函数最多读取nmemb * size
个字节。如果流中没有足够的数据,则读到文件末尾停止,此时如果最后一个元素不满足size个字节,则此元素不被计入返回值。因为写入ptr指向的内存的数据没有确定的结尾符,为了知道函数读取了多少字节,size应该设置为1。
fwrite
size_t fwrite(const void * restrict ptr,
size_t size, size_t nmemb,
FILE * restrict stream);
fwrite
函数向流中写入nmemb个元素,每个元素size个字节,写入的数据来自ptr指向的内存区域。
函数返回成功写入的元素个数,即小于或等于nmemb的非负整数。
fwrite
函数最多写入nmemb * size
个字节。如果遇到写入错误,则函数返回,此时如果最后一个元素不满足size个字节,则此元素不被计入返回值。size也建议设置为1。