学 C 语言第一次读字符串,老师几乎都会说别用 gets,用 fgets。原因很简单:gets 不检查缓冲区长度,输入超过数组容量就越界写,现在编译器已经不让用了。fgets 加了个长度参数,就是替代方案。
函数原型
char *fgets(char *str, int n, FILE *stream);三个参数:存放结果的缓冲区、最多读多少字节(含结尾的 \0)、从哪个流读。stdin 是键盘,fp 是文件。返回 str 指针,失败或到文件末尾返回 NULL。
最容易误解的:n 的含义
很多人以为 n 是能读 n 个字符,其实最多只能存 n-1 个,最后一位留给 \0。数组开 100 就传 100。
char buf[100];
fgets(buf, sizeof(buf), stdin);用 sizeof 比手写 100 好,改数组大小不会漏改。但 buf 是函数参数传进来时,sizeof 得到的是指针大小,必须把长度一起传。
换行符会被读进来
新手最常踩的坑。输入 abc 回车,buf 里存的是 "abc\n",后面用 strcmp 和 "abc" 比较永远不相等。
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n')
buf[len - 1] = '\0';Windows 文件里常见 \r\n,最好一起处理,只删 \n 会留下一个 \r。
输入太长会怎样
一行超过缓冲长度时,fgets 只读前 n-1 个字符就返回,剩下的留在缓冲区里,下次调用会接着读,看起来像「多读了一行」。所以要检查末尾有没有换行,没有就是被截断了。
读文件的标准写法
FILE *fp = fopen("data.txt", "r");
if (!fp) { perror("open"); return 1; }
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
printf("%s", line);
}
fclose(fp);NULL 的判断放在循环条件里,别在循环体里再判一次。
和 getline、scanf 的区别
getline 是 POSIX 函数,能自动分配内存,多长都不截断,缺点是跨平台要自己准备。scanf("%s") 遇到空格就停,也不限长度,读整行不如 fgets 稳。日常读一行用 fgets,需要不限长度就用 getline。