C 编程/stdio.h
C 编程语言提供了许多用于文件输入和输出的标准库函数。这些函数构成了 C 标准库头文件<stdio.h>
的大部分内容。
C 的 I/O 功能按照现代标准来说是相当低级的;C 将所有文件操作抽象为对字节流的操作,这些字节流可以是“输入流”或“输出流”。与一些早期的编程语言不同,C 不直接支持随机访问数据文件;要从文件中间的记录读取数据,程序员必须创建一个流,定位到文件中间,然后从流中按顺序读取字节。
流式文件 I/O 模型是由 Unix 操作系统推广的,Unix 与 C 编程语言本身同时开发。绝大多数现代操作系统都从 Unix 继承了流,而 C 编程语言家族中的许多语言都继承了 C 的文件 I/O 接口,几乎没有改动(例如,PHP)。C++ 标准库在其语法中反映了“流”的概念;参见 iostream。
大多数 C 文件输入/输出函数在以下文件中定义:/stdio.h (/cstdio(在 C++ 中的头文件)。
- 文件访问
- fopen- 打开一个文件
- freopen- 使用现有流打开不同的文件
- fflush- 将输出流与实际文件同步
- fclose- 关闭文件
- setbuf- 设置文件流的缓冲区
- setvbuf- 设置文件流的缓冲区及其大小
- fwide- 在宽字符 I/O 和窄字符 I/O 之间切换文件流
- 直接输入/输出
- fread- 从文件读取
- fwrite- 写入文件
- 非格式化输入/输出
-
- 窄字符
- fgetc, getc- 从文件流读取一个字符
- fgets- 从文件流读取一个字符串
- fputc, putc- 将一个字符写入文件流
- fputs- 将一个字符串写入文件流
- getchar- 从 stdin 读取一个字符
- gets- 从 stdin 读取一个字符串
- putchar- 将一个字符写入 stdout
- puts- 将一个字符串写入 stdout
- ungetc- 将一个字符放回文件流
- 宽字符
- fgetwc, getwc- 从文件流读取一个宽字符
- fgetws- 从文件流读取一个宽字符串
- fputwc, putwc- 将一个宽字符写入文件流
- fputws- 将一个宽字符串写入文件流
- getwchar- 从 stdin 读取一个宽字符
- putwchar- 将一个宽字符写入 stdout
- ungetwc- 将一个宽字符放回文件流
- 格式化输入/输出
-
- 窄字符
- scanf, fscanf, sscanf- 从 stdin、文件流或缓冲区读取格式化输入
- vscanf, vfscanf, wsscanf- 使用可变参数列表从 stdin、文件流或缓冲区读取格式化输入
- printf, fprintf, sprintf, snprintf- 将格式化输出打印到 stdout、文件流或缓冲区
- vprintf, vfprintf, vsprintf, vsnprintf- 使用可变参数列表将格式化输出打印到 stdout、文件流或缓冲区
- 宽字符
- wscanf, fwscanf, swscanf- 从 stdin、文件流或缓冲区读取格式化的宽字符输入
- vwscanf, vfwscanf, vswscanf- 使用可变参数列表从 stdin、文件流或缓冲区读取格式化的宽字符输入
- wprintf, fwprintf, swprintf- 将格式化的宽字符输出打印到 stdout、文件流或缓冲区
- vwprintf, vfwprintf, vswprintf- 使用可变参数列表将格式化的宽字符输出打印到 stdout、文件流或缓冲区
- 文件定位
- ftell- 返回当前文件位置指示器
- fgetpos- 获取文件位置指示器
- fseek- 将文件位置指示器移动到文件中的特定位置
- fsetpos- 将文件位置指示器移动到文件中的特定位置
- rewind- 将文件位置指示器移动到文件开头
- 错误处理
- clearerr- 清除错误
- feof- 检查文件结尾
- ferror- 检查文件错误
- perror- 将对应于当前错误的字符串显示到 stderr
- 文件操作
- remove- 擦除文件
- rename- 重命名文件
- tmpfile- 返回指向临时文件的指针
- tmpnam- 返回唯一的文件名
fgetc
函数用于从流中读取一个字符。
int fgetc(FILE *fp);
如果成功,fgetc
将返回流中的下一个字节或字符(取决于文件是“二进制”还是“文本”,如上文 fopen
中所述)。如果失败,fgetc 将返回 EOF
。(可以使用文件指针调用 ferror
或 feof
来确定特定的错误类型。)
标准宏 getc
也在 <stdio.h>
中定义,其行为与 fgetc
几乎相同,只是作为宏,它可能会多次计算其参数。
标准函数 getchar
也在 <stdio.h>
中定义,它不接受任何参数,并且等效于 getc(stdin)
。
使用 fgetc
、getc
或 getchar
时,一个错误是在与 EOF
比较之前将结果分配给 char
类型的变量。以下代码片段展示了这种错误,然后显示了正确的方法(使用 int
类型)
错误 | 更正 |
---|---|
char c;
while ((c = getchar()) != EOF)
putchar(c);
|
int c; /* This will hold the EOF value */
while ((c = getchar()) != EOF)
putchar(c);
|
假设一个系统中 char
类型为 8 位宽,表示 256 个不同的值。getchar
可能返回 256 个可能的字符中的任何一个,它也可能返回 EOF
来表示文件结尾,总共 257 个不同的可能返回值。
当 getchar
的结果分配给 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
,这至少可以阻止带有这种假设的程序在这样的系统上编译。
在 C 编程语言中,fread
和 fwrite
函数分别提供文件输入和输出操作。fread
和 fwrite
在 <stdio.h>
中声明。
fwrite 定义为
size_t fwrite (const void *array, size_t size, size_t count, FILE *stream);
fwrite
函数将一块数据写入流。它将写入 count
个元素的数组到流中的当前位置。对于每个元素,它将写入 size
个字节。流的位置指示器将根据成功写入的字节数进行调整。
该函数将返回成功写入的元素数量。如果写入成功完成,返回值将等于 count
。如果发生写入错误,返回值将小于 count
。
以下程序打开一个名为sample.txt的文件,向文件写入一串字符,然后关闭文件。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
size_t count;
const char *str = "hello\n";
fp = fopen("sample.txt", "w");
if(fp == NULL) {
perror("failed to open sample.txt");
return EXIT_FAILURE;
}
count = fwrite(str, 1, strlen(str), fp);
printf("Wrote %zu bytes. fclose(fp) %s.\n", count, fclose(fp) == 0 ? "succeeded" : "failed");
return EXIT_SUCCESS;
}
fputc
函数用于将一个字符写入流。
int fputc(int c, FILE *fp);
参数 c
在输出之前会静默地转换为 unsigned char
。如果成功,fputc
将返回写入的字符。如果失败,fputc 将返回 EOF
。
标准宏 putc
也是在 <stdio.h>
中定义的,它的行为几乎与 fputc
相同,除了它是一个宏,它可能会多次计算其参数。
标准函数 putchar
也在 <stdio.h>
中定义,它只接受第一个参数,等效于 putc(c, stdout)
,其中 c
是该参数。
以下 C 程序打开一个名为 myfile 的二进制文件,从中读取五个字节,然后关闭该文件。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
const int count = 5; /* count of bytes to read from file */
char buffer[count] = {0}; /* initialized to zeroes */
int i, rc;
FILE *fp = fopen("myfile", "rb");
if (fp == NULL) {
perror("Failed to open file \"myfile\"");
return EXIT_FAILURE;
}
for (i = 0; (rc = getc(fp)) != EOF && i < count; buffer[i++] = rc)
;
fclose(fp);
if (i == count) {
puts("The bytes read were...");
for (i = 0; i < count; i++)
printf("%x ", buffer[i]);
puts("");
} else
fputs("There was an error reading the file.\n", stderr);
return EXIT_SUCCESS;
}
- ↑ C99 §6.2.5/15