流与文件
# 10.流和文件
# 目录介绍
# 10.1 流基础介绍
# 10.1.1 什么是流
在 C 语言中,流(Stream) 是一个抽象的概念,用于表示数据的输入和输出。流可以是文件、键盘、屏幕、网络等数据源或目标的抽象。
C 标准库提供了一系列函数来操作流,使得程序可以方便地处理输入和输出。
# 10.1.2 流基本概念
流:流是数据在程序和外部设备之间传输的抽象。它可以是输入流(数据从外部设备到程序)或输出流(数据从程序到外部设备)。
- 文本流(Text Stream):由字符组成,通常用于处理文本文件。
- 二进制流(Binary Stream):由字节组成,通常用于处理二进制文件。
# 10.1.3 标准流
C 标准库中定义了三种标准流:
stdin:标准输入流(通常指键盘)。stdout:标准输出流(通常指屏幕)。stderr:标准错误流(通常指屏幕)。
# 10.1.4 综合案例与思考
综合案例:理解流和缓冲机制
#include <stdio.h>
int main() {
// 案例1:三种标准流的使用
printf("这是stdout输出\n"); // 标准输出
fprintf(stderr, "这是stderr输出\n"); // 标准错误
// 案例2:输出重定向演示说明
printf("\n=== 重定向说明 ===\n");
printf("执行: ./program > out.txt 2> err.txt\n");
printf("stdout的内容会写入out.txt\n");
printf("stderr的内容会写入err.txt\n");
// 案例3:缓冲区的影响
printf("\n=== 缓冲区演示 ===\n");
printf("行缓冲(带\\n)"); // 不带\n可能不会立即显示
printf(" -> ");
printf("已输出\n"); // \n触发刷新
// 手动刷新
printf("手动刷新:");
fflush(stdout); // 强制刷新
printf(" 完成\n");
// 案例4:设置缓冲模式
// setvbuf(stdout, NULL, _IONBF, 0); // 无缓冲
// setvbuf(stdout, NULL, _IOLBF, 0); // 行缓冲
// setvbuf(stdout, NULL, _IOFBF, 1024); // 全缓冲(1KB)
printf("缓冲模式: _IONBF(无), _IOLBF(行), _IOFBF(全)\n");
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
原理说明:C的I/O系统基于流的抽象,流封装了底层的文件描述符和缓冲区。缓冲分三种:无缓冲(_IONBF,立即输出,如 stderr)、行缓冲(_IOLBF,遇到 \n 刷新,连接终端时的 stdout)、全缓冲(_IOFBF,缓冲区满时才刷新,如文件流)。程序正常退出时(调用 exit() 或 main 返回),所有打开的流会被自动刷新并关闭。但如果程序异常终止(如 abort() 或信号杀死),缓冲区中的数据可能丢失。
思考题:
- 为什么
stderr默认是无缓冲的?如果程序崩溃时stdout有未刷新的数据,会发生什么? printf的输出在管道中和在终端中的缓冲行为有什么不同?为什么?setvbuf和setbuf有什么区别?何时需要自定义缓冲区?
# 10.2.1 文件基本概念
- 文件:文件是存储在磁盘上的数据集合,可以是文本文件或二进制文件。
- 文件指针:C 语言通过
FILE *类型的指针来操作文件。 - 文件模式:打开文件时需要指定模式,如只读、只写、追加等。
# 10.2.2 文件操作
文件是存储在磁盘上的数据集合。C 语言通过文件指针(FILE *)来操作文件。文件操作的基本步骤包括:
- 打开文件:使用
fopen函数打开文件。 - 读写文件:使用
fread、fwrite、fscanf、fprintf等函数读写文件。 - 关闭文件:使用
fclose函数关闭文件。
# 10.3 文件操作函数
# 10.3.1 打开文件
使用 fopen 函数打开文件:
FILE *fopen(const char *filename, const char *mode);
filename:文件名。mode:打开模式,如"r"(只读)、"w"(只写)、"a"(追加)、"rb"(二进制只读)等。
示例:
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
2
3
4
5
# 10.3.2 关闭文件
使用 fclose 函数关闭文件:
int fclose(FILE *stream);
示例:
fclose(file);
# 10.3.3 读取文件
字符读取:fgetc
int fgetc(FILE *stream);
行读取:fgets
char *fgets(char *str, int n, FILE *stream);
格式化读取:fscanf
int fscanf(FILE *stream, const char *format, ...);
示例:
char buffer[100];
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer);
}
2
3
4
# 10.3.4 写入文件
字符写入:fputc
int fputc(int char, FILE *stream);
行写入:fputs
int fputs(const char *str, FILE *stream);
格式化写入:fprintf
int fprintf(FILE *stream, const char *format, ...);
示例:
fprintf(file, "Hello, World!\n");
# 10.3.5 二进制文件操作
读取二进制数据:fread
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
写入二进制数据:fwrite
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
示例:
int data[10];
fread(data, sizeof(int), 10, file);
2
# 10.3.6 文件定位
- 获取当前位置:
ftelllong ftell(FILE *stream);1 - 移动文件指针:
fseekint fseek(FILE *stream, long offset, int origin);1 - 重置文件指针:
rewindvoid rewind(FILE *stream);1
示例:
fseek(file, 0, SEEK_END); // 移动到文件末尾
long size = ftell(file); // 获取文件大小
rewind(file); // 重置文件指针到开头
2
3
# 10.3.7 综合案例与思考
综合案例:文件操作函数的综合应用
#include <stdio.h>
#include <string.h>
int main() {
const char *filename = "demo.txt";
// 案例1:写入文件
printf("=== 写入文件 ===\n");
FILE *fp = fopen(filename, "w");
if (!fp) { perror("打开失败"); return 1; }
fprintf(fp, "姓名: 张三\n");
fprintf(fp, "年龄: 25\n");
fprintf(fp, "成绩: 95.5\n");
fputs("备注: C语言学习中\n", fp);
fclose(fp);
printf("写入完成\n");
// 案例2:逐行读取
printf("\n=== 逐行读取 ===\n");
fp = fopen(filename, "r");
if (!fp) { perror("打开失败"); return 1; }
char line[256];
int line_num = 0;
while (fgets(line, sizeof(line), fp)) {
line[strcspn(line, "\n")] = '\0'; // 去掉换行符
printf("第%d行: %s\n", ++line_num, line);
}
// 案例3:获取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
printf("\n文件大小: %ld 字节\n", size);
// 案例4:定位到特定位置读取
rewind(fp); // 回到开头
fseek(fp, 4, SEEK_SET); // 跳过前4个字节
char buf[20];
fgets(buf, sizeof(buf), fp);
buf[strcspn(buf, "\n")] = '\0';
printf("从第5字节开始读: %s\n", buf);
fclose(fp);
// 案例5:追加写入
printf("\n=== 追加写入 ===\n");
fp = fopen(filename, "a");
if (!fp) { perror("打开失败"); return 1; }
fprintf(fp, "追加: 学习进度100%%\n");
fclose(fp);
// 验证追加结果
fp = fopen(filename, "r");
printf("追加后的完整内容:\n");
while (fgets(line, sizeof(line), fp)) {
printf(" %s", line);
}
fclose(fp);
// 清理测试文件
remove(filename);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
原理说明:文件操作遵循"打开→操作→关闭"的模式。fopen 返回的 FILE * 指针封装了文件描述符、缓冲区、当前位置等信息。fseek 配合 SEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件末尾)可以在文件中任意定位。文件I/O也是有缓冲的,fclose 会自动刷新缓冲区并释放资源。忘记 fclose 会导致缓冲区数据丢失和文件描述符泄漏(系统对打开文件数有上限)。
思考题:
fopen返回NULL的常见原因有哪些?如何获取具体的错误信息?- 文本模式
"r"和二进制模式"rb"在Windows和Linux上有什么区别?为什么? - 如何安全地实现"读取文件、修改内容、写回文件"的操作?直接用
"r+"模式有什么风险?
# 10.4.1 文件模式汇总
| 模式 | 描述 |
|---|---|
"r" | 只读模式,文件必须存在。 |
"w" | 只写模式,文件不存在则创建,存在则清空。 |
"a" | 追加模式,文件不存在则创建,存在则追加。 |
"rb" | 二进制只读模式。 |
"wb" | 二进制只写模式。 |
"ab" | 二进制追加模式。 |
"r+" | 读写模式,文件必须存在。 |
"w+" | 读写模式,文件不存在则创建,存在则清空。 |
"a+" | 读写模式,文件不存在则创建,存在则追加。 |
# 10.5 IO流综合案例
# 10.5.1 文本文件读写
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
fprintf(file, "Hello, World!\n");
fclose(file);
file = fopen("example.txt", "r");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
char buffer[100];
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer);
}
fclose(file);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 10.5.2 二进制文件读写
#include <stdio.h>
int main() {
int data[5] = {1, 2, 3, 4, 5};
FILE *file = fopen("data.bin", "wb");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
fwrite(data, sizeof(int), 5, file);
fclose(file);
file = fopen("data.bin", "rb");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
int read_data[5];
fread(read_data, sizeof(int), 5, file);
for (int i = 0; i < 5; i++) {
printf("%d ", read_data[i]);
}
fclose(file);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30