1. 文件读写的三种基本方式
C 语言标准 I/O(Standard I/O)库提供了三种主要的文件读写方式:
- 字符方式(Character-wise):一次读/写一个字符。
- 行方式(Line-wise):一次读/写一行文本。
- 对象方式(Object-wise):以结构体或自定义数据块为单位进行读写,模拟“对象”操作。
注:此处的“对象”并非面向对象编程中的概念,而是指将数据视为一个整体(如
struct)进行操作。
2. 字符输入接口(Character Input)
2.1 fgetc() 函数
- 功能:从指定流中读取一个字符。
- 函数原型:
int fgetc(FILE *stream); - 参数:
FILE *stream—— 已打开的文件流(由fopen()返回)。 - 返回值:
- 成功时返回读取字符的
int类型值(实际为unsigned char转换而来)。 - 遇到文件结尾(EOF)或出错时返回
EOF(通常为-1)。
- 成功时返回读取字符的
- 关键点:
- 返回类型为
int而非char,是为了能表示所有可能的字节值(0–255)以及EOF(-1)。 - 使用前需确保文件以读模式(如
"r")打开。
- 返回类型为
2.2 getc() 宏
- 功能:与
fgetc()功能相同。 - 区别:
getc()是宏(macro),而fgetc()是函数。 - 用法:
int c = getc(fp); // fp 为 FILE* 类型
2.3 getchar() 函数
- 功能:从标准输入(
stdin)读取一个字符。 - 等价于:
fgetc(stdin)或getc(stdin)。 - 典型用法:
int c = getchar();
2.4 使用示例与注意事项
#include <stdio.h>
int main() {
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
perror("fopen");
return 1;
}
int c;
while ((c = fgetc(fp)) != EOF) {
printf("%c", c);
}
printf("\nFgetc reached EOF normally.\n");
fclose(fp);
return 0;
}
- 必须检查
EOF:用于判断是否到达文件末尾或发生错误。 EOF不是字符:它是整型常量(通常为-1),不应存入字符数组。
3. 字符输出接口(Character Output)
3.1 fputc() 函数
- 功能:向指定流写入一个字符。
- 原型:
int fputc(int c, FILE *stream); - 返回值:
- 成功时返回写入的字符(作为
unsigned char转int)。 - 失败时返回
EOF。
- 成功时返回写入的字符(作为
3.2 putc() 宏
- 功能同
fputc(),但实现为宏。
3.3 putchar() 函数
- 功能:向标准输出(
stdout)写入一个字符。 - 等价于:
fputc(c, stdout)。
3.4 常见错误:bad file descriptor
- 原因:文件打开模式错误。
- 若需写入,必须使用写模式(如
"w"、"a"、"w+"等)。 - 若以只读模式(
"r")打开却尝试写入,将导致错误。
- 若需写入,必须使用写模式(如
- 解决方法:检查
fopen()的第二个参数是否匹配操作意图。
3.5 示例
FILE *fp = fopen("test.txt", "a"); // 追加模式,保留原内容
if (fp == NULL) { /* error handling */ }
int ret = fputc('A', fp);
if (ret == EOF) {
printf("Write failed!\n");
}
fclose(fp);
- 使用
"w"模式会清空原文件;"a"模式则在末尾追加。
4. 行输入接口:fgets()
4.1 函数原型
char *fgets(char *s, int size, FILE *stream);
4.2 功能与安全机制
- 从
stream中最多读取size - 1个字符,存入s。 - 自动在末尾添加
\0,确保字符串安全。 - 若遇到换行符
\n或 EOF,则提前结束,并将\n保留在缓冲区中(若空间足够)。 - 不会发生缓冲区溢出(相比危险的
gets())。
4.3 返回值
- 成功时返回
s(即传入的缓冲区地址)。 - 遇到 EOF 或出错时返回
NULL。
4.4 关键行为分析
| 输入内容(含回车) | size = 6 |
buffer 内容 |
|---|---|---|
ABCD\n |
6 | 'A','B','C','D','\n','\0' |
ABCDE\n |
6 | 'A','B','C','D','E','\0'(\n 被截断) |
ABCDEF\n |
6 | 第一次读:'A','B','C','D','E','\0';第二次读:'F','\n','\0',... |
注意:
fgets()可能需要多次调用才能读完超长行。
4.5 示例
#define N 32
char buffer[N];
while (fgets(buffer, N, fp) != NULL) {
printf("Read: %s", buffer); // buffer 已含 \n
}
5. 行输出接口:fputs() 与 puts()
5.1 fputs()
- 原型:
int fputs(const char *s, FILE *stream); - 功能:将字符串
s(不含自动\0)写入流。 - 不自动添加换行符
\n。 - 返回值:成功返回非负值,失败返回
EOF。
5.2 puts()
- 原型:
int puts(const char *s); - 功能:将字符串写入
stdout,自动追加\n。 - 等价于:
fputs(s, stdout); putchar('\n');
5.3 对比总结
| 函数 | 目标流 | 自动加 \n? |
安全性 |
|---|---|---|---|
fputs |
指定流 | 否 | 安全 |
puts |
stdout |
是 | 安全 |
二者均不会导致缓冲区溢出,因只输出已有字符串。
6. 实践应用案例
6.1 文件复制(字符级)
// charcopy.c
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <src> <dst>\n", argv[0]);
return 1;
}
FILE *src = fopen(argv[1], "r");
if (!src) { perror("fopen src"); return 1; }
FILE *dst = fopen(argv[2], "w"); // 覆盖模式
if (!dst) {
perror("fopen dst");
fclose(src);
return 1;
}
int c;
while ((c = fgetc(src)) != EOF) {
if (fputc(c, dst) == EOF) {
perror("fputc");
break;
}
}
fclose(src);
fclose(dst);
printf("Copy completed.\n");
return 0;
}
6.2 统计文件行数
#include <stdio.h>
#include <string.h>
#define N 1024
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
return 1;
}
FILE *fp = fopen(argv[1], "r");
if (!fp) { perror("fopen"); return 1; }
char buffer[N];
int count = 0;
while (fgets(buffer, N, fp) != NULL) {
size_t len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
count++; // 完整一行(含 \n)
}
// 注意:最后一行若无 \n,是否计入需根据需求决定
}
printf("Total lines: %d\n", count);
fclose(fp);
return 0;
}
- 判断完整行:检查
buffer末尾是否为\n。 - 使用
strlen()获取有效长度(不含\0)。
7. 关键总结
- 始终检查返回值:
fopen、fgetc、fputc、fgets等均需错误处理。 - 正确使用文件模式:
- 读:
"r" - 写(覆盖):
"w" - 追加:
"a" - 读写:
"r+","w+","a+"
- 读:
- 避免
gets():使用fgets()替代,防止缓冲区溢出。 - 资源管理:打开的文件务必
fclose(),尤其在错误路径中也要关闭已打开的文件。 EOF处理:fgetc()返回int是为了兼容EOF,勿用char接收。- 行处理注意:
fgets()保留\n,fputs()不添加\n,而puts()会添加。