C 库函数 – fgets()(超详细)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
在 C 语言编程中,输入输出操作是开发者必须掌握的核心技能之一。fgets()
函数作为 C 标准库中用于读取字符串的工具,其功能强大且适用场景广泛。无论是从文件读取数据,还是从标准输入接收用户输入,fgets()
都能提供安全、灵活的解决方案。然而,许多开发者在初次接触该函数时,容易因参数设置不当或边界条件处理不足而引发问题。本文将从基础概念、使用场景、代码示例到常见陷阱,系统性地解析 fgets()
的核心知识点,并通过生动的比喻和实践案例,帮助读者快速掌握这一工具。
函数原型与参数解析
fgets()
函数的完整原型为:
char *fgets(char *str, int n, FILE *stream);
该函数接受三个参数:
str
:指向字符数组的指针,用于存储读取到的字符串。n
:表示最多读取的字符数(包括终止符\0
)。stream
:指向FILE
结构的指针,表示输入源,如标准输入stdin
或文件流。
参数的“容量”与“安全边界”
将 n
参数想象为一个“容器的容量”。例如,若 n
设为 10,则 fgets()
最多会读取 9 个字符(第 10 个位置留给终止符 \0
)。这种设计类似于给水杯设定最大容量,确保液体不会溢出,从而避免缓冲区溢出漏洞。
基础用法与代码示例
从标准输入读取字符串
以下示例演示如何通过 fgets()
读取用户输入:
#include <stdio.h>
int main() {
char input[100]; // 定义足够大的缓冲区
printf("请输入一行文字:");
fgets(input, sizeof(input), stdin); // 读取输入到 input 数组
printf("您输入的内容是:%s", input);
return 0;
}
关键点解析:
sizeof(input)
返回数组总长度(100),确保读取不超过缓冲区容量。- 若输入内容超过
n-1
字符,fgets()
会自动截断多余字符,并添加\0
。
从文件读取数据
fgets()
也能用于文件操作。例如,逐行读取文件内容:
#include <stdio.h>
int main() {
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
perror("无法打开文件");
return 1;
}
char line[256];
while (fgets(line, sizeof(line), file) != NULL) {
printf("%s", line); // 输出文件的每一行
}
fclose(file);
return 0;
}
注:循环条件 fgets(...)!=NULL
可检测到文件结束或错误。
与 gets()
的对比:安全性至关重要
C 标准库中存在另一个字符串输入函数 gets()
,其原型为 char *gets(char *str)
。然而,gets()
完全不检查缓冲区边界,若输入内容超过数组容量,将引发缓冲区溢出,导致程序崩溃或安全漏洞。因此,C11 标准已弃用 gets()
,而 fgets()
因其安全性成为推荐替代方案。
深入细节:换行符与空格处理
换行符的保留与截断
fgets()
会读取输入中的换行符 \n
,并将其存储在字符串中。例如,若用户输入“Hello World\n”,则 input
数组的内容为 "Hello World\n\0"
。若希望去除换行符,可通过以下代码处理:
// 移除字符串末尾的换行符
size_t len = strlen(input);
if (len > 0 && input[len-1] == '\n') {
input[len-1] = '\0';
}
空格与空白字符的保留
fgets()
保留输入中的所有空白字符,包括空格、制表符和换行符。这使得它适合读取包含空格的完整句子或行。
常见问题与解决方案
问题 1:输入被截断
现象:输入内容超过 n-1
字符时,字符串被截断。
解决方案:
- 增大缓冲区容量(如使用
char buffer[1024]
)。 - 在截断后,通过
fflush(stdin)
或循环清理输入流中的剩余字符(需注意平台兼容性)。
问题 2:读取空行或未读取到内容
现象:若输入流中存在残留字符(如前一次输入未处理的换行符),fgets()
可能直接读取这些残留,导致空行或意外结果。
解决方案:
// 清理输入流中的残留字符
int c;
while ((c = getchar()) != '\n' && c != EOF);
进阶技巧:灵活控制读取行为
1. 动态缓冲区管理
通过 malloc()
动态分配缓冲区,避免固定大小的限制:
#include <stdlib.h>
#include <string.h>
char *read_line(FILE *stream) {
char *buffer = malloc(100 * sizeof(char));
if (fgets(buffer, 100, stream) == NULL) {
free(buffer);
return NULL;
}
// 若输入超过 99 字符,重新分配内存
while (strlen(buffer) == 99 && buffer[99] != '\n') {
buffer = realloc(buffer, 2 * strlen(buffer) + 1);
fgets(buffer + strlen(buffer), strlen(buffer)+1, stream);
}
return buffer;
}
此示例通过动态扩展缓冲区,应对超长输入。
2. 读取指定长度的内容
若需读取固定长度的字符串(如密码输入),可结合 fgets()
和 strncpy()
:
char password[10];
printf("请输入密码(最多9字符):");
fgets(password, sizeof(password), stdin);
// 移除换行符并截断
password[strcspn(password, "\n")] = '\0';
性能与内存优化
缓冲区大小的权衡
- 过小的缓冲区:频繁截断可能导致用户体验下降(如需要多次输入)。
- 过大的缓冲区:占用过多内存,尤其在内存受限的嵌入式系统中需谨慎。
文件读取的效率优化
若需高效读取大文件,可结合 fread()
或 mmap()
,但需权衡代码复杂度与性能需求。
实战案例:实现简易文本编辑器
以下代码演示如何利用 fgets()
构建一个基础文本编辑器:
#include <stdio.h>
#include <string.h>
#define MAX_LINE 256
void edit_file(const char *filename) {
FILE *file = fopen(filename, "r+");
if (!file) {
perror("文件打开失败");
return;
}
char lines[100][MAX_LINE]; // 存储文件的每一行
int count = 0;
char line[MAX_LINE];
// 读取文件内容到数组
while (fgets(line, MAX_LINE, file)) {
strcpy(lines[count++], line);
}
// 编辑操作(此处简化为添加新行)
printf("输入新内容:");
fgets(line, MAX_LINE, stdin);
strcpy(lines[count++], line);
// 重写文件
rewind(file);
for (int i = 0; i < count; i++) {
fputs(lines[i], file);
}
fclose(file);
}
int main() {
edit_file("test.txt");
return 0;
}
此案例展示了 fgets()
在文件读写和文本处理中的实际应用。
结论
fgets()
函数凭借其安全性、灵活性和广泛适用性,成为 C 语言开发者处理字符串输入的首选工具。通过合理设置缓冲区容量、处理换行符、避免常见陷阱,并结合动态内存管理等进阶技巧,开发者可以高效且安全地实现复杂输入输出场景。掌握 fgets()
的核心逻辑与细节,不仅能提升代码质量,更能为后续学习更复杂的 C 标准库函数打下坚实基础。建议读者通过实际编码练习,逐步加深对这一函数的理解与应用。