字符串输入输出
字符串的输入输出是 C 程序与外部世界交互的主要方式。标准库提供了多种函数:格式化输出 printf、格式化输入 scanf、字符串输出 puts、字符输出 putchar、行输入 fgets 等。理解这些函数的行为和限制,能帮助你安全地处理字符串输入输出,避免缓冲区溢出等安全问题。
输出函数
printf:格式化输出
char str[] = "Hello";
printf("%s\n", str); /* 输出字符串 */
printf("%10s\n", str); /* 右对齐,宽度 10 */
printf("%-10s\n", str); /* 左对齐,宽度 10 */
printf("%.3s\n", str); /* 只输出前 3 个字符:Hel */
puts:输出字符串并自动换行
puts("Hello"); /* 输出 Hello\n */
puts(str); /* 输出 str 内容 + \n */
puts 比 printf("%s\n", str) 稍快,因为不需要解析格式字符串。
putchar:输出单个字符
putchar('A'); /* 输出 A */
putchar('\n'); /* 输出换行 */
输入函数
scanf:格式化输入
char str[100];
scanf("%s", str); /* 读取一个单词(到空白字符为止) */
/* 危险:不检查 str 大小! */
/* 安全:限制长度 */
char str[100];
scanf("%99s", str); /* 最多读取 99 个字符,留 1 个给 '\0' */
scanf("%s") 遇到空白字符(空格、制表符、换行)停止,不读取换行符。
fgets:读取一行(安全)
char str[100];
if (fgets(str, sizeof(str), stdin) != NULL) {
/* 移除末尾的换行符 */
size_t len = strlen(str);
if (len > 0 && str[len - 1] == '\n')
str[len - 1] = '\0';
printf("Read: %s\n", str);
}
fgets 读取最多 n-1 个字符,自动添加 \0。如果读取到换行符或文件结束,会提前停止。这是读取字符串最安全的方式。
getchar:读取单个字符
int c;
while ((c = getchar()) != '\n' && c != EOF) {
/* 处理字符 */
}
注意 getchar 返回 int 而非 char,因为需要区分 EOF(通常是 -1)和有效字符 255。
已废弃的函数
gets:已移除(C11),极其危险
char str[100];
gets(str); /* 不检查缓冲区大小,已移除! */
gets 不限制读取长度,是缓冲区溢出攻击的经典目标。永远不要使用 gets,用 fgets 替代。
格式化字符串函数
sprintf:格式化到字符串(不安全)
char buffer[100];
int n = 123;
sprintf(buffer, "The number is %d", n); /* 不检查 buffer 大小 */
snprintf:安全格式化(C99)
char buffer[100];
int n = 123;
snprintf(buffer, sizeof(buffer), "The number is %d", n);
/* 最多写入 sizeof(buffer)-1 字符 + '\0' */
/* 如果超出,自动截断并添加 '\0' */
snprintf 是 C99 引入的安全函数,强烈推荐使用。
常见错误
scanf 不检查大小:
char str[10];
scanf("%s", str); /* 危险:输入超过 9 字符会溢出 */
/* 正确 */
scanf("%9s", str); /* 限制为 9 字符 + '\0' */
fgets 保留换行符:
char str[100];
fgets(str, sizeof(str), stdin);
printf("[%s]\n", str); /* 可能输出 [Hello\n] */
/* 正确:移除换行符 */
size_t len = strlen(str);
if (len > 0 && str[len - 1] == '\n')
str[len - 1] = '\0';
混淆 scanf 和 fgets:
int n;
scanf("%d", &n); /* 读取数字,但换行符留在输入缓冲区 */
char str[100];
fgets(str, sizeof(str), stdin); /* 读取到换行符,可能只读到空行! */
/* 正确:清除输入缓冲区 */
int c;
while ((c = getchar()) != '\n' && c != EOF) { }
fgets(str, sizeof(str), stdin);
最佳实践
- 字符串输入用
fgets,不用gets或不限制长度的scanf - 格式化输出用
snprintf,不用sprintf scanf("%s")始终指定最大宽度fgets后检查并移除末尾换行符- 混合使用
scanf和fgets时,注意清理输入缓冲区 - 始终检查输入函数的返回值(NULL 或 EOF 表示失败)