在 C 语言编程中,处理字符串是常见的任务之一。而将字符串从用户(通常通过键盘)读取到程序中,以及将程序中的字符串显示给用户,是字符串处理的基础。本文将深入探讨 C 语言中用于字符串输入输出的几个核心函数:printf, scanf, gets, 和 fgets,并通过详细的代码示例,帮助您理解它们的用法、特性以及潜在的风险,特别是如何安全地进行字符串输入。
printf 的 %s首先,我们来看字符串的输出。在 C 语言中,我们主要使用 printf 函数配合 %s 格式说明符来打印字符串。%s 格式说明符期望接收一个字符数组(也就是字符串)的起始地址,然后从该地址开始,逐个打印字符,直到遇到字符串的结束标志 \0 为止。
#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;
}编译和运行:
gcc print_string.c -o print_string
./print_string运行结果:
--- 字符串输出示例 ---
使用 %s 打印字符串:
Hello, C Language!
--- 示例结束 ---printf("%s", ...) 的使用相对简单,只要确保您传递的是一个以 \0 结尾的有效 C 语言字符串即可。
字符串输入相对复杂,因为用户可能输入包含空格的文本,而不同的输入函数处理空格和换行符的方式不同。
scanf 函数与 %sscanf 函数是 C 语言中常用的输入函数,配合 %s 格式说明符可以读取字符串。然而,scanf 在读取字符串时有一个非常重要的特性:它会跳过开头的空白字符(空格、制表符、换行符),然后从第一个非空白字符开始读取,直到遇到下一个空白字符为止。这意味着 scanf("%s", ...) 只能读取单个词,无法读取包含空格的整行文本。
此外,scanf 不会检查你提供的字符数组缓冲区的大小,如果用户输入的字符串长度超过了缓冲区的大小,就会发生缓冲区溢出,这是非常危险的。虽然可以通过 %ns (其中 n 是数字) 来限制读取的最大字符数(如 %9s 最多读 9 个字符 + \0),但这种方式仍然不够灵活和安全。
示例代码 2.1:
#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;
}编译和运行:
gcc scanf_string.c -o scanf_string
./scanf_string输入测试及运行结果:
Hello输出:--- 使用 scanf 读取字符串 ---
请输入一个字符串 (包含空格测试):
Hello
scanf 读取到的字符串是:
Hello
--- 示例结束 ---解释:输入不含空格,scanf 正常读取 "Hello",并在 buffer 末尾添加 \0。
Hello World输出:--- 使用 scanf 读取字符串 ---
请输入一个字符串 (包含空格测试):
Hello World
scanf 读取到的字符串是:
Hello
--- 示例结束 ---解释:scanf 遇到 "Hello" 后面的空格就停止读取了,只将 "Hello" 存入 buffer。剩下的 " World" 仍然留在输入缓冲区中,可能会影响后续的输入操作。
AAAAAAAAAAAAAAAAAAAAAAAAA输出: (程序可能会崩溃或产生不可预测的行为,因为发生了缓冲区溢出)--- 使用 scanf 读取字符串 ---
请输入一个字符串 (包含空格测试):
AAAAAAAAAAAAAAAAAAAAAAAAA
scanf 读取到的字符串是:
AAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped) <-- 或者其他错误
--- 示例结束 ---解释:用户输入超过 buffer 大小(20,需留一个位置给 \0,所以最多存 19 个字符)。scanf 没有检查,直接将超出的字符写到了 buffer 后面的内存区域,导致缓冲区溢出,破坏了其他数据或程序结构,引发了错误。
总结 scanf("%s", ...):
gets 函数 (已废弃,请勿使用!)gets 函数用于从标准输入读取一行文本,直到遇到换行符或文件结束符为止。它会将换行符替换为 \0,并将读取到的字符串存储到指定的字符数组中。与 scanf 不同,gets 会读取包含空格的整行文本。
然而,gets 函数最大的问题在于它无法限制读取的字符数量,它不知道目标缓冲区有多大。如果用户输入的字符串超过了缓冲区的容量,gets 就会无情地继续写入,导致严重的缓冲区溢出。这种漏洞极易被恶意攻击者利用,造成程序崩溃、数据泄露甚至远程代码执行。
正因为其固有的安全性缺陷,gets 函数在 C99 标准中已经被标记为“不推荐使用”,在 C11 标准中已经被移除。现代 C 编程应绝对避免使用 gets 函数。
示例代码 2.2 (仅为演示其行为和风险,请勿在实际项目中使用!):
#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 的代码)
gcc gets_string_dangerous.c -o gets_string_dangerous
# 如果编译器警告,你可能需要添加选项,例如 -Wno-deprecated-declarations (不建议这样做)
# 或者使用旧的编译器版本
./gets_string_dangerous输入测试及运行结果:
Hello World输出:--- 使用 gets 读取字符串 (危险!) ---
请输入一个字符串 (gets 会读取整行):
Hello World
gets 读取到的字符串是:
Hello World
--- 示例结束 ---解释:gets 读取了整行文本,包括空格,直到遇到换行符。
BBBBBBBBBBBBBBBBBBBBBBBBB输出: (程序极可能崩溃或产生严重错误)--- 使用 gets 读取字符串 (危险!) ---
请输入一个字符串 (gets 会读取整行):
BBBBBBBBBBBBBBBBBBBBBBBBB
gets 读取到的字符串是:
BBBBBBBBBBBBBBBBBBBBBBBBB
Segmentation fault (core dumped) <-- 极可能发生的错误
--- 示例结束 ---解释:用户输入超过 buffer 大小,gets 不会停止,导致严重的缓冲区溢出和程序崩溃。
总结 gets:
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:
#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;
}编译和运行:
gcc fgets_string_safe.c -o fgets_string_safe
./fgets_string_safe输入测试及运行结果:
Hello World输出:--- 使用 fgets 读取字符串 (推荐!) ---
请输入一个字符串 (fgets 会读取整行,最多 19 个字符):
Hello World
fgets 读取到的原始字符串是:
Hello World
移除换行符后的字符串是:
Hello World
--- 示例结束 ---解释:fgets 读取了 "Hello World" 和用户按下的换行符 (\n),并将它们都存入 buffer。原始打印时包含了换行符,所以 "移除换行符后的字符串是:" 出现在下一行。移除换行符后,再次打印就正常了。
CCCCCCCCCCCCCCCCCCCCCCCCC输出:--- 使用 fgets 读取字符串 (推荐!) ---
请输入一个字符串 (fgets 会读取整行,最多 19 个字符):
CCCCCCCCCCCCCCCCCCCCCCCCC
fgets 读取到的原始字符串是:
CCCCCCCCCCCCCCCCCCC
移除换行符后的字符串是:
CCCCCCCCCCCCCCCCCCC
--- 示例结束 ---解释:尽管用户输入了很多字符,fgets 只读取了前 19 个字符(因为缓冲区大小是 20,最多读 19 个字符 + \0),并在末尾添加了 \0。用户的换行符以及后面的字符被留在了输入缓冲区中。程序安全地运行,没有发生溢出。
总结 fgets:
size 参数防止缓冲区溢出,非常安全。函数 | 格式符/参数 | 读取行为 | 遇到空格? | 遇到换行符? | 安全性 (缓冲区溢出) | 推荐度 |
|
| 跳过空白,读到空白 | 停止读取 | 停止读取 | 不安全 | 低 |
|
| 读到换行符或 EOF | 读取 | 读取并替换为 | 极不安全 (已移除) | 绝不使用 |
|
| 读到 | 读取 | 读取 (通常包含在内) | 安全 | 高 |
在 C 语言中进行字符串输入输出时:
printf("%s", ...) 进行字符串输出。scanf("%s", ...),但要注意潜在的缓冲区溢出风险,最好使用 %ns 限制读取数量。gets 函数,因为它存在严重的安全性问题。fgets(buffer, size, stdin)。它通过限制读取数量有效防止了缓冲区溢出,是最安全的标准输入函数。使用 fgets 后,可能需要手动处理读取到的末尾换行符。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。