编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
      • 循环和选择
      • 输入输出
      • 函数
      • 指针
      • 数组和容器
      • 类和内存
      • 流与文件
        • 10.1 流基础介绍
          • 10.1.1 什么是流
          • 10.1.2 流基本概念
          • 10.1.3 标准流
          • 10.1.4 综合案例与思考
          • 10.2.1 文件基本概念
          • 10.2.2 文件操作
        • 10.3 文件操作函数
          • 10.3.1 打开文件
          • 10.3.2 关闭文件
          • 10.3.3 读取文件
          • 10.3.4 写入文件
          • 10.3.5 二进制文件操作
          • 10.3.6 文件定位
          • 10.3.7 综合案例与思考
          • 10.4.1 文件模式汇总
        • 10.5 IO流综合案例
          • 10.5.1 文本文件读写
          • 10.5.2 二进制文件读写
      • 结构体
      • 线程和锁
      • 预处理器
      • 高级数据
    • 综合案例

    • 专栏博客

    • 标准集库

  • Cpp入门到精通

  • Java入门精通

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • C语言入门精通
  • 入门教程
杨充
2025-07-21
目录

流与文件

# 10.流和文件

# 目录介绍

  • 10.1 流基础介绍
    • 10.1.1 什么是流
    • 10.1.2 流基本概念
    • 10.1.3 标准流
    • 10.1.4 综合案例与思考
  • 10.2 文件
    • 10.2.1 文件基本概念
    • 10.2.2 文件操作
  • 10.3 文件操作函数
    • 10.3.1 打开文件
    • 10.3.2 关闭文件
    • 10.3.3 读取文件
    • 10.3.4 写入文件
    • 10.3.5 二进制文件操作
    • 10.3.6 文件定位
    • 10.3.7 综合案例与思考
  • 10.4 文件模式
    • 10.4.1 文件模式汇总
  • 10.5 IO流综合案例
    • 10.5.1 文本文件读写
    • 10.5.2 二进制文件读写

# 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;
}
1
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() 或信号杀死),缓冲区中的数据可能丢失。

思考题:

  1. 为什么 stderr 默认是无缓冲的?如果程序崩溃时 stdout 有未刷新的数据,会发生什么?
  2. printf 的输出在管道中和在终端中的缓冲行为有什么不同?为什么?
  3. setvbuf 和 setbuf 有什么区别?何时需要自定义缓冲区?

# 10.2.1 文件基本概念

  • 文件:文件是存储在磁盘上的数据集合,可以是文本文件或二进制文件。
  • 文件指针:C 语言通过 FILE * 类型的指针来操作文件。
  • 文件模式:打开文件时需要指定模式,如只读、只写、追加等。

# 10.2.2 文件操作

文件是存储在磁盘上的数据集合。C 语言通过文件指针(FILE *)来操作文件。文件操作的基本步骤包括:

  1. 打开文件:使用 fopen 函数打开文件。
  2. 读写文件:使用 fread、fwrite、fscanf、fprintf 等函数读写文件。
  3. 关闭文件:使用 fclose 函数关闭文件。

# 10.3 文件操作函数

# 10.3.1 打开文件

使用 fopen 函数打开文件:

FILE *fopen(const char *filename, const char *mode);
1
  • filename:文件名。
  • mode:打开模式,如 "r"(只读)、"w"(只写)、"a"(追加)、"rb"(二进制只读)等。

示例:

FILE *file = fopen("example.txt", "r");
if (file == NULL) {
    perror("Failed to open file");
    return 1;
}
1
2
3
4
5

# 10.3.2 关闭文件

使用 fclose 函数关闭文件:

int fclose(FILE *stream);
1

示例:

fclose(file);
1

# 10.3.3 读取文件

字符读取:fgetc

int fgetc(FILE *stream);
1

行读取:fgets

char *fgets(char *str, int n, FILE *stream);
1

格式化读取:fscanf

int fscanf(FILE *stream, const char *format, ...);
1

示例:

char buffer[100];
while (fgets(buffer, sizeof(buffer), file) != NULL) {
    printf("%s", buffer);
}
1
2
3
4

# 10.3.4 写入文件

字符写入:fputc

int fputc(int char, FILE *stream);
1

行写入:fputs

int fputs(const char *str, FILE *stream);
1

格式化写入:fprintf

int fprintf(FILE *stream, const char *format, ...);
1

示例:

fprintf(file, "Hello, World!\n");
1

# 10.3.5 二进制文件操作

读取二进制数据:fread

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
1

写入二进制数据:fwrite

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
1

示例:

int data[10];
fread(data, sizeof(int), 10, file);
1
2

# 10.3.6 文件定位

  • 获取当前位置:ftell
    long ftell(FILE *stream);
    
    1
  • 移动文件指针:fseek
    int fseek(FILE *stream, long offset, int origin);
    
    1
  • 重置文件指针:rewind
    void rewind(FILE *stream);
    
    1

示例:

fseek(file, 0, SEEK_END); // 移动到文件末尾
long size = ftell(file);  // 获取文件大小
rewind(file);             // 重置文件指针到开头
1
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;
}
1
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 会导致缓冲区数据丢失和文件描述符泄漏(系统对打开文件数有上限)。

思考题:

  1. fopen 返回 NULL 的常见原因有哪些?如何获取具体的错误信息?
  2. 文本模式 "r" 和二进制模式 "rb" 在Windows和Linux上有什么区别?为什么?
  3. 如何安全地实现"读取文件、修改内容、写回文件"的操作?直接用 "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;
}
1
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;
}
1
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
上次更新: 2026/06/10, 11:13:41
类和内存
结构体

← 类和内存 结构体→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式