C 输入与输出
C - 输入与输出 (I/O)
Section titled “C - 输入与输出 (I/O)”输入 (Input) 指向程序提供数据,通常来自键盘、文件或网络连接。输出 (Output) 指从程序呈现数据,通常输出到屏幕、文件或网络。
C 语言提供了一个标准库 (stdio.h),其中包含用于执行输入和输出 (I/O) 操作的函数。
标准 I/O 流
Section titled “标准 I/O 流”C 程序启动时,会自动打开三个标准 I/O 流:
| 流名称 | 文件指针 | 典型设备与用途 |
|---|---|---|
| 标准输入 | stdin | 通常连接到键盘。用于读取输入。 |
| 标准输出 | stdout | 通常连接到屏幕/终端。用于显示正常输出。 |
| 标准错误 | stderr | 通常连接到屏幕/终端。用于显示错误消息。 |
这些流由文件指针 (FILE *) 表示,提供了一个一致的接口用于与这些标准设备交互。
字符 I/O: getchar() 与 putchar()
Section titled “字符 I/O: getchar() 与 putchar()”这些函数一次处理一个字符的 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: helloYou entered: h行 I/O: fgets() 与 puts()
Section titled “行 I/O: fgets() 与 puts()”这些函数一次处理一行的 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: scanf() 与 printf()
Section titled “格式化 I/O: scanf() 与 printf()”这些函数根据指定的格式字符串处理 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匹配失败,问题输入通常会留在输入缓冲区中,可能对后续读取造成问题。存在清除缓冲区所需的技术。
输入处理的最佳实践
Section titled “输入处理的最佳实践”-
优先使用
fgets先将整行输入读取到缓冲区中。这为你提供了更多控制,并避免了许多scanf的陷阱。 -
使用
fgets读取一行后,你可以使用sscanf(scanf的字符串版本)解析缓冲区,它提供了相同的格式化能力,但从字符串而不是stdin读取。这通常能实现更健壮的输入处理。 -
务必检查 I/O 函数(
fgets、scanf、printf、putchar、getchar等)的返回值,以检测错误或意外情况(如文件结束)。 -
使用
stderr打印错误消息,因为stdout可能被重定向。
有关格式说明符和高级 I/O 函数的详细信息,请查阅 C 文档或资源,例如 printf(3) 和 scanf(3) 的 man pages。