3、文件的顺序读写(一):fputc、fgetc、fputs、fgets 函数的使用
文件操作里最基础、最常用的,就是顺序读写。说白了,就是从头到尾,一个字符一个字符,或者一行一行地读和写。今天咱们先聊四个最基础的函数:fputc、fgetc、fputs、fgets。
我个人习惯把这四个函数分成两组:字符级和字符串级。字符级就是一次处理一个字符,字符串级就是一次处理一整行。咱们一个一个来看。
3.1 字符级读写:fputc 和 fgetc
3.1.1 fputc:写一个字符到文件
fputc 的用法很简单:把一个字符写到指定的文件流里。原型长这样:
int fputc(int ch, FILE *stream);
第一个参数是你要写的字符(注意是 int 类型,但实际传字符常量就行),第二个参数是文件指针。返回值:写成功返回你写的那个字符,失败返回 EOF。
我在项目中遇到过这样一个场景:需要把一个字符串逐字符加密后写入文件。那时候就是用 fputc 一个字符一个字符地写,每写一个字符就做一次异或运算。嗯,虽然效率不高,但控制力极强。
核心要点:fputc 每次只写一个字符。如果你需要写大量数据,建议用缓冲区批量写入,否则性能会受影响。
来看个例子:
#include <stdio.h>
int main() {
FILE *fp = fopen("test.txt", "w");
if (fp == NULL) {
perror("文件打开失败");
return 1;
}
char str[] = "Hello, C!";
int i = 0;
while (str[i] != '\0') {
fputc(str[i], fp);
i++;
}
fclose(fp);
return 0;
}
这段代码把字符串 "Hello, C!" 逐字符写入了 test.txt。你想想看,如果字符串很长,这样写确实有点慢。但好处是——你可以在写入每个字符前做点手脚,比如加密、过滤、转换格式。
3.1.2 fgetc:从文件读一个字符
有写就有读。fgetc 从文件流中读取下一个字符。原型:
int fgetc(FILE *stream);
返回值:读取成功返回字符的 ASCII 码值(转为 int),读到文件末尾或出错返回 EOF。
注意:fgetc 返回的是 int 类型,不是 char。为什么?因为 EOF 是 -1,而 char 可能是无符号的 0~255,无法表示 -1。所以一定要用 int 接收返回值,否则可能永远检测不到文件结尾。
我曾经踩过这个坑——用 char ch 接收 fgetc 的返回值,结果文件明明读完了,循环就是不停。后来查了半天才发现是类型问题。嗯,这个坑我帮你踩过了,你记住就好。
看个例子:
#include <stdio.h>
int main() {
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
perror("文件打开失败");
return 1;
}
int ch;
while ((ch = fgetc(fp)) != EOF) {
putchar(ch); // 输出到屏幕
}
fclose(fp);
return 0;
}
这段代码把 test.txt 的内容逐字符读到屏幕上。注意那个 while 条件——先赋值,再比较,这是 C 语言里非常经典的写法。
3.2 字符串级读写:fputs 和 fgets
字符级读写虽然灵活,但效率低。如果你要处理的是整行文本,用 fputs 和 fgets 更合适。
3.2.1 fputs:写一个字符串到文件
fputs 把一个字符串写入文件,不包含字符串结尾的 \0。原型:
int fputs(const char *str, FILE *stream);
返回值:成功返回非负数(通常是 0),失败返回 EOF。
注意:fputs 不会自动加换行符。如果你需要换行,得自己在字符串里加 \n。这和 puts 不同——puts 会自动加换行,但 fputs 不会。
小技巧:如果你想把多行文本写入文件,可以用 fputs 配合循环,每行末尾加 \n。我个人习惯在字符串末尾直接包含换行符,这样代码更清晰。
看例子:
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "w");
if (fp == NULL) {
perror("文件打开失败");
return 1;
}
fputs("第一行内容\n", fp);
fputs("第二行内容\n", fp);
fputs("第三行内容\n", fp);
fclose(fp);
return 0;
}
这段代码写入了三行文本。注意每个字符串末尾都有 \n,这样文件里就是三行。
3.2.2 fgets:从文件读一个字符串
fgets 从文件读取一行,最多读取 n-1 个字符,遇到换行符或文件末尾停止。原型:
char *fgets(char *str, int n, FILE *stream);
参数说明:
str:存放读取内容的缓冲区n:缓冲区大小(最多读取 n-1 个字符,留一个给\0)stream:文件指针
返回值:成功返回 str,读到文件末尾或出错返回 NULL。
重要特性:fgets 会读取换行符并存入缓冲区。也就是说,如果一行有 10 个字符加一个换行符,fgets 会把这 11 个字符都读进来,然后在末尾加 \0。这一点和 gets 完全不同——gets 会丢弃换行符,但 gets 不安全,已经被废弃了。
看个例子:
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("文件打开失败");
return 1;
}
char buffer[256];
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("读取到: %s", buffer);
}
fclose(fp);
return 0;
}
这段代码逐行读取 data.txt 的内容并打印到屏幕。注意 printf 里没有加 \n,因为 fgets 已经把换行符读进来了。
3.3 四个函数的对比总结
为了方便你对比,我整理了一张表:
| 函数 | 操作单位 | 是否包含换行符 | 返回值 | 典型用途 |
|---|---|---|---|---|
| fputc | 1 个字符 | 不自动加 | 写入的字符 或 EOF | 逐字符写入、加密、过滤 |
| fgetc | 1 个字符 | 会读取 | 读取的字符 或 EOF | 逐字符读取、解析 |
| fputs | 1 个字符串 | 不自动加 | 非负数 或 EOF | 写入整行文本 |
| fgets | 1 行(最多 n-1 字符) | 会读取并保留 | 缓冲区指针 或 NULL | 逐行读取文本 |
3.4 知识体系图
下面这张图帮你理清这四个函数的关系和适用场景:
3.5 避坑指南
最后,分享几个我实际开发中遇到的坑:
- fgetc 返回值类型:一定要用
int接收,别用char。否则 EOF 检测会失效。 - fgets 缓冲区大小:第二个参数一定要传真实大小,别传小了导致截断,也别传大了浪费内存。我一般用
sizeof(buffer)。 - fputs 不加换行:如果你用
fputs写文件,记得在字符串里加\n,否则所有内容会挤在一行。 - 文件打开模式:写文件用
"w"会清空原有内容,追加用"a"。我曾经因为忘了这个,把配置文件清空了……嗯,从那以后我每次打开文件前都会确认模式。
这四个函数虽然基础,但用好了能解决 80% 的文本文件读写需求。下一节咱们继续聊 fprintf 和 fscanf,它们更强大,也更灵活。
公众号:蓝海资料掘金营,微信 deep3321