C 编程/流 I/O
stdio.h
头文件声明了大量执行文件和设备(如控制台)输入和输出的函数。它是 C 库中最早出现的头文件之一。它声明的函数比任何其他标准头文件都多,并且由于函数背后的复杂机制,也需要更多的解释。
输入和输出的设备无关模型多年来得到了显著的改进,并且对其成功获得了很少的认可。FORTRAN II 在 20 世纪 60 年代被吹捧为一种机器无关的语言,但实际上在不进行一些更改的情况下,在架构之间移动 FORTRAN 程序几乎是不可能的。在 FORTRAN II 中,您在 FORTRAN 代码中间的 FORTRAN 语句中直接命名您正在与之通信的设备。因此,您在基于磁带的 IBM 7090 上说READ INPUT TAPE 5
,但在其他机器上说READ CARD
以读取卡片图像。FORTRAN IV 具有更通用的READ
和WRITE
语句,指定了逻辑单元号(LUN)而不是设备名称。设备无关 I/O 的时代开始了。
外围设备(如打印机)仍然对它们被要求执行的操作有相当强的概念。然后,发明了外设交换实用程序来处理奇特的设备。当阴极射线管出现时,每家控制台制造商都以独立的方式解决了诸如控制台光标移动等问题,从而造成了进一步的麻烦。
Unix 正是在这种氛围下诞生的。Unix 的开发者 Ken Thompson 和 Dennis Ritchie 应该得到赞誉,因为他们在操作系统中融入了许多巧妙的想法。他们对设备独立性的方法是最聪明的方法之一。
ANSI C <stdio.h>
库基于原始的 Unix 文件 I/O 原语,但范围更广,以适应不同系统之间的最小公分母。
输入和输出,无论是到物理设备(如终端和磁带驱动器),还是到结构化存储设备上支持的文件,都映射到逻辑数据流中,其属性比其各种输入和输出更统一。支持两种映射形式:文本流和二进制流。
文本流由一个或多个行组成。文本流中的一行由零个或多个字符加上一个终止换行符组成。(唯一的例外是在某些实现中,文件的最后一行不需要终止换行符。)Unix 采用了所有文本流的标准内部格式。每一行文本都以换行符结尾。这就是任何程序在读取文本时所期望的,也是任何程序在写入文本时所产生的。(这是最基本的约定,如果它不满足连接到 Unix 机器上的面向文本的外围设备的需求,那么修复发生在系统的边缘。中间的任何东西都不需要改变。)进入或离开文本流的字符字符串可能需要修改以符合特定约定。这会导致输入数据与输出数据之间存在可能的差异。例如,在某些实现中,当空格字符在输入中位于换行符之前时,空格字符会从输出中删除。一般来说,当数据仅包含可打印字符和控制字符(如水平制表符和换行符)时,文本流的输入和输出是相等的。
与文本流相比,二进制流非常简单。二进制流是字符的有序序列,可以透明地记录内部数据。写入二进制流的数据应始终等于在相同实现下读取的数据。但是,二进制流可能在流的末尾附加了实现定义数量的空字符。没有其他需要考虑的约定。
Unix 中没有任何内容阻止程序将任意 8 位二进制代码写入任何打开的文件,或从足够的存储库中读取它们而不进行更改。因此,Unix 消除了长期以来文本流和二进制流之间的区别。
当 C 程序开始执行时,程序会自动打开三个名为stdin
、stdout
和stderr
的标准流。这些是为每个 C 程序附加的。
第一个标准流用于输入缓冲,另外两个用于输出。这些流是字节序列。
考虑以下程序
/* An example program. */
int main()
{
int var;
scanf ("%d", &var); /* use stdin for scanning an integer from keyboard. */
printf ("%d", var); /* use stdout for printing the integer that was just scanned in. */
return 0;
}
/* end program. */
默认情况下,stdin
指向键盘,stdout
和stderr
指向屏幕。在 Unix 下是可能的,在其他操作系统下也可能是可能的,可以将输入从文件重定向或将输出重定向到文件或两者兼而有之。
FILE
而不是stream
。<stdio.h>
头文件包含类型FILE
的定义(通常通过typedef
),它能够处理控制流所需的所有信息,包括其文件位置指示器、指向关联缓冲区(如果有)的指针、记录是否发生读/写错误的错误指示器以及记录文件结尾是否已到达的文件结尾指示器。
除非程序员正在编写<stdio.h>
及其内容的实现,否则直接访问FILE
的内容被认为是不好的做法。通过<stdio.h>
中的函数提供了对FILE
内容的更好访问。可以说,FILE
类型是面向对象编程的早期示例。
为了打开和关闭文件,<stdio.h>
库提供了三个函数:fopen
、freopen
和 fclose
。
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);
FILE *freopen(const char *filename, const char *mode, FILE *stream);
fopen
和 freopen
打开由 filename
指向的字符串中指定的文件,并将其与一个流关联。两者都返回一个指向控制该流的对象的指针,或者如果打开操作失败,则返回一个空指针。错误和文件结束指示器被清除,如果打开操作失败,则设置错误。freopen
与 fopen
的区别在于,如果 stream
指向的文件已经打开,则首先将其关闭,并且忽略任何关闭错误。
对于这两个函数,mode
指向一个以以下序列之一开头的字符串(序列之后可以跟其他字符)
r open a text file for reading w truncate to zero length or create a text file for writing a append; open or create text file for writing at end-of-file rb open binary file for reading wb truncate to zero length or create a binary file for writing ab append; open or create binary file for writing at end-of-file r+ open text file for update (reading and writing) w+ truncate to zero length or create a text file for update a+ append; open or create text file for update r+b or rb+ open binary file for update (reading and writing) w+b or wb+ truncate to zero length or create a binary file for update a+b or ab+ append; open or create binary file for update
如果文件不存在或无法读取,则以读取模式(mode
参数的第一个字符为 'r
')打开文件将失败。
以追加模式(mode
参数的第一个字符为 'a
')打开文件会导致所有后续写入文件操作都被强制到当时的 文件末尾,而不管中间是否调用了 fseek
函数。在某些实现中,以追加模式(上述 mode
参数列表中的第二个或第三个字符为 'b
')打开二进制文件可能会将流的文件位置指示器初始定位到最后一个写入数据之后,因为存在空字符填充。
当以更新模式(上述 mode
参数值的第二个或第三个字符为 '+
')打开文件时,可以在关联的流上执行输入和输出。但是,输出不能直接后跟输入,除非在中间调用 fflush
函数或文件定位函数(fseek
、fsetpos
或 rewind
),并且输入不能直接后跟输出,除非在中间调用文件定位函数,除非输入操作遇到文件结束。在某些实现中,以更新模式打开(或创建)文本文件可能会改为打开(或创建)二进制流。
当打开时,当且仅当可以确定流不引用交互式设备时,它才会被完全缓冲。
#include <stdio.h>
int fclose(FILE *stream);
fclose
函数会导致 stream
指向的流被刷新,并且关联的文件被关闭。流的任何未写入的缓冲数据都将传递到主机环境以写入文件;任何未读取的缓冲数据都将被丢弃。流与文件分离。如果关联的缓冲区是自动分配的,则将其释放。如果流成功关闭,则函数返回零;如果检测到任何错误,则返回 EOF
。
#include <stdio.h>
int fflush(FILE *stream);
如果 stream
指向输出流或更新流,并且最近的操作不是输入,则 fflush
函数会导致该流的任何未写入数据被延迟到主机环境以写入文件。对于输入流,fflush
的行为未定义。
如果 stream
是一个空指针,则 fflush
函数对所有行为如上所述定义的流执行此刷新操作。
如果发生写入错误,fflush
函数返回 EOF
,否则返回零。
使用 fflush
函数的原因是,C 中的流可以具有缓冲输入/输出;也就是说,写入文件的函数实际上写入 FILE
结构内部的缓冲区。如果缓冲区已满,则写入函数将调用 fflush
以实际“写入”缓冲区中的数据到文件。由于 fflush
只是偶尔被调用,因此可以最大限度地减少对操作系统的原始写入调用。
#include <stdio.h>
void setbuf(FILE *stream, char *buf);
除了不返回值之外,setbuf
函数等效于使用 _IOFBF
作为 mode
和 BUFSIZ
作为 size
调用 setvbuf
函数,或者(如果 buf
是一个空指针)使用 _IONBF
作为 mode
调用 setvbuf
函数。
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
setvbuf
函数只能在 stream
指向的流与打开的文件关联之后以及在对流执行任何其他操作之前使用。参数 mode
确定流将如何被缓冲,如下所示:_IOFBF
使输入/输出被完全缓冲;_IOLBF
使输入/输出被行缓冲;_IONBF
使输入/输出不被缓冲。如果 buf
不是空指针,则它指向的数组可以用来代替 setvbuf
函数关联的缓冲区。(缓冲区的生存期必须至少与打开的流一样长,因此在块退出时释放具有自动存储持续时间的缓冲区之前,应先关闭流。)参数 size
指定数组的大小。数组内容在任何时候都是不确定的。
如果成功,setvbuf
函数返回零;如果为 mode
提供了无效值或无法满足请求,则返回非零值。
除了执行读写操作的函数外,stdio.h
库还有五个影响文件位置指示器的函数:fgetpos
、fseek
、fsetpos
、ftell
和 rewind
。
fseek
和 ftell
函数比 fgetpos
和 fsetpos
函数更早出现。
#include <stdio.h>
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
fgetpos
函数将 stream
指向的流的文件位置指示器的当前值存储在 pos
指向的对象中。存储的值包含 fsetpos
函数可用于将流重新定位到调用 fgetpos
函数时的位置的未指定信息。
如果成功,fgetpos
函数返回零;如果失败,则 fgetpos
函数返回非零值,并在 errno
中存储一个实现定义的正值。
fsetpos
函数根据 pos
指向的对象的值设置 stream
指向的流的文件位置指示器,该值应是从对同一流的先前 fgetpos
函数调用中获得的值。
成功调用 fsetpos
函数会清除流的文件结束指示器,并撤消 ungetc
函数对同一流的任何影响。在 fsetpos
调用之后,更新流上的下一个操作可以是输入或输出。
如果成功,fsetpos
函数返回零;如果失败,则 fsetpos
函数返回非零值,并在 errno
中存储一个实现定义的正值。
#include <stdio.h>
int fseek(FILE *stream, long int offset, int whence);
long int ftell(FILE *stream);
fseek
函数设置 stream
指向的流的文件位置指示器。
对于二进制流,新的位置(以文件开头字符为单位测量)是通过将offset
添加到由whence
指定的位置得到的。stdio.h
中名为SEEK_SET
、SEEK_CUR
和SEEK_END
的三个宏扩展为唯一值。如果由whence
指定的位置是SEEK_SET
,则指定的位置是文件开头;如果whence
是SEEK_END
,则指定的位置是文件结尾;如果whence
是SEEK_CUR
,则指定的位置是当前文件位置。二进制流不必有意义地支持whence
值为SEEK_END
的fseek
调用。
对于文本流,offset
应为零,或者offset
应为之前对同一流调用ftell
函数返回的值,并且whence
应为SEEK_SET
。
只有在无法满足请求时,fseek
函数才会返回非零值。
ftell
函数获取由stream
指向的流的文件位置指示器的当前值。对于二进制流,该值是从文件开头算起的字符数;对于文本流,其文件位置指示器包含fseek
函数可用于将流的文件位置指示器返回到ftell
调用时其位置的未指定信息;这两个返回值之间的差值不一定是写入或读取的字符数的有意义的度量。
如果成功,ftell
函数将返回流的文件位置指示器的当前值。如果失败,ftell
函数将返回-1L
,并在errno
中存储一个实现定义的正值。
#include <stdio.h>
void rewind(FILE *stream);
rewind
函数将由stream
指向的流的文件位置指示器设置为文件开头。它等效于
(void)fseek(stream, 0L, SEEK_SET)
但它还会清除流的错误指示器。
#include <stdio.h>
void clearerr(FILE *stream);
clearerr
函数清除由stream
指向的流的文件结束指示器和错误指示器。
#include <stdio.h>
int feof(FILE *stream);
feof
函数测试由stream
指向的流的文件结束指示器,当且仅当stream
的文件结束指示器已设置时返回非零值,否则返回零。
#include <stdio.h>
int ferror(FILE *stream);
ferror
函数测试由stream
指向的流的错误指示器,当且仅当stream
的错误指示器已设置时返回非零值,否则返回零。
#include <stdio.h>
void perror(const char *s);
perror
函数将整数表达式errno
中的错误号映射到错误消息。它向标准错误流写入一系列字符,如下所示:首先,如果s
不是空指针,并且s
指向的字符不是空字符,则写入s
指向的字符串,后跟冒号(:) 和一个空格;然后是一个适当的错误消息字符串,后跟一个换行符。错误消息的内容与使用参数errno
调用strerror
函数返回的内容相同,这些内容是实现定义的。
stdio.h
库包含各种函数,除了读取和写入之外,还可以对文件执行某些操作。
#include <stdio.h>
int remove(const char *filename);
remove
函数会导致不再可以通过filename
指向的字符串指定的文件名访问该文件。随后尝试使用该文件名打开该文件将失败,除非重新创建该文件。如果文件已打开,则remove
函数的行为是实现定义的。
如果操作成功,remove
函数返回零,如果失败则返回非零值。
#include <stdio.h>
int rename(const char *old_filename, const char *new_filename);
rename
函数会导致从此以后通过new_filename
指向的字符串给出的名称来识别old_filename
指向的字符串指定的文件名。不再可以通过该名称访问名为old_filename
的文件。如果在调用rename
函数之前存在名为new_filename
指向的字符串的文件,则行为是实现定义的。
如果操作成功,rename
函数返回零,如果失败则返回非零值,在这种情况下,如果文件先前存在,则它仍然以其原始名称识别。
#include <stdio.h>
FILE *tmpfile(void);
tmpfile
函数创建一个临时二进制文件,该文件将在关闭时或程序终止时自动删除。如果程序异常终止,则是否删除打开的临时文件是实现定义的。该文件以"wb+"
模式打开以进行更新。
tmpfile
函数返回指向其创建的文件的流的指针。如果无法创建文件,则tmpfile
函数将返回空指针。
#include <stdio.h>
char *tmpnam(char *s);
tmpnam
函数生成一个有效的文件名字符串,该字符串不是现有文件的名称。
tmpnam
函数每次被调用时都会生成一个不同的字符串,最多TMP_MAX
次。(TMP_MAX
是stdio.h
中定义的宏。)如果它被调用超过TMP_MAX
次,则行为是实现定义的。
实现应表现得好像没有库函数调用tmpnam
函数一样。
如果参数为空指针,则tmpnam
函数将其结果保留在内部静态对象中,并返回指向该对象的指针。随后对tmpnam
函数的调用可能会修改同一对象。如果参数不是空指针,则假定它指向至少L_tmpnam
个字符的数组(L_tmpnam
是stdio.h
中的另一个宏);tmpnam
函数将其结果写入该数组,并返回参数作为其值。
宏TMP_MAX
的值必须至少为25。
#include <stdio.h>
int fgetc(FILE *stream);
fgetc
函数从由stream
指向的流中获取下一个字符(如果存在)作为转换为int
的unsigned char
,并(如果已定义)推进流的相关文件位置指示器。
fgetc
函数返回由stream
指向的流中的下一个字符。如果流处于文件结尾或发生读取错误,则fgetc
将返回EOF
(EOF
是<stdio.h>
中定义的负值,通常为(-1)
)。必须使用feof
和ferror
例程来区分文件结尾和错误。如果发生错误,则全局变量errno
将设置为指示错误。
#include <stdio.h>
char *fgets(char *s, int n, FILE *stream);
fgets
函数最多从由stream
指向的流中读取比n
指定字符数少一个的字符,并将其写入由s
指向的数组中。在换行符(保留)或文件结尾之后,不会读取其他字符。一个空字符将立即写入读取到数组中的最后一个字符之后。
如果 fgets
函数执行成功,则返回 s
。如果遇到文件结束并且没有字符读入数组,则数组内容保持不变并返回空指针。如果操作过程中发生读取错误,则数组内容不确定并返回空指针。
警告:不同的操作系统可能使用不同的字符序列来表示行结束序列。例如,某些文件系统在文本文件中使用终止符 \r\n
;fgets
可能会读取这些行,删除 \n
但保留 \r
作为 s
的最后一个字符。在使用该字符串进行任何操作之前,应从字符串 s
中删除此多余的字符(除非程序员不关心它)。Unix 通常使用 \n
作为其行结束序列,MS-DOS 和 Windows 使用 \r\n
,Mac OS 在 OS X 之前使用 \r
。许多非 Unix 或 Linux 操作系统上的编译器在文本文件的输入中将换行序列映射到 \n
;请检查编译器的文档以了解它在这种情况下执行的操作。
/* An example program that reads from stdin and writes to stdout */
#include <stdio.h>
#define BUFFER_SIZE 100
int main(void)
{
char buffer[BUFFER_SIZE]; /* a read buffer */
while( fgets (buffer, BUFFER_SIZE, stdin) != NULL)
{
printf("%s",buffer);
}
return 0;
}
/* end program. */
#include <stdio.h>
int getc(FILE *stream);
getc
函数等效于 fgetc
,但它可能被实现为宏。如果它被实现为宏,则 stream
参数可能会被评估多次,因此该参数永远不应该是一个有副作用的表达式(即具有赋值、增量或减量运算符,或为函数调用)。
getc
函数返回由 stream
指向的输入流中的下一个字符。如果流处于文件结束位置,则设置流的文件结束指示符,并且 getc
返回 EOF
(EOF
是在 <stdio.h>
中定义的负值,通常为 (-1)
)。如果发生读取错误,则设置流的错误指示符,并且 getc
返回 EOF
。
#include <stdio.h>
int getchar(void);
getchar
函数等效于参数为 stdin
的 getc
。
getchar
函数返回由 stdin
指向的输入流中的下一个字符。如果 stdin
处于文件结束位置,则设置 stdin
的文件结束指示符,并且 getchar
返回 EOF
(EOF
是在 <stdio.h>
中定义的负值,通常为 (-1)
)。如果发生读取错误,则设置 stdin
的错误指示符,并且 getchar
返回 EOF
。
#include <stdio.h>
char *gets(char *s);
gets
函数从由 stdin
指向的输入流中读取字符到由 s
指向的数组中,直到遇到文件结束或读取到换行符。任何换行符都将被丢弃,并且一个空字符将写入读取到数组中的最后一个字符之后。
如果 gets
函数执行成功,则返回 s
。如果遇到文件结束并且没有字符读入数组,则数组内容保持不变并返回空指针。如果操作过程中发生读取错误,则数组内容不确定并返回空指针。
此函数和描述仅出于完整性考虑而在此处包含。如今,大多数 C 程序员都避免使用 gets
,因为该函数无法知道程序员想要读取到的缓冲区大小。
Henry Spencer 的《C 程序员十诫(注释版)》中的第 5 条诫命写道
你必须检查所有字符串(实际上是所有数组)的数组边界,因为你键入 foo 的地方,总有一天会有人键入 supercalifragilisticexpialidocious。
它在注释中提到了 gets
正如伟大的蠕虫的事迹所证明的那样,这条诫命的结果是,健壮的生产软件永远不应该使用gets()
,因为它确实是魔鬼的工具。你的接口应该始终告知你的仆人你的数组的边界,而那些轻视此类建议或悄悄地未能遵循此类建议的仆人,应立即被送往 Rm 之地,在那里他们无法再对你造成任何伤害。
在 2018 年版本的 C 标准之前,gets
函数已被弃用。希望程序员改用 fgets
函数。
#include <stdio.h>
int ungetc(int c, FILE *stream);
ungetc
函数将由 c
指定的字符(转换为 unsigned char
)压回由 stream 指向的输入流中。压回的字符将在该流上后续读取时以其压入的反序返回。对文件定位函数(fseek
、fsetpos
或 rewind
)的成功干预调用(使用由 stream
指向的流)将丢弃该流的任何压回字符。与流对应的外部存储保持不变。
保证一个字符的压回。如果在对同一流进行干预读取或文件定位操作之前,对同一流调用 ungetc
函数的次数过多,则操作可能会失败。
如果 c
的值等于宏 EOF
的值,则操作失败并且输入流保持不变。
成功调用 ungetc
函数将清除流的文件结束指示符。在读取或丢弃所有压回字符后,流的文件位置指示符的值应与压回字符之前相同。对于文本流,在成功调用 ungetc
函数后,其文件位置指示符的值是不确定的,直到所有压回字符都被读取或丢弃。对于二进制流,其文件位置指示符将通过每次成功调用 ungetc
函数而递减;如果其值在调用之前为零,则在调用之后是不确定的。
ungetc
函数返回转换后压回的字符,如果操作失败则返回 EOF
。
使用 fgetc
、getc
或 getchar
时的一个错误是在将其与 EOF
进行比较之前将结果赋值给 char
类型的变量。以下代码片段展示了此错误,然后显示了正确的方法(使用 int
类型)
错误 | 更正 |
---|---|
char c;
while ((c = getchar()) != EOF)
putchar(c);
|
int c;
while ((c = getchar()) != EOF)
putchar(c);
|
考虑一个 char
类型为 8 位宽的系统,表示 256 个不同的值。getchar
可以返回 256 个可能的字符中的任何一个,它还可以返回 EOF
来指示文件结束,总共有 257 个不同的可能返回值。
当 getchar
的结果被赋值给 char
时,char
只能表示 256 个不同的值,因此必然会丢失一些信息——当将 257 个项目打包到 256 个槽中时,必然会发生冲突。EOF
值在转换为 char
时,会变得与与其数值相同的 256 个字符中的任何一个无法区分。如果在文件中找到该字符,则以上示例可能会将其误认为是文件结束指示符;或者,同样糟糕的是,如果 char
类型是无符号的,那么由于 EOF
是负数,它永远不可能等于任何无符号 char
,因此以上示例不会在文件结束时终止。它将永远循环,反复打印将 EOF
转换为 char
所产生的字符。
但是,如果 char
定义是有符号的(C 使默认 char
类型是有符号还是无符号的实现相关),[1]假设常用的EOF
值为 -1,则不会发生这种循环失败模式。但是,根本问题仍然存在,如果 EOF
值定义在 char
类型的范围之外,则当将其赋值给 char
时,该值会被截断,并且将不再匹配退出循环所需的完整 EOF
值。另一方面,如果 EOF
在 char
的范围内,则这保证了 EOF
和 char
值之间存在冲突。因此,无论系统类型如何定义,在测试 EOF
时都不要使用 char
类型。
在 int
和 char
大小相同的系统上(即与至少 POSIX 和 C99 标准不兼容的系统),即使“良好”示例也会受到 EOF
和某些字符值无法区分的影响。处理这种情况的正确方法是在 getchar
返回 EOF
后检查 feof
和 ferror
。如果 feof
指示尚未到达文件结束,并且 ferror
指示没有发生错误,则可以假定 getchar
返回的 EOF
表示一个实际的字符。这些额外的检查很少执行,因为大多数程序员假设他们的代码永远不需要在这些“大 char
”系统上运行。另一种方法是使用编译时断言以确保 UINT_MAX > UCHAR_MAX
,这至少可以防止具有此类假设的程序在这样的系统上编译。
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
fread
函数将来自由 stream
指向的流中的最多 nmemb
个元素读取到由 ptr
指向的数组中,每个元素的大小由 size
指定。流的文件位置指示器(如果已定义)将根据成功读取的字符数前进。如果发生错误,流的文件位置指示器的结果值是不确定的。如果读取了部分元素,则其值是不确定的。
fread
函数返回成功读取的元素数量,如果遇到读取错误或文件结尾,则该数量可能小于 nmemb
。如果 size
或 nmemb
为零,则 fread
返回零,并且数组的内容和流的状态保持不变。
#include <stdio.h>
int fscanf(FILE *stream, const char *format, ...);
int scanf(const char *format, ...);
int sscanf(const char *s, const char *format, ...);
fscanf
函数从由 stream
指向的流中读取输入,在由 format
指向的字符串的控制下,该字符串指定了可接受的序列以及如何将它们转换为赋值,使用后续参数作为指向接收转换输入的对象的指针。如果格式的参数不足,则行为未定义。如果在参数仍然存在的情况下格式已用尽,则会评估多余的参数(一如既往),但其他情况下会被忽略。
格式应为多字节字符序列,以其初始移位状态开始和结束。格式由零个或多个指令组成:一个或多个空白字符;一个普通的多字节字符(既不是%也不是空白字符);或转换说明。每个转换说明都以字符%开头。在%之后,以下内容按顺序出现
- 一个可选的赋值抑制字符*.
- 一个可选的非零十进制整数,指定最大字段宽度。
- 一个可选的h, l(ell)或L指示接收对象的尺寸。转换说明符d, i和n应以h为前缀,如果相应的参数是指向
short int
的指针而不是指向int
的指针,或者以l为前缀,如果它是指向long int
的指针。类似地,转换说明符o, u和x应以h应以l为前缀,如果相应的参数是指向unsigned short int
的指针而不是unsigned int
的指针,或者以e, f和g应以l应以L为前缀,如果相应的参数是指向double
的指针而不是指向float
的指针,或者以h, l与任何其他格式说明符一起使用,则行为未定义。L或 - 指定要应用的转换类型的字符。有效的转换说明符如下所述。
fscanf
函数依次执行格式的每个指令。如果指令失败,如下所述,fscanf
函数将返回。失败被描述为输入失败(由于输入字符不可用)或匹配失败(由于输入不合适)。
由空白字符组成的指令通过读取输入直到第一个非空白字符(该字符保持未读)或直到没有更多字符保持未读来执行。
作为普通多字节字符的指令通过读取流的下一个字符来执行。如果其中一个字符与构成指令的字符不同,则指令失败,并且不同的字符和后续字符保持未读。
作为转换说明的指令定义了一组匹配的输入序列,如下所述,每个说明符都有相应的描述。转换说明按以下步骤执行
跳过输入空白字符(由 isspace
函数指定),除非说明包含[, c与任何其他格式说明符一起使用,则行为未定义。n说明符。(空白字符不计入指定的字段宽度。)
从流中读取一个输入项,除非说明包含n说明符。输入项定义为最长的匹配输入字符序列,除非它超过指定的字段宽度,在这种情况下,它是该序列中初始的长度相同的子序列。第一个字符(如果有)在输入项之后保持未读。如果输入项的长度为零,则指令的执行失败;此条件是匹配失败,除非错误阻止了来自流的输入,在这种情况下,它是输入失败。
除了%说明符的情况外,输入项(或者,在%n指令的情况下,输入字符的数量)被转换为适合转换说明符的类型。如果输入项不是匹配的序列,则指令的执行失败;此条件是匹配失败。除非赋值抑制由*指示,否则转换的结果将放置在 format
参数之后第一个尚未接收转换结果的参数指向的对象中。如果此对象没有合适的类型,或者转换的结果无法在提供的空间中表示,则行为未定义。
以下转换说明符有效
- d
- 匹配一个可选带符号的十进制整数,其格式与
strtol
函数的主题序列(base
参数的值为 10)的预期格式相同。相应的参数应是指向整数的指针。
- i
- 匹配一个可选带符号的整数,其格式与
strtol
函数的主题序列(base
参数的值为 0)的预期格式相同。相应的参数应是指向整数的指针。
- o
- 匹配一个可选带符号的八进制整数,其格式与
strtoul
函数的主题序列(base
参数的值为 8)的预期格式相同。相应的参数应是指向无符号整数的指针。
- u
- 匹配一个可选带符号的十进制整数,其格式与
strtoul
函数的主题序列(base
参数的值为 10)的预期格式相同。相应的参数应是指向无符号整数的指针。
- x
- 匹配一个可选带符号的十六进制整数,其格式与
strtoul
函数的主题序列(base
参数的值为 16)的预期格式相同。相应的参数应是指向无符号整数的指针。
- e, f, g
- 匹配一个可选带符号的浮点数,其格式与
strtod
函数的主题字符串的预期格式相同。相应的参数将是指向浮点数的指针。
- s
- 匹配一系列非空白字符。(没有为多字节字符做出特殊规定。)相应的参数应是指向足够大的数组的初始字符的指针,以接受该序列和一个终止空字符,该字符将自动添加。
- [
- 匹配一系列非空字符(没有为多字节字符做出特殊规定),这些字符来自一组预期字符(扫描集)。相应的参数应是指向足够大的数组的初始字符的指针,以接受该序列和一个终止空字符,该字符将自动添加。转换说明符包含
format
字符串中的所有后续字符,直到包含匹配的右括号(])。括号之间的字符(扫描列表)构成扫描集,除非左括号后的字符是插入符号(^),在这种情况下,扫描集包含扫描列表中插入符号和右括号之间未出现的字符。如果转换说明符以[]或[^]开头,则右括号字符位于扫描列表中,下一个右括号字符是结束说明的匹配右括号;否则,第一个右括号字符是结束说明的字符。如果-字符位于扫描列表中,并且不是第一个字符,也不是第二个字符(其中第一个字符是^),也不是最后一个字符,则行为由实现定义。
- c
- 匹配由字段宽度指定的字符序列(如果指令中没有字段宽度,则为 1)。相应的参数应是指向足够大的数组的初始字符的指针,以接受该序列。不会添加空字符。
- p
- 匹配一组由实现定义的序列,这组序列应与
fprintf
函数的%p转换可能产生的序列集相同。相应的参数应是指向void
的指针。然后,输入的解释由实现定义。如果输入项是在同一程序执行期间早些时候转换的值,则结果指针应与该值相等;否则,%p转换的行为未定义。
- n
- 不使用任何输入。相应的参数应是指向整数的指针,该整数用于写入到目前为止此
fscanf
函数调用从输入流中读取的字符数。执行%n指令不会增加在fscanf
函数执行完成时返回的赋值计数。
- %
- 匹配单个%;不发生转换或赋值。完整的转换说明应为%%.
如果转换说明无效,则行为未定义。
转换说明符E, G和X也有效,其行为分别与e, g和x.
相同。如果在输入期间遇到文件结尾,则转换将终止。如果在读取任何与当前指令匹配的字符(除了允许的开头空白字符)之前发生文件结尾,则当前指令的执行将以输入失败终止;否则,除非当前指令的执行以匹配失败终止,否则后续指令(如果有)的执行将以输入失败终止。
如果转换在遇到冲突的输入字符时终止,则有问题的输入字符将保留在输入流中未读取。尾随空白(包括换行符)除非由指令匹配,否则将保留未读取。除了通过%n指令之外,无法直接确定文字匹配和抑制赋值的成功与否。
如果在任何转换之前发生输入错误,则fscanf
函数将返回宏EOF
的值。否则,fscanf
函数将返回已分配的输入项数,在早期匹配失败的情况下,该数可能少于提供的项数,甚至为零。
scanf
函数等效于fscanf
,只不过在scanf
的参数之前插入了参数stdin
。它的返回值类似于fscanf
。
sscanf
函数等效于fscanf
,除了参数s
指定要从中获取输入的字符串,而不是从流中获取。到达字符串末尾等效于遇到fscanf
函数的 文件结束符。如果在重叠的对象之间进行复制,则行为未定义。
#include <stdio.h> int fputc(int c, FILE *stream);
fputc
函数将由c
指定的字符(转换为unsigned char
)写入stream
指向的流中,写入位置由关联的文件位置指示器(如果已定义)指示,并适当地推进指示器。如果文件不支持定位请求,或者如果流以追加模式打开,则字符将追加到输出流中。该函数返回写入的字符,除非发生写入错误,在这种情况下,流的错误指示器将被设置,并且fputc
返回EOF
。
#include <stdio.h> int fputs(const char *s, FILE *stream);
fputs
函数将s
指向的字符串写入stream
指向的流中。不会写入终止的空字符。如果发生写入错误,则函数返回EOF
,否则返回非负值。
#include <stdio.h> int putc(int c, FILE *stream);
putc
函数等效于fputc
,除了如果它作为宏实现,它可能会多次评估stream
,因此参数永远不应该是具有副作用的表达式。该函数返回写入的字符,除非发生写入错误,在这种情况下,流的错误指示器将被设置,并且函数返回EOF
。
#include <stdio.h> int putchar(int c);
putchar
函数等效于第二个参数为stdout
的putc
。它返回写入的字符,除非发生写入错误,在这种情况下,stdout
的错误指示器将被设置,并且函数返回EOF
。
#include <stdio.h> int puts(const char *s);
puts
函数将s
指向的字符串写入stdout
指向的流中,并在输出中追加一个换行符。不会写入终止的空字符。如果发生写入错误,则函数返回EOF
;否则,返回非负值。
#include <stdio.h> size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
fwrite
函数从ptr
指向的数组中,最多写入nmemb
个元素,每个元素的大小由size
指定,写入stream
指向的流中。流的文件位置指示器(如果已定义)将根据成功写入的字符数前进。如果发生错误,则流的文件位置指示器的结果值是不确定的。该函数返回成功写入的元素数,只有在遇到写入错误时,该数才会小于nmemb
。
#include <stdarg.h> #include <stdio.h> int fprintf(FILE *stream, const char *format, ...); int printf(const char *format, ...); int sprintf(char *s, const char *format, ...); int vfprintf(FILE *stream, const char *format, va_list arg); int vprintf(const char *format, va_list arg); int vsprintf(char *s, const char *format, va_list arg);
注意:某些长度说明符和格式说明符是 C99 中的新增内容。在较旧的编译器和 stdio 库版本(遵循 C89/C90 标准)中可能无法使用这些内容。在可能的情况下,新的内容将用 (C99) 标记。
fprintf
函数根据format
指向的字符串控制,将输出写入stream
指向的流中,该字符串指定后续参数如何转换为输出。如果格式的参数不足,则行为未定义。如果格式用尽而参数仍然存在,则会评估多余的参数(一如既往),但除此之外将被忽略。当遇到格式字符串的末尾时,fprintf
函数返回。
格式应为多字节字符序列,在初始移位状态下开始和结束。格式由零个或多个指令组成:普通多字节字符(不是%),它们会原样复制到输出流中;以及转换说明,每个转换说明都会导致获取零个或多个后续参数,如果适用,则根据相应的转换说明符转换它们,然后将结果写入输出流。
每个转换说明都以字符%开头。在%之后,以下内容按顺序出现
- 零个或多个标志(按任何顺序)修改转换说明的含义。
- 可选的最小字段宽度。如果转换后的值的字符少于字段宽度,则在左侧(或右侧,如果已给出稍后描述的左调整标志)用空格(默认情况下)填充到字段宽度。字段宽度采用星号*(稍后描述)或十进制整数的形式。(请注意,0 被视为标志,而不是字段宽度的开头。)
- 可选的精度,它给出转换出现的最小数字位数。d, i, o, u, x和X转换,在小数点字符之后出现的数字位数。一个, 一个, e, E, f和F转换,转换的最大有效数字位数。g和G转换,或从字符串中写入的最大字符数。s转换。精度采用句点(.)后跟星号*(稍后描述)或可选的十进制整数的形式;如果只指定句点,则精度取为零。如果与任何其他转换说明符一起出现精度,则行为未定义。浮点数将四舍五入以适应精度;即printf("%1.1f\n", 1.19);产生1.2.
- 一个可选的长度修饰符,指定参数的大小。
- 一个转换说明符字符,指定要应用的转换类型。
如上所述,字段宽度或精度或两者都可以由星号表示。在这种情况下,int
参数提供字段宽度或精度。指定字段宽度或精度或两者的参数应(按该顺序)出现在要转换的参数(如果有)之前。负字段宽度参数被视为-标志后跟正字段宽度。负精度参数被视为省略了精度。
标志字符及其含义为
- -
- 转换的结果在字段内左对齐。(如果未指定此标志,则右对齐。)
- +
- 带符号转换的结果始终以加号或减号开头。(如果未指定此标志,则仅在转换负值时以符号开头。所有负零的浮点转换以及舍入为零的负值的转换结果都包含减号。)
- 空格
- 如果带符号转换的第一个字符不是符号,或者带符号转换没有产生任何字符,则在结果前面加上空格。如果空格和+标志都出现,则忽略空格标志。
- #
- 结果转换为“备用形式”。对于o转换,如果且仅当需要时,它会增加精度,以强制结果的第一个数字为零(如果值和精度均为 0,则打印单个 0)。对于x(或X)转换,非零结果具有0x(或0X)作为前缀。一个, 一个, e, E, f, F, g和G对于转换,结果始终包含一个小数点字符,即使后面没有数字。(通常,只有在小数点字符后面有数字时,这些转换的结果中才会出现小数点字符。)对于g和G转换,结果不会删除尾随零。对于其他转换,行为未定义。
- 0
- 对于d, i, o, u, x, X, 一个, 一个, e, E, f, F, g和G转换,前导零(在任何符号或基数指示之后)用于填充到字段宽度;不执行空格填充。如果0和-标志都出现,则0标志将被忽略。对于d, i, o, u, x和X转换,如果指定了精度,则0标志将被忽略。对于其他转换,行为未定义。
长度修饰符及其含义为
- hh
- (C99)指定后续d, i, o, u, x与任何其他格式说明符一起使用,则行为未定义。X转换说明符应用于
signed char
或unsigned char
参数(参数将根据整数提升进行提升,但其值应在打印之前转换为signed char
或unsigned char
);或者后续n转换说明符应用于指向signed char
参数的指针。
- h
- 指定后续d, i, o, u, x与任何其他格式说明符一起使用,则行为未定义。X转换说明符应用于
short int
或unsigned short int
参数(参数将根据整数提升进行提升,但其值应在打印之前转换为short int
或unsigned short int
);或者后续n转换说明符应用于指向short int
参数的指针。
- l(ell)
- 指定后续d, i, o, u, x与任何其他格式说明符一起使用,则行为未定义。X转换说明符应用于
long int
或unsigned long int
参数;后续n转换说明符应用于指向long int
参数的指针;(C99)后续c转换说明符应用于wint_t
参数;(C99)后续s转换说明符应用于指向wchar_t
参数的指针;或者对后续一个, 一个, e, E, f, F, g与任何其他格式说明符一起使用,则行为未定义。G转换说明符没有影响。
- ll(ell-ell)
- (C99)指定后续d, i, o, u, x与任何其他格式说明符一起使用,则行为未定义。X转换说明符应用于
long long int
或unsigned long long int
参数;或者后续n转换说明符应用于指向long long int
参数的指针。
- j
- (C99)指定后续d, i, o, u, x与任何其他格式说明符一起使用,则行为未定义。X转换说明符应用于
intmax_t
或uintmax_t
参数;或者后续n转换说明符应用于指向intmax_t
参数的指针。
- z
- (C99)指定后续d, i, o, u, x与任何其他格式说明符一起使用,则行为未定义。X转换说明符应用于
size_t
或相应的带符号整数类型参数;或者后续n转换说明符应用于指向与size_t
参数相对应的带符号整数类型的指针。
- t
- (C99)指定后续d, i, o, u, x与任何其他格式说明符一起使用,则行为未定义。X转换说明符应用于
ptrdiff_t
或相应的无符号整数类型参数;或者后续n转换说明符应用于指向ptrdiff_t
参数的指针。
- L
- 指定后续一个, 一个, e, E, f, F, g与任何其他格式说明符一起使用,则行为未定义。G转换说明符应用于
long double
参数。
如果长度修饰符与除上述指定之外的任何转换说明符一起出现,则行为未定义。
转换说明符及其含义为
- d, i
int
参数将转换为带符号十进制,样式为[−]dddd。精度指定要显示的最小位数;如果要转换的值可以用更少的位数表示,则用前导零扩展。默认精度为 1。将零值转换为精度为零的结果是没有任何字符。
- o, u, x, X
unsigned int
参数将转换为无符号八进制(o)、无符号十进制(u)或无符号十六进制表示法(x或X)样式为dddd;字母abcdef用于x转换,字母ABCDEF用于X转换。精度指定要显示的最小位数;如果要转换的值可以用更少的位数表示,则用前导零扩展。默认精度为 1。将零值转换为精度为零的结果是没有任何字符。
- f, F
- 表示(有限)浮点数的
double
参数将转换为十进制表示法,样式为[−]ddd.ddd,其中小数点字符后面的数字个数等于精度规范。如果缺少精度,则取为 6;如果精度为零且未指定#标志,则不显示小数点字符。如果出现小数点字符,则其前面至少出现一个数字。该值将四舍五入到适当的数字位数。
(C99)表示无穷大的double
参数将转换为以下样式之一:[-]inf或[-]infinity——哪种样式由实现定义。表示 NaN 的 double 参数将转换为以下样式之一:[-]nan或[-]nan(n-char-sequence)——哪种样式以及任何n-char-sequence的含义由实现定义。该F转换说明符产生INF, INFINITY与任何其他格式说明符一起使用,则行为未定义。NAN而不是inf, infinity与任何其他格式说明符一起使用,则行为未定义。nan,分别。(当应用于无穷大和 NaN 值时,-, +和space标志具有其通常的含义;#和0标志无效。)
- e, E
- 表示(有限)浮点数的
double
参数将转换为样式[−]d.ddde±dd,其中小数点字符前有一个数字(如果参数非零,则该数字非零),并且其后的数字个数等于精度;如果缺少精度,则取为 6;如果精度为零且未指定#标志,则不显示小数点字符。该值将四舍五入到适当的数字位数。该E转换说明符产生一个带有E而不是e引入指数的数字。指数始终包含至少两位数字,并且仅包含表示指数所需的更多位数。如果值为零,则指数为零。
(C99)表示无穷大或 NaN 的double
参数将转换为f或F转换说明符没有影响。
- g, G
- 表示(有限)浮点数的
double
参数将转换为样式f或e(或在样式F或E的情况下,在G转换说明符的情况下),精度指定有效数字的个数。如果精度为零,则取为 1。使用的样式取决于转换的值;样式e(或E)仅在由此类转换产生的指数小于 –4 或大于或等于精度时使用。除非指定了#标志,否则结果的小数部分将删除尾随零;仅当小数点字符后面有数字时,才会显示小数点字符。
(C99)表示无穷大或 NaN 的double
参数将转换为f或F转换说明符没有影响。
- 一个, 一个
- (C99)表示(有限)浮点数的 double 参数将转换为样式[−]0xh.hhhhp±d,其中小数点字符前有一个十六进制数字(如果参数是标准化的浮点数,则该数字非零,否则未指定)(二进制实现可以选择小数点字符左侧的十六进制数字,以便后续数字与 nibble [4 位] 边界对齐。)并且其后的十六进制数字个数等于精度;如果缺少精度且
FLT_RADIX
是 2 的幂,则精度足以精确表示该值;如果缺少精度且FLT_RADIX
不是 2 的幂,则精度足以区分(精度p足以区分源类型的值,如果 16p–1 > bn 其中b是FLT_RADIX
,而n是源类型有效数中以b为底的数字个数。根据实现确定小数点字符左侧数字的方案,较小的p可能就足够了。)double
类型的值,但可以省略尾随零;如果精度为零且未指定#标志,则不显示小数点字符。字母abcdef用于一个转换,字母ABCDEF用于一个转换。该一个转换说明符产生一个带有X和P而不是x和p。指数始终包含至少一位数字,并且仅包含表示 2 的十进制指数所需的更多位数。如果值为零,则指数为零。
表示无穷大或 NaN 的double
参数将转换为f或F转换说明符没有影响。
- c
- 如果没有l长度修饰符,则
int
参数将转换为unsigned char
,并写入生成的字符。
(C99)如果l长度修饰符存在,则wint_t
参数将转换为好像由ls转换规范(没有精度)和一个指向包含wint_t
参数的两个元素wchar_t
数组的初始元素的指针一样,第一个元素包含wint_t
参数传递到lc转换规范,第二个元素为 null 宽字符。
- s
- 如果没有l长度修饰符存在,则参数应是指向字符类型数组的初始元素的指针。(未对多字节字符进行特殊规定。)写入数组中的字符,直至(但不包括)终止 null 字符。如果指定了精度,则最多写入这么多字符。如果未指定精度或大于数组的大小,则数组应包含一个 null 字符。
(C99)如果l长度修饰符存在,则参数应是指向wchar_t
类型数组的初始元素的指针。数组中的宽字符将转换为多字节字符(每个都好像通过调用wcrtomb
函数一样,在第一个宽字符转换之前,由mbstate_t
对象描述的转换状态初始化为零)直至并包括终止 null 宽字符。生成的多个字节字符被写入,直到(但不包括)终止 null 字符(字节)。如果没有指定精度,则数组应包含一个 null 宽字符。如果指定了精度,则最多写入这么多字符(字节)(包括任何移位序列),如果要等于精度给出的多字节字符序列长度,函数需要访问数组末尾之外的一个宽字符,则数组应包含一个 null 宽字符。在任何情况下都不会写入部分多字节字符。(如果多字节字符具有状态相关编码,则可能会导致冗余的移位序列。)
- p
- 参数应是指向
void
的指针。指针的值将以实现定义的方式转换为可打印字符序列。
- n
- 参数应是指向带符号整数的指针,其中写入此调用到
fprintf
时迄今为止写入输出流的字符数。没有转换参数,但消耗了一个参数。如果转换规范包含任何标志、字段宽度或精度,则行为未定义。
- %
- 一个%字符被写入。没有转换参数。完整的转换规范应为%%.
如果转换规范无效,则行为未定义。如果任何参数不符合相应的转换规范的正确类型,则行为未定义。
在任何情况下,不存在或小的字段宽度都不会导致字段截断;如果转换结果比字段宽度宽,则字段将扩展以包含转换结果。
对于一个和一个如果FLT_RADIX
是2的幂,则转换结果会正确舍入为具有给定精度的十六进制浮点数。
建议的做法是,如果FLT_RADIX
不是2的幂,则结果应为具有给定精度的十六进制浮点样式中的两个相邻数字之一,并额外规定误差应具有当前舍入方向的正确符号。
建议的做法是,对于e, E, f, F, g和G转换,如果有效小数位数最多为DECIMAL_DIG
,则结果应正确舍入。(对于二进制到十进制的转换,结果格式的值是可以用给定格式说明符表示的数字。有效数字的数量由格式说明符确定,在定点转换的情况下也由源值确定。)如果有效小数位数超过DECIMAL_DIG
,但源值可以用DECIMAL_DIG
位精确表示,则结果应为带有尾随零的精确表示。否则,源值由两个相邻的十进制字符串L < U界定,两者都具有DECIMAL_DIG位有效数字;结果十进制字符串D的值应满足L ≤ D ≤ U,并额外规定误差应具有当前舍入方向的正确符号。
fprintf
函数返回传输的字符数,如果发生输出或编码错误,则返回负值。
printf
函数等效于fprintf
,只不过在printf
的参数之前插入了参数stdout
。它返回传输的字符数,如果发生输出错误,则返回负值。
sprintf
函数等效于fprintf
,除了参数s
指定一个数组,生成的输入将写入该数组,而不是写入流。在写入的字符的末尾写入一个空字符;它不计入返回的总和。如果在重叠的对象之间进行复制,则行为未定义。该函数返回写入数组中的字符数,不包括终止的空字符。
vfprintf
函数等效于fprintf
,可变参数列表被arg
替换,arg
应由va_start
宏(以及可能随后的va_arg
调用)初始化。vfprintf
函数不调用va_end
宏。该函数返回传输的字符数,如果发生输出错误,则返回负值。
vprintf
函数等效于printf
,可变参数列表被arg
替换,arg
应由va_start
宏(以及可能随后的va_arg
调用)初始化。vprintf
函数不调用va_end
宏。该函数返回传输的字符数,如果发生输出错误,则返回负值。
vsprintf
函数等效于sprintf
,可变参数列表被arg
替换,arg
应由va_start
宏(以及可能随后的va_arg
调用)初始化。vsprintf
函数不调用va_end
宏。如果在重叠的对象之间进行复制,则行为未定义。该函数返回写入数组中的字符数,不包括终止的空字符。
- ↑ C99 §6.2.5/15