跳转到内容

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 从流中读取

[编辑 | 编辑源代码]

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 的范围内,则这保证了 EOFchar 值之间的冲突。因此,无论系统类型如何定义,在测试 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
华夏公益教科书