fgets 是什么?
fgets 是 "file string get" 的缩写,它是一个 C 标准库函数,用于从指定的流(stream)中读取一行字符串。
与 scanf 或 gets 不同,fgets 的一个关键优点是可以防止缓冲区溢出,因为它允许你指定读取的最大字符数。
函数原型
char *fgets(char *str, int n, FILE *stream);
参数详解:
-
char *str:- 这是一个字符指针,指向一个字符数组(也就是缓冲区)。
fgets会将从流中读取的字符存入这个缓冲区。- 调用者必须负责提供一个足够大的内存空间。
-
int n:- 这是一个整数,表示最多要读取的字符数(包括结尾的换行符
\n和空字符\0)。 - 这是
fgets安全性的关键所在,它最多会读取n-1个字符,然后自动在第n个位置添加一个空字符\0来结束字符串。 - 如果一行中的字符数(包括
\n)超过了n-1,那么多余的字符会留在输入流中,等待下一次读取。
- 这是一个整数,表示最多要读取的字符数(包括结尾的换行符
-
FILE *stream:- 这是一个指向
FILE对象的指针,代表了你要读取的输入源。 - 当你想从键盘读取输入时,这个参数就是
stdin,它是在stdio.h中定义的标准输入流。
- 这是一个指向
返回值:
- 成功: 返回指向
str的指针(也就是你传入的第一个参数)。 - 失败或到达文件末尾: 返回
NULL指针,当发生错误(比如流发生错误)或遇到文件结束符(EOF, End-Of-File)时,fgets会返回NULL,在 Windows 上,你可以通过组合键Ctrl+Z然后按Enter来模拟输入 EOF;在 Linux/macOS 上,是Ctrl+D。
从 stdin 读取输入的基本用法
这是一个最简单的例子,从键盘读取一行输入并打印出来。
#include <stdio.h>
int main() {
char buffer[100]; // 定义一个足够大的缓冲区
printf("请输入一行文字: ");
// 从 stdin 读取最多 sizeof(buffer) - 1 个字符
// sizeof(buffer) 是 100,所以最多读取 99 个字符
// 第 100 个位置留给 '\0'
if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
// 成功读取
printf("你输入的内容是: %s", buffer);
} else {
// 读取失败或遇到 EOF
printf("读取输入失败或遇到文件结束符,\n");
}
return 0;
}
代码解释:
char buffer[100];:我们创建了一个可以容纳 100 个字符的数组。fgets(buffer, sizeof(buffer), stdin);:buffer:数据将存入这个数组。sizeof(buffer):计算出数组的大小是 100。fgets最多会读取 99 个字符。stdin:表示从键盘读取。
if (fgets(...) != NULL):这是一个良好的编程习惯,我们检查fgets是否成功执行,如果用户直接按Ctrl+Z(Windows) 或Ctrl+D(Linux/macOS),fgets会返回NULL,程序会进入else分支。printf("你输入的内容是: %s", buffer);:打印出读取到的内容。
一个非常重要的细节:换行符 \n 的处理
fgets 的一个行为与很多人直觉不同:它会保留输入行末尾的换行符 \n。
让我们看一个例子:
#include <stdio.h>
#include <string.h> // 用于 strlen
int main() {
char buffer[50];
printf("请输入你的名字: ");
fgets(buffer, sizeof(buffer), stdin);
// 使用 strlen 打印字符串的实际长度
printf("buffer 的内容: \"%s\"\n", buffer);
printf("buffer 的长度: %zu\n", strlen(buffer)); // %zu 用于打印 size_t 类型
return 0;
}
可能的输入和输出:
请输入你的名字: Alice
buffer 的内容: "Alice\n"
buffer 的长度: 6 // 'A','l','i','c','e','\n'
你会发现,buffer 的内容里包含了换行符 \n,这在大多数情况下都不是我们想要的,因为我们通常希望得到一个纯粹的字符串("Alice"),而不是 "Alice\n"。
如何去除换行符?
你需要手动检查并去除它,一个通用的方法是:
#include <stdio.h>
#include <string.h>
void remove_newline(char *str) {
// 查找换行符的位置
size_t len = strlen(str);
if (len > 0 && str[len - 1] == '\n') {
// 如果找到,就将它替换为字符串结束符 '\0'
str[len - 1] = '\0';
}
}
int main() {
char buffer[50];
printf("请输入你的名字: ");
fgets(buffer, sizeof(buffer), stdin);
remove_newline(buffer); // 调用函数去除换行符
printf("处理后的 buffer: \"%s\"\n", buffer);
printf("处理后的长度: %zu\n", strlen(buffer));
return 0;
}
现在运行结果:
请输入你的名字: Alice
处理后的 buffer: "Alice"
处理后的长度: 5
这个 remove_newline 函数非常实用,建议你将其加入自己的工具函数库中。
fgets 与 scanf 的对比
| 特性 | fgets |
scanf |
|---|---|---|
| 安全性 | 高,可以指定最大读取长度,防止溢出。 | 低,容易导致缓冲区溢出,且难以控制。 |
| 读取整行,包括空格,直到遇到换行符。 | 默认以空白字符(空格、Tab、换行)作为分隔符,无法直接读取带空格的字符串。 | |
| 换行符处理 | 会保留换行符 \n 在字符串中,需要手动去除。 |
不会保留换行符,换行符会留在输入流中,可能影响后续的输入。 |
| 主要用途 | 读取一行文本、命令行参数、配置文件等。 | 格式化输入,如读取整数、浮点数等。 |
为什么推荐使用 fgets?
在现代 C 语言编程中,当你需要从用户那里读取字符串时,强烈推荐使用 fgets。scanf 虽然灵活,但在处理字符串输入时非常脆弱,容易出错。fgets 提供了更可控、更安全的方式来处理输入。
完整示例:一个简单的循环读取器
这个例子展示了如何使用 fgets 创建一个循环,持续读取用户输入,直到用户输入 "exit" 或遇到文件结束符。
#include <stdio.h>
#include <string.h>
#include <ctype.h> // 用于 strcasecmp (非标准,但常用) 或自己实现不区分大小写的比较
#define BUFFER_SIZE 256
// 一个简单的去除换行符的函数
void remove_newline(char *str) {
size_t len = strlen(str);
if (len > 0 && str[len - 1] == '\n') {
str[len - 1] = '\0';
}
}
int main() {
char input[BUFFER_SIZE];
printf("--- 简单的回声程序 (输入 'exit' 退出) ---\n");
while (1) {
printf("> ");
// 读取输入
if (fgets(input, BUFFER_SIZE, stdin) == NULL) {
// 用户输入了 EOF (Ctrl+Z / Ctrl+D)
printf("\n检测到文件结束符,程序退出,\n");
break;
}
// 去除换行符
remove_newline(input);
// 检查用户是否想退出
// 注意: strcasecmp 是一个非标准函数,但很多编译器都支持。
// 如果要严格遵循标准,可以自己实现一个不区分大小写的比较函数。
// 或者使用 strcmp(input, "exit") 进行区分大小写的比较。
#ifdef __STDC_LIB_EXT1__ // 或者直接使用,很多环境都支持
if (strcasecmp(input, "exit") == 0) {
#else
if (strcmp(input, "exit") == 0) { // 使用标准库的 strcmp
#endif
printf("再见!\n");
break;
}
// 回显用户输入
printf("你输入了: %s\n", input);
}
return 0;
}
fgets(str, n, stdin)是从标准输入安全读取一行字符串的首选方法。- 核心参数
n控制了读取的最大长度,有效防止了缓冲区溢出。 - 必须处理换行符
\n,fgets会保留它,通常需要手动去除。 - 始终检查返回值,判断是成功读取还是遇到了文件结束符或错误。
- 相比
scanf,fgets在处理字符串输入时更安全、更可靠。
