C 语言从文件读数据
更新时间:2026-08-26。本文是
languages/c/主题入门层第 61 篇。文件打开后,怎么把内容读出来?两个主力:fgetc一个字符一个字符地啃,fgets一次读一整行。读文件的代码人人会写,但EOF 判断写错、行尾换行没处理——教科书级错误就藏在最不起眼的地方。
本文要回答的问题
fgetc怎么判断读到结尾了?fgets一次读多少?缓冲区不够怎么办?- 读文件的标准循环长什么样?
一、fgetc:逐字符读取
c
#include <stdio.h>
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) return 1;
int c;
while ((c = fgetc(fp)) != EOF) { // 关键:返回值用 int!
putchar(c);
}
fclose(fp);两个重点:
- 返回值类型是
int不是char——EOF(通常 -1)不在 char 范围内,用char存会截断,永远比不出 EOF; EOF是"读完"的信号,不是文件里的某个字符。
二、fgets:一次读一行
c
char buf[256];
while (fgets(buf, sizeof(buf), fp) != NULL) {
printf("%s", buf);
}fgets(buf, size, fp) 的行为:
- 最多读
size-1个字符,末尾自动加'\0'; - 遇到换行符
\n就停(包括换行); - 读到文件尾返回
NULL。
返回值 NULL = 读完,用返回值判断,不要用 feof(那是个暗坑,见文件错误处理)。
三、行尾换行:fgets 会一起读进来
fgets 读到 \n 会把它放进缓冲区——打印时无感,但做字符串处理(strcmp、拼接)时要注意:
c
while (fgets(buf, sizeof(buf), fp) != NULL) {
buf[strcspn(buf, "\n")] = '\0'; // 去掉结尾换行
// 现在 buf 是干净的"行内容"
}strcspn(buf, "\n") 找第一个换行的位置,把它替换成 '\0'。这是处理 fgets 结果的标配写法。
四、超长行:缓冲区被截断
一行超过 size-1 个字符时,fgets 分多次读完——第一次读到缓冲区满,剩余部分下次循环再读:
c
// 文件内容:这是一行超过 200 字符的长行……
char buf[64];
while (fgets(buf, sizeof(buf), fp) != NULL) {
printf("[%zu] %s", strlen(buf), buf); // 会输出多段
}如果必须按"整行"处理,要么缓冲区开够大,要么用 getline(GNU/POSIX):
c
char *line = NULL;
size_t cap = 0;
ssize_t n = getline(&line, &cap, fp); // 自动分配,读整行
if (n >= 0) {
/* line 就是完整的一行 */
}
free(line); // getline 分配的内存要释放getline 会自己扩容,适合处理未知长度的行。
五、标准输入也是文件
stdin 就是一个 FILE*,getchar() 等价于 fgetc(stdin):
c
int c;
while ((c = getchar()) != EOF) { // Ctrl+D (Linux) / Ctrl+Z (Windows) 结束
putchar(c);
}这解释了为什么 scanf 总是和换行纠缠——控制台输入也是流。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 用 char 接 fgetc | EOF 判断失效死循环 | 用 int |
| 用 feof 判断结束 | 多读一次/错一次 | 用返回值判断 |
| 忘了去行尾 \n | strcmp 永远不相等 | strcspn 去换行 |
| 缓冲区太小 | 长行被截断 | getline 或大缓冲区 |
| 忘判 fgets 返回值 | 读到垃圾数据 | 先判 NULL |
七、与本站主线衔接
一句话总结
读文件的标准姿势:fgetc 用 int 接返回值、!= EOF 判断结尾;fgets 读一行并带上换行、用返回 NULL 判断读完,需要整行时用 getline——"读到结尾"的判断方式,是读写文件代码里一半 bug 的源头。