跳转到内容

C 编程/stdio.h

来自维基教科书,开放书籍,构建开放世界

C 编程语言为文件输入和输出提供了许多标准库函数。这些函数构成了 C 标准库头文件 <stdio.h> 的大部分内容。

C 的 I/O 功能按现代标准来说是相当底层的;C 将所有文件操作抽象成字节流操作,这些字节流可以是“输入流”或“输出流”。与一些早期的编程语言不同,C 不直接支持随机访问数据文件;要从文件中间的一个记录中读取数据,程序员必须创建一个流,定位到文件的中间,然后从流中按顺序读取字节。

流式文件 I/O 模型是由 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 从流中读取

[编辑 | 编辑源代码]

fgetc 函数用于从流中读取一个字符。

int fgetc(FILE *fp);

如果成功,fgetc 返回流中的下一个字节或字符(取决于文件是“二进制”还是“文本”,如上文 fopen 中所述)。如果失败,fgetc 返回 EOF。(可以使用 ferrorfeof 与文件指针一起确定具体的错误类型。)

标准宏 getc 也在 <stdio.h> 中定义,其行为与 fgetc 几乎相同,只是它是一个宏,因此可能会多次评估其参数。

标准函数 getchar 也在 <stdio.h> 中定义,它不接受任何参数,等效于 getc(stdin)

EOF 陷阱

[编辑 | 编辑源代码]

使用 fgetcgetcgetchar 时的一个错误是在将结果与 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 值匹配。另一方面,如果 EOFchar 的范围内,这保证了 EOF 和 char 值之间存在冲突。因此,无论系统类型如何定义,在测试 EOF 时永远不要使用 char 类型。

intchar 大小相同的系统上(即,与至少满足 POSIX 和 C99 标准的系统不兼容的系统),即使是“好的”示例也会受到 EOF 和某个字符的值无法区分的影响。处理这种情况的正确方法是在 getchar 返回 EOF 后检查 feofferror。如果 feof 指示文件结束尚未到达,并且 ferror 指示没有发生错误,那么 getchar 返回的 EOF 可以被认为代表一个实际的字符。这些额外的检查很少进行,因为大多数程序员假设他们的代码永远不需要在这些“大 char”系统上运行。另一种方法是使用编译时断言来确保 UINT_MAX > UCHAR_MAX,这至少可以防止带有这种假设的程序在这样的系统上编译。

在 C 编程语言中,freadfwrite 函数分别提供输入和输出的文件操作。freadfwrite<stdio.h> 中声明。

使用 fwrite 写入文件

[编辑 | 编辑源代码]

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 写入流

[编辑 | 编辑源代码]

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;
}

参考文献

[编辑 | 编辑源代码]
  1. C99 §6.2.5/15
华夏公益教科书