Skip to content

C 输入与输出

输入 (Input) 指向程序提供数据,通常来自键盘、文件或网络连接。输出 (Output) 指从程序呈现数据,通常输出到屏幕、文件或网络。

C 语言提供了一个标准库 (stdio.h),其中包含用于执行输入和输出 (I/O) 操作的函数。

C 程序启动时,会自动打开三个标准 I/O 流:

流名称文件指针典型设备与用途
标准输入stdin通常连接到键盘。用于读取输入。
标准输出stdout通常连接到屏幕/终端。用于显示正常输出。
标准错误stderr通常连接到屏幕/终端。用于显示错误消息。

这些流由文件指针 (FILE *) 表示,提供了一个一致的接口用于与这些标准设备交互。

这些函数一次处理一个字符的 I/O。

  • int getchar(void): 从 stdin 读取下一个可用字符并将其作为 int 返回。在文件结束或出错时返回 EOF(一个特殊的整型常量,通常是 -1)。

  • int putchar(int c): 将字符 c(转换为 unsigned char)写入 stdout,并返回写入的字符;出错时返回 EOF。

示例:

#include <stdio.h>
int main(void) {
int c;
printf("Enter a character: ");
c = getchar(); // 读取一个字符
// 在使用 putchar() 之前检查 getchar() 是否成功
if (c != EOF) {
printf("You entered: ");
putchar(c); // 写回字符
putchar('\n'); // 添加换行以更好地格式化
} else {
printf("\nEnd of input or error encountered.\n");
}
return 0;
}

运行时,该程序等待输入。如果你输入 hello 并按回车,它只会读取第一个字符 ‘h’:

Enter a character: hello
You entered: h

这些函数一次处理一行的 I/O。

  • char *fgets(char *str, int size, FILE *stream): 从指定的 stream(例如 stdin)读取一行到缓冲区 str。它在读取 size - 1 个字符后,或者读取到换行符 (\n) 时,或者到达文件结束时停止。关键是,fgets 是安全的,因为它通过遵守提供的 size 来防止缓冲区溢出。如果读取了换行符并且空间允许,它会存储该换行符,并且总是以空字符 (\0) 结束字符串。成功时返回 str,出错或在未读取任何字符时到达文件结束返回 NULL。

  • int puts(const char *s): 将字符串 s 写入 stdout,后跟一个换行符。成功时返回非负值,出错时返回 EOF。

重要安全说明: 避免使用遗留的 gets() 函数。它不执行边界检查,很容易导致缓冲区溢出,这是一个严重的安全漏洞。务必始终使用 fgets() 代替 gets() 来读取字符串。

使用 fgets 和 puts 的示例:

#include <stdio.h>
#include <string.h> // 用于 strcspn,如果需要移除换行符
#define BUFFER_SIZE 100
int main(void) {
char str[BUFFER_SIZE];
printf("Enter a line of text: ");
// 使用 fgets 安全地读取一行
if (fgets(str, BUFFER_SIZE, stdin) != NULL) {
// 可选:如果 fgets 存储了尾随的换行符,则将其移除
// strcspn 查找拒绝字符串(此处仅为换行符)中任何字符的第一次出现位置
str[strcspn(str, "\n")] = 0;
printf("You entered: ");
puts(str); // puts 会添加一个自己的换行符
} else {
// 处理错误或 EOF
printf("\nFailed to read input or reached end-of-file.\n");
}
return 0;
}

运行时:

Enter a line of text: This is a test line.
You entered: This is a test line.

这些函数根据指定的格式字符串处理 I/O,允许你读取和写入各种数据类型。

  • int scanf(const char *format, ...): 根据 format 字符串从 stdin 读取格式化输入。后续参数是指向应存储读取数据的变量的指针。返回成功匹配并赋值的输入项数量,如果在任何转换之前输入失败则返回 EOF。

  • int printf(const char *format, ...): 根据 format 字符串将格式化输出写入 stdout。后续参数是要格式化并打印的值。返回打印的字符数,出错时返回负值。

format 字符串包含转换说明符 (conversion specifiers)(例如,%d 用于 int,%f 用于 float/double,%c 用于 char,%s 用于 string,%zu 用于 size_t),这些说明符决定了参数如何转换。这两个函数都有许多格式化选项。

示例:

#include <stdio.h>
#define MAX_NAME_LEN 50
int main(void) {
char name[MAX_NAME_LEN];
int age;
int items_read;
printf("Enter your name and age (e.g., Alice 30): ");
// 读取一个字符串(直到空白字符)和一个整型
// 对 %s 使用宽度限制以防止缓冲区溢出
// 检查 scanf 的返回值!
items_read = scanf("%49s %d", name, &age); // %49s 最多读取 49 个字符 + 空终止符
if (items_read == 2) { // 检查是否成功读取了两个项
printf("\nHello, %s! You are %d years old.\n", name, age);
} else if (items_read == 1) {
printf("\nError: Only read the name. Please enter name and age correctly.\n");
} else {
// 可能是 EOF 或匹配失败
printf("\nError: Failed to read input correctly.\n");
// 在此处清除输入缓冲区是个好习惯,如果需要重试输入
int c;
while ((c = getchar()) != '\n' && c != EOF) { /* 消费行剩余部分 */ }
}
return 0;
}

运行时:

Enter your name and age (e.g., Alice 30): Bob 25
Hello, Bob! You are 25 years old.

关于 scanf 的重要说明:

  • 空白符敏感性 (Whitespace Sensitivity): 大多数格式说明符(%d、%s、%f 等)会自动跳过前导空白符。然而,%c 不跳过空白符。

  • %s 在空白符处停止: %s 说明符会读取字符直到遇到任何空白符(空格、制表符、换行符)。如果你需要读取包含空格的完整行,请使用 fgets。

  • 安全性 (Security): 使用没有宽度限制的 %s(例如,例子中的 %49s)是危险的,如果输入长于缓冲区,它可能导致缓冲区溢出。务必指定最大宽度或使用 fgets。

  • 返回值 (Return Value): 务必检查 scanf 的返回值,以确保成功读取并赋值了预期的项数。输入可能与格式字符串不匹配,如果不检查将导致错误。

  • 输入缓冲区 (Input Buffer): 如果 scanf 匹配失败,问题输入通常会留在输入缓冲区中,可能对后续读取造成问题。存在清除缓冲区所需的技术。

  • 优先使用 fgets 先将整行输入读取到缓冲区中。这为你提供了更多控制,并避免了许多 scanf 的陷阱。

  • 使用 fgets 读取一行后,你可以使用 sscanf(scanf 的字符串版本)解析缓冲区,它提供了相同的格式化能力,但从字符串而不是 stdin 读取。这通常能实现更健壮的输入处理。

  • 务必检查 I/O 函数(fgets、scanf、printf、putchar、getchar 等)的返回值,以检测错误或意外情况(如文件结束)。

  • 使用 stderr 打印错误消息,因为 stdout 可能被重定向。

有关格式说明符和高级 I/O 函数的详细信息,请查阅 C 文档或资源,例如 printf(3) 和 scanf(3) 的 man pages。