首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C 语言字符串输入输出:scanf, gets, fgets 的选择与陷阱

C 语言字符串输入输出:scanf, gets, fgets 的选择与陷阱

原创
作者头像
用户12619587
发布2026-07-13 12:37:13
发布2026-07-13 12:37:13
140
举报

在 C 语言编程中,处理字符串是常见的任务之一。而将字符串从用户(通常通过键盘)读取到程序中,以及将程序中的字符串显示给用户,是字符串处理的基础。本文将深入探讨 C 语言中用于字符串输入输出的几个核心函数:printf, scanf, gets, 和 fgets,并通过详细的代码示例,帮助您理解它们的用法、特性以及潜在的风险,特别是如何安全地进行字符串输入。

1. 字符串输出:printf 的 %s

首先,我们来看字符串的输出。在 C 语言中,我们主要使用 printf 函数配合 %s 格式说明符来打印字符串。%s 格式说明符期望接收一个字符数组(也就是字符串)的起始地址,然后从该地址开始,逐个打印字符,直到遇到字符串的结束标志 \0 为止。

代码语言:plain
复制
#include <stdio.h>

int main() {
    printf("--- 字符串输出示例 ---\n");

    char my_string[] = "Hello, C Language!"; // 使用字符串字面量初始化,编译器自动添加 '\0'

    printf("使用 %%s 打印字符串:\n");
    printf("%s\n", my_string); // %s 遇到 '\0' 停止打印

    printf("--- 示例结束 ---\n");

    return 0;
}

编译和运行:

代码语言:plain
复制
gcc print_string.c -o print_string
./print_string

运行结果:

代码语言:plain
复制
--- 字符串输出示例 ---
使用 %s 打印字符串:
Hello, C Language!
--- 示例结束 ---

printf("%s", ...) 的使用相对简单,只要确保您传递的是一个以 \0 结尾的有效 C 语言字符串即可。

2. 字符串输入:挑战与不同函数

字符串输入相对复杂,因为用户可能输入包含空格的文本,而不同的输入函数处理空格和换行符的方式不同。

2.1 scanf 函数与 %s

scanf 函数是 C 语言中常用的输入函数,配合 %s 格式说明符可以读取字符串。然而,scanf 在读取字符串时有一个非常重要的特性:它会跳过开头的空白字符(空格、制表符、换行符),然后从第一个非空白字符开始读取,直到遇到下一个空白字符为止。这意味着 scanf("%s", ...) 只能读取单个词,无法读取包含空格的整行文本。

此外,scanf 不会检查你提供的字符数组缓冲区的大小,如果用户输入的字符串长度超过了缓冲区的大小,就会发生缓冲区溢出,这是非常危险的。虽然可以通过 %ns (其中 n 是数字) 来限制读取的最大字符数(如 %9s 最多读 9 个字符 + \0),但这种方式仍然不够灵活和安全。

示例代码 2.1:

代码语言:plain
复制
#include <stdio.h>

int main() {
    printf("--- 使用 scanf 读取字符串 ---\n");

    char buffer[20]; // 定义一个大小为 20 的字符数组

    printf("请输入一个字符串 (包含空格测试):\n");
    // 使用 %s 读取字符串
    // 警告:scanf %s 不检查缓冲区大小,且遇到空格停止
    scanf("%s", buffer);

    printf("scanf 读取到的字符串是:\n");
    printf("%s\n", buffer);

    printf("--- 示例结束 ---\n");

    return 0;
}

编译和运行:

代码语言:plain
复制
gcc scanf_string.c -o scanf_string
./scanf_string

输入测试及运行结果:

  • 输入:Hello输出:
代码语言:plain
复制
--- 使用 scanf 读取字符串 ---
请输入一个字符串 (包含空格测试):
Hello
scanf 读取到的字符串是:
Hello
--- 示例结束 ---

解释:输入不含空格,scanf 正常读取 "Hello",并在 buffer 末尾添加 \0

  • 输入:Hello World输出:
代码语言:plain
复制
--- 使用 scanf 读取字符串 ---
请输入一个字符串 (包含空格测试):
Hello World
scanf 读取到的字符串是:
Hello
--- 示例结束 ---

解释:scanf 遇到 "Hello" 后面的空格就停止读取了,只将 "Hello" 存入 buffer。剩下的 " World" 仍然留在输入缓冲区中,可能会影响后续的输入操作。

  • 输入: (输入超过 19 个字符,例如输入 25个 'A') AAAAAAAAAAAAAAAAAAAAAAAAA输出: (程序可能会崩溃或产生不可预测的行为,因为发生了缓冲区溢出)
代码语言:plain
复制
--- 使用 scanf 读取字符串 ---
请输入一个字符串 (包含空格测试):
AAAAAAAAAAAAAAAAAAAAAAAAA
scanf 读取到的字符串是:
AAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped) <-- 或者其他错误
--- 示例结束 ---

解释:用户输入超过 buffer 大小(20,需留一个位置给 \0,所以最多存 19 个字符)。scanf 没有检查,直接将超出的字符写到了 buffer 后面的内存区域,导致缓冲区溢出,破坏了其他数据或程序结构,引发了错误。

总结 scanf("%s", ...)

  • 优点: 简单易用,适合读取单个词或不含空格的字符串。
  • 缺点:
  • 遇到空格停止读取,无法读取整行文本。
  • 存在严重的缓冲区溢出风险,不安全。

2.2 gets 函数 (已废弃,请勿使用!)

gets 函数用于从标准输入读取一行文本,直到遇到换行符或文件结束符为止。它会将换行符替换为 \0,并将读取到的字符串存储到指定的字符数组中。与 scanf 不同,gets 会读取包含空格的整行文本。

然而,gets 函数最大的问题在于它无法限制读取的字符数量,它不知道目标缓冲区有多大。如果用户输入的字符串超过了缓冲区的容量,gets 就会无情地继续写入,导致严重的缓冲区溢出。这种漏洞极易被恶意攻击者利用,造成程序崩溃、数据泄露甚至远程代码执行。

正因为其固有的安全性缺陷,gets 函数在 C99 标准中已经被标记为“不推荐使用”,在 C11 标准中已经被移除。现代 C 编程应绝对避免使用 gets 函数

示例代码 2.2 (仅为演示其行为和风险,请勿在实际项目中使用!):

代码语言:plain
复制
#include <stdio.h>

int main() {
    printf("--- 使用 gets 读取字符串 (危险!) ---\n");

    char buffer[20]; // 定义一个大小为 20 的字符数组

    printf("请输入一个字符串 (gets 会读取整行):\n");
    // 警告:gets 函数不安全,存在缓冲区溢出风险!
    gets(buffer); // !!! 危险操作,不推荐使用 !!!

    printf("gets 读取到的字符串是:\n");
    printf("%s\n", buffer);

    printf("--- 示例结束 ---\n");

    return 0;
}

编译和运行: (某些现代编译器可能会警告或拒绝编译包含 gets 的代码)

代码语言:plain
复制
gcc gets_string_dangerous.c -o gets_string_dangerous
# 如果编译器警告,你可能需要添加选项,例如 -Wno-deprecated-declarations (不建议这样做)
# 或者使用旧的编译器版本
./gets_string_dangerous

输入测试及运行结果:

  • 输入:Hello World输出:
代码语言:plain
复制
--- 使用 gets 读取字符串 (危险!) ---
请输入一个字符串 (gets 会读取整行):
Hello World
gets 读取到的字符串是:
Hello World
--- 示例结束 ---

解释:gets 读取了整行文本,包括空格,直到遇到换行符。

  • 输入: (输入超过 19 个字符,例如输入 25个 'B') BBBBBBBBBBBBBBBBBBBBBBBBB输出: (程序极可能崩溃或产生严重错误)
代码语言:plain
复制
--- 使用 gets 读取字符串 (危险!) ---
请输入一个字符串 (gets 会读取整行):
BBBBBBBBBBBBBBBBBBBBBBBBB
gets 读取到的字符串是:
BBBBBBBBBBBBBBBBBBBBBBBBB
Segmentation fault (core dumped) <-- 极可能发生的错误
--- 示例结束 ---

解释:用户输入超过 buffer 大小,gets 不会停止,导致严重的缓冲区溢出和程序崩溃。

总结 gets

  • 优点: 可以读取包含空格的整行文本 (这是它被设计出来的目的)。
  • 缺点:
  • 存在严重的、无法避免的缓冲区溢出风险。
  • 已从 C 标准中移除,不应使用。

2.3 fgets 函数 (推荐!)

fgets 函数是用于从文件流中读取字符串的函数。它的原型是 char *fgets(char *s, int size, FILE *stream);

  • s: 指向存储读取到的字符串的字符数组(缓冲区)。
  • size: 指定要读取的最大字符数(包括 \0)。fgets 最多会读取 size - 1 个字符,或者直到遇到换行符或文件结束符为止。
  • stream: 要读取的文件流。对于标准输入(键盘),我们使用 stdin

fgets 的关键优点在于它通过 size 参数限制了读取的数量,从而有效防止了缓冲区溢出。它是从标准输入安全读取一行文本的首选方法。

fgets 会将读取到的换行符 (\n) 包含在字符串中(除非读取的字符数达到 size-1 且换行符还没出现)。如果需要去除末尾的换行符,需要手动处理。

示例代码 2.3:

代码语言:plain
复制
#include <stdio.h>
#include <string.h> // 使用 strlen 函数需要包含此头文件

int main() {
    printf("--- 使用 fgets 读取字符串 (推荐!) ---\n");

    char buffer[20]; // 定义一个大小为 20 的字符数组

    printf("请输入一个字符串 (fgets 会读取整行,最多 19 个字符):\n");
    // 使用 fgets 从标准输入 (stdin) 读取,最多读取 19 个字符 (+ '\0')
    // fgets 会将换行符也读入缓冲区 (如果缓冲区有空间)
    fgets(buffer, sizeof(buffer), stdin);

    printf("fgets 读取到的原始字符串是:\n");
    printf("%s", buffer); // 注意这里使用 %s 打印,fgets 读入的换行符会在这里体现

    // **可选:移除末尾的换行符**
    // 检查最后一个非 '\0' 字符是否是 '\n'
    size_t len = strlen(buffer);
    if (len > 0 && buffer[len - 1] == '\n') {
        buffer[len - 1] = '\0'; // 用 '\0' 替换换行符
    }

    printf("\n移除换行符后的字符串是:\n");
    printf("%s\n", buffer); // 再次打印

    printf("--- 示例结束 ---\n");

    return 0;
}

编译和运行:

代码语言:plain
复制
gcc fgets_string_safe.c -o fgets_string_safe
./fgets_string_safe

输入测试及运行结果:

  • 输入:Hello World输出:
代码语言:plain
复制
--- 使用 fgets 读取字符串 (推荐!) ---
请输入一个字符串 (fgets 会读取整行,最多 19 个字符):
Hello World
fgets 读取到的原始字符串是:
Hello World

移除换行符后的字符串是:
Hello World
--- 示例结束 ---

解释:fgets 读取了 "Hello World" 和用户按下的换行符 (\n),并将它们都存入 buffer。原始打印时包含了换行符,所以 "移除换行符后的字符串是:" 出现在下一行。移除换行符后,再次打印就正常了。

  • 输入: (输入超过 19 个字符,例如输入 25个 'C') CCCCCCCCCCCCCCCCCCCCCCCCC输出:
代码语言:plain
复制
--- 使用 fgets 读取字符串 (推荐!) ---
请输入一个字符串 (fgets 会读取整行,最多 19 个字符):
CCCCCCCCCCCCCCCCCCCCCCCCC
fgets 读取到的原始字符串是:
CCCCCCCCCCCCCCCCCCC
移除换行符后的字符串是:
CCCCCCCCCCCCCCCCCCC
--- 示例结束 ---

解释:尽管用户输入了很多字符,fgets 只读取了前 19 个字符(因为缓冲区大小是 20,最多读 19 个字符 + \0),并在末尾添加了 \0。用户的换行符以及后面的字符被留在了输入缓冲区中。程序安全地运行,没有发生溢出。

总结 fgets

  • 优点:
  • 可以安全地读取包含空格的整行文本。
  • 通过 size 参数防止缓冲区溢出,非常安全。
  • 是推荐的 C 语言字符串输入函数。
  • 缺点:
  • 会读取并包含末尾的换行符(如果缓冲区有空间),有时需要手动移除。
  • 如果输入的行太长,超过了缓冲区大小,剩余的字符会留在输入缓冲区中,需要额外处理(例如清空缓冲区)以避免影响后续的输入操作。

3. 输入函数对比总结

函数

格式符/参数

读取行为

遇到空格?

遇到换行符?

安全性 (缓冲区溢出)

推荐度

scanf

%s

跳过空白,读到空白

停止读取

停止读取

不安全

gets

buffer

读到换行符或 EOF

读取

读取并替换为 \0

极不安全 (已移除)

绝不使用

fgets

buffer, size, stream

读到 size-1 字符,或换行符,或 EOF

读取

读取 (通常包含在内)

安全

4. 结论

在 C 语言中进行字符串输入输出时:

  • 使用 printf("%s", ...) 进行字符串输出。
  • 对于需要读取单个不含空格的“词”时,可以使用 scanf("%s", ...),但要注意潜在的缓冲区溢出风险,最好使用 %ns 限制读取数量。
  • 绝不要使用 gets 函数,因为它存在严重的安全性问题。
  • 对于需要读取包含空格的整行文本时,强烈推荐使用 fgets(buffer, size, stdin)。它通过限制读取数量有效防止了缓冲区溢出,是最安全的标准输入函数。使用 fgets 后,可能需要手动处理读取到的末尾换行符。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 字符串输出:printf 的 %s
  • 2. 字符串输入:挑战与不同函数
    • 2.1 scanf 函数与 %s
    • 2.2 gets 函数 (已废弃,请勿使用!)
    • 2.3 fgets 函数 (推荐!)
  • 3. 输入函数对比总结
  • 4. 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档