1、为什么使用文件

2、什么是文件

3、二进制文件和文本文件

4、文件的打开和关闭

5、文件的顺序

6、文件的随机读写

7、文件读取结束的判定

8、文件缓冲区

int main()
{
    int a = 0;
    scanf("%d", &a); // 这个就是将输入的数字放入文件中
    printf("%d\n", a);
    return 0;
}

一、问什么使用文件

  • 内存数据断电丢失,文件可永久保存数据
  • 方便数据重复读取、批量处理;
  • 实现多程序数据共享。

二、什么是文件

磁盘(硬盘)上的文件是文件,但是在程序设计中,我们一般谈到的文件有两种:程序文件、数据文件

程序文件:

    源程序文件(.c

  • 后缀:.c

    目标文件(.obj / .o

  • 后缀:Windows 下.obj,Linux 下.o

    可执行文件(.exe

  • 后缀:Windows 系统下 .exe

数据文件

专门用来存放程序运行所需数据的文件,不包含代码指令。作用:给程序提供输入数据、保存程序运行结果,实现数据长久存储。

本章讨论的是数据文件

在以前各章所处理数据的输入和输出都是以终端为对象的,即从终端的键盘输入数据并行结果显示在显示器上,其实有的时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读到内存中,使用这里处理的就是磁盘上的文件。

文件名:

一个文件要有一个唯一的文件标识,以便用户识别和引用。文件名包含三个部分,文件路径 + 文件名主干 + 文件后缀(扩展名);例如:E:\project\main.c为了方便起见,文件标识常被称为文件名。

二进制文件和文本文件

文本文件(ASCII 文件)

1. 存储规则

每一个字符按它对应的 ASCII 码 存储,内容就是我们能看懂的文字、数字、符号。

2. 特点

  1. 可用记事本、写字板直接打开阅读、编辑;
  2. \nEOF(-1) 作为结束标志;
  3. 占用空间略大,读写速度稍慢;
  4. 适合存放文字、普通数据。

二进制文件

1. 存储规则

数据直接按照内存中的二进制原样保存,不转换成 ASCII 字符。

2. 特点

  1. 记事本打开显示乱码,只能由对应程序读取;
  2. 存储紧凑、占用空间小、读写速度快
  3. 原样保存变量、结构体、数组,适合存大批量数据、自定义类型;
  4. 没有专门的换行分隔符。

流和标准流

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入、输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念。我们可以把流想象成流淌着的字符的河流。c 程序针对文件、画面、键盘等的数据输入,输出的操作都是通过流操作的,一般情况下我们想要向流里写数据,或者从流中读取数据,都要打开流,然后操作。

标准流

那为什么我们从键盘输入数据,向屏幕上输出数据并没有打开流?那是因为 c 语言程序在启动的时候默认打开了三个流:

  • stdin 标准输入流 → 绑定键盘,scanf函数就是从标准输入流中读取数据的
  • stdout 标准输出流 → 绑定显示器,printf函数就是将信息输出到标准输出流中
  • stderr 标准错误流 → 绑定显示器,大多数环境中输出到显示器界面

这默认打开了这三个流,我们使用 scanf、printf 的函数就可以直接进行输入,输出操作的。stdin、stdout、stder三个流是类型是:FILE*,通常称为文件指针。 C 语言通过 FILE 文件指针 管理流。

文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。在编写程序的时候,在打开文件的同时都会返回一个 FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。

fclose

语法:int fclose ( FILE * stream );
  • 头文件:#include <stdio.h>
  • 形参:文件指针(必须是已经通过 fopen 成功打开的指针)
  • 返回值:
    • 关闭成功:返回 0
    • 关闭失败:返回 非 0 值

功能关闭形参 stream 所关联的文件,解除文件指针和文件之间的关联

  1. 解除内部缓冲区与流的绑定;
  2. 刷新输出缓冲区:缓冲区里还没写到磁盘的数据,全部写入文件(防止数据丢失);
  3. 清空输入缓冲区:缓冲区中还未读取的数据,直接丢弃。
  1. 参数FILE * 类型的流 / 文件指针。
  2. 返回值:成功返回 0,失败返回非 0 值。

fopen 打开文件

1. 函数原型

FILE *fopen(const char *filename, const char *mode);
  • 返回值:文件指针 FILE*
    • 打开成功:返回有效文件地址
    • 打开失败:返回 NULL(空指针)
  • 参数 1 filename:文件路径 + 文件名(同目录直接写文件名)
  • 参数 2 mode打开模式

标准使用模板

固定四步:打开 → 判断是否失败 → 读写 → 关闭

#include <stdio.h>

int main()
{
    FILE *fp;
    // 1. 打开文件
    fp = fopen("test.txt", "r"); 

    // 2. 必须判断打开是否失败(高频考点)
    if (fp == NULL)
    {
        printf("文件打开失败!\n");
        return 1; // 异常退出
    }

    // 3. 此处写 读/写 文件代码

    // 4. 关闭文件
    fclose(fp);
    fp = NULL; // 好习惯,置空指针
    return 0;
}

w 模式 写入文本文件

#include <stdio.h>

int main()
{
    FILE *fp;
    // 只写模式,不存在则创建,存在则清空
    fp = fopen("info.txt", "w");
    if (fp == NULL)
    {
        printf("打开失败\n");
        return 1;
    }

    // 向文件写入内容
    fprintf(fp, "Hello C File\n");
    fprintf(fp, "文件读写练习");

    fclose(fp); // 关闭
    return 0;
}

运行后同级目录生成 info.txt,内容被写入。

r 模式 读取文本文件

#include <stdio.h>

int main()
{
    FILE *fp;
    char buf[100];
    fp = fopen("info.txt", "r"); // 只读
    if (fp == NULL)
    {
        printf("文件不存在或打开失败\n");
        return 1;
    }

    // 读取一行
    fgets(buf, 100, fp);
    printf("%s", buf);

    fclose(fp);
    return 0;
}

示例 3:a 模式 追加内容

#include <stdio.h>

int main()
{
    FILE *fp;
    fp = fopen("info.txt", "a"); // 追加
    if (fp == NULL)
    {
        return 1;
    }

    fprintf(fp, "\n追加一行新内容");
    fclose(fp);
    return 0;
}

原有内容保留,新内容写在文件最后。

fwrite 向文件写入数据

参数写法口诀:地址 + 单个大小 + 个数 + 文件指针

写入模板

fwrite( &变量, sizeof(变量类型), 个数, 文件指针 );

fread 从文件读取数据

读取模板

fread( &变量, sizeof(变量类型), 个数, 文件指针 );

基础示例 1:读写普通变量

#include <stdio.h>

int main()
{
    FILE *fp;
    int num = 100;
    int res;

    // 1. 二进制只写模式打开
    fp = fopen("data.dat", "wb");
    if (fp == NULL)
    {
        printf("打开失败\n");
        return 1;
    }
    // 写入1个int数据
    fwrite(&num, sizeof(int), 1, fp);
    fclose(fp);

    // 2. 二进制只读模式打开
    fp = fopen("data.dat", "rb");
    if (fp == NULL)
    {
        printf("打开失败\n");
        return 1;
    }
    // 读取1个int数据
    fread(&res, sizeof(int), 1, fp);
    printf("读取到:%d\n", res);

    fclose(fp);
    return 0;
}

高频考点示例 2:结构体读写(必考)

这是考试最常考场景,搭配 wb/rb + 结构体。

#include <stdio.h>

// 定义结构体
struct Student
{
    char name[20];
    int age;
};

int main()
{
    FILE *fp;
    struct Student s1 = {"张三", 18};
    struct Student s2;

    // 写入:wb 二进制只写
    fp = fopen("stu.dat", "wb");
    if (fp == NULL) return 1;
    fwrite(&s1, sizeof(struct Student), 1, fp);
    fclose(fp);

    // 读取:rb 二进制只读
    fp = fopen("stu.dat", "rb");
    if (fp == NULL) return 1;
    fread(&s2, sizeof(struct Student), 1, fp);
    fclose(fp);

    // 输出读取内容
    printf("姓名:%s  年龄:%d\n", s2.name, s2.age);
    return 0;
}

数组批量读写

#include <stdio.h>

int main()
{
    FILE *fp;
    int arr[3] = {10, 20, 30};
    int brr[3];

    // 写入整个数组:3 个 int 数据
    fp = fopen("arr.dat", "wb");
    fwrite(arr, sizeof(int), 3, fp);
    fclose(fp);

    // 读取整个数组
    fp = fopen("arr.dat", "rb");
    fread(brr, sizeof(int), 3, fp);
    fclose(fp);

    for (int i = 0; i < 3; i++)
    {
        printf("%d ", brr[i]);
    }
    return 0;
}

文件的顺序读写:

表格

函数名 函数原型 核心功能 关键参数 返回值 高频考点
fopen

FILE *fopen

(char *filename, char *mode);

打开文件,建立文件指针与文件的关联

filename:文件路径 / 名

mode:打开方式

成功:返回文件FILE*指针

失败:返回NULL

必须判断打开是否成功;路径转义符\\;打开方式的选用
fclose

int fclose

(FILE *stream);

关闭文件,解除指针与文件的关联,刷新缓冲区 stream:已打开的文件指针

成功:返回0

失败:返回非 0 值

必须配合fopen使用;防止数据丢失;释放系统资源
fprintf

int fprintf

(FILE*stream, char *format, 输出列表);

文本文件顺序写入,按格式将内存数据写入文件 stream:文件指针format:格式控制串

成功:返回写入的字符数

失败:返回负数

文本文件写专用;用法和printf几乎一致,仅多文件指针参数
fscanf

int fscanf

(FILE*stream, char *format, 地址列表);

文本文件顺序读取,从文件读取数据存入内存变量 stream:文件指针format:格式控制串 成功:返回成功读取的变量数失败 / 到文件尾:返回EOF 文本文件读专用;变量必须加&;和scanf用法一致
fwrite

int fwrite

(void *ptr, int size, int count, FILE *stream);

二进制文件顺序写入,按字节块写入数据 ptr:数据首地址size:单个数据字节数count:数据个数 成功:返回实际写入的完整数据块数 二进制文件写专用;适合数组 / 结构体 / 大批量数据
fread

int fread

(void *ptr, int size, int count, FILE *stream);

二进制文件顺序读取,按字节块读取数据 ptr:接收数据的内存地址其余参数同fwrite 成功:返回实际读取的完整数据块数 二进制文件读专用;和fwrite配对使用

表格

流名称 系统预设指针 对应设备 核心作用 常用函数
标准输入流 stdin 键盘 从键盘读取数据,输入到程序中 scanfgetchar、fscanf(stdin,...)
标准输出流 stdout 显示器 把程序数据输出到屏幕上 printfputcharfprintf(stdout,...)
标准错误流 stderr 显示器 专门输出程序的错误、警告信息 报错提示专用

表格

打开方式 含义 核心规则 适用场景
r 只读(文本文件) 文件必须存在,否则打开失败;只能读,不能写 读取已有的文本文件数据
w 只写(文本文件) 文件不存在则新建;文件存在则清空原有全部内容,再写入 新建文本文件、覆盖原有文件内容
a 追加(文本文件) 文件不存在则新建;文件存在则在末尾续写内容,不覆盖原有数据 在已有文本文件末尾添加新内容
rb 只读(二进制文件) r,仅针对二进制文件 读取已有的二进制文件数据
wb 只写(二进制文件) w,仅针对二进制文件 新建 / 覆盖二进制文件
ab 追加(二进制文件) a,仅针对二进制文件 在二进制文件末尾续写内容

feof和ferror

feof + ferror 组合使用(综合判断)

同时判断:是读到末尾,还是读写出错

#include <stdio.h>

int main()
{
    FILE *fp;
    char buf[100];
    fp = fopen("test.txt", "r");
    if (fp == NULL) return 1;

    while (1)
    {
        fgets(buf, 100, fp);

        if (ferror(fp))   // 先判断是否出错
        {
            printf("文件读写异常\n");
            break;
        }
        if (feof(fp))     // 再判断是否到末尾
        {
            printf("读取完毕,到达文件尾\n");
            break;
        }

        printf("%s", buf);
    }

    fclose(fp);
    return 0;
}

表格

函数 原型 功能 返回值 使用场景
feof int feof(FILE *fp); 判断是否到达文件末尾

到文件尾:返回非 0

未到尾:返回0

循环读文件,判断是否读完
ferror int ferror(FILE *fp); 判断文件读写是否出错

出错:返回非 0

正常:返回0

检测读写过程

fputs和fgets

组合读写(写 → 读 完整流程)

#include <stdio.h>

int main()
{
    FILE *fp;
    char str[100];

    // 1. 写入
    fp = fopen("msg.txt", "w");
    fputs("Hello C\n", fp);
    fputs("fgets fputs 练习", fp);
    fclose(fp);

    // 2. 读取
    fp = fopen("msg.txt", "r");
    while (fgets(str, 100, fp) != NULL)
    {
        printf("%s", str);
    }
    fclose(fp);

    return 0;
}

表格

函数 功能 原型 适用文件 核心特点
fputs 向文件写入字符串 int fputs(char *str, FILE *fp); 文本文件 不自动加换行、\0 不写入文件
fgets 从文件读取字符串 char *fgets(char *str, int n, FILE *fp); 文本文件 最多读 n-1 个字符;读到 \n 会一并存入;遇文件尾返回NULL

规则总览

  1. 正常读到 换行符 \n
    • 只要还没读满 n-1 位,会把 \n 一并存入数组
    • 然后自动追加 \0 作为字符串结束。
  2. 未遇到 \n,但字符数达到 n-1
    • 停止读取,不接收后续字符,也不收 \n
    • 末尾直接补 \0
  3. 读到文件末尾:返回 NULL

和同类函数对比 & 易错点

1. fgets 与 gets

  • gets:不安全,无长度限制,容易越界,不推荐使用
  • fgets:带长度限制,安全,标准用法

2. fputs 与 puts

  • puts(字符串):输出到屏幕,自动加换行
  • fputs(字符串, fp):输出到文件,不会自动加换行,需手动写 \n

对比一组函数

一、输入类函数对比(scanf /fscanf/sscanf)

表格

函数名称 核心功能 标准函数原型 核心输入源 适用场景 核心特点 返回值规则 高频考点 / 易错点
scanf 标准输入流读取格式化数据

int scanf

(const char *format, 地址列表);

键盘(终端) 日常程序的终端手动输入、控制台数据读取

1. 系统自动打开标准输入流,无需手动开关

2. 输入数据以空格 / 回车 / 制表符分隔

3. 是最基础、最常用的输入函数

成功:返回成功读取的变量个数

失败 / 输入结束:返回 EOF(-1)

1. 读取变量必须加取地址符&(最常考易错点)

2. 格式符必须与变量类型严格匹配

3. 无法读取带空格的完整字符串

fscanf 文件流读取格式化数据

int fscanf

(FILE *fp, const char *format, 地址列表);

磁盘文本文件 从已打开的文本文件中读取数据、文件内容解析

1. 必须先通过fopen打开文件,获取有效文件指针fp

2. 用法与scanf几乎完全一致,仅多 1 个文件指针参数3. 按文件内容顺序读取,文件指针自动后移

成功:返回成功读取的变量个数失败 / 到文件末尾:返回 EOF(-1)

1. 必须先判断文件是否打开成功(fp == NULL

2. 仅适用于文本文件,二进制文件不推荐使用

3. 读取结束后必须用fclose关闭文件

sscanf 内存字符串中读取格式化数据

int sscanf

(const char *str, const char *format, 地址列表);

内存中的字符数组 / 字符串常量 字符串解析、从已有字符串中提取指定格式数据、字符串拆分

1. 输入源是内存里的字符串,不涉及键盘 / 文件操作

2. 不会修改原字符串,仅从其中读取数据3. 可实现复杂的字符串格式提取,是字符串处理高频函数

成功:返回成功读取的变量个数失败 / 字符串结束:返回 EOF(-1)

1. 第一个参数是字符串首地址,不是文件指针

2. 原字符串必须以\0结尾,否则会读取越界

3. 常与sprintf配对使用,实现字符串的格式化读写


二、输出类函数对比(printf /fprintf/sprintf)

fprintf 写入文件:fprintf(fp, "格式", 变量);

fscanf 读取文件:fscanf(fp, "格式", &变量);

表格

函数名称 核心功能 标准函数原型 核心输出目标 适用场景 核心特点 返回值规则 高频考点 / 易错点
printf 标准输出流输出格式化数据 int printf(const char *format, 输出列表); 显示器(终端屏幕) 日常程序的控制台结果输出、调试信息打印

1. 系统自动打开标准输出流,无需手动开关

2. 是最基础、最常用的输出函数

3. 输出内容直接显示在终端屏幕上

成功:返回成功输出的字符总数失败:返回负数 1. 格式符必须与输出变量类型严格匹配2. 输出字符串时,数组名 / 指针无需加&3. 可通过\n实现换行、\t实现制表对齐
fprintf 文件流输出格式化数据 int fprintf(FILE *fp, const char *format, 输出列表); 磁盘文本文件 向已打开的文本文件写入数据、生成带格式的文本文件

1. 必须先通过fopen打开文件,获取有效文件指针fp

2. 用法与printf几乎完全一致,仅多 1 个文件指针参数

3. 按顺序写入文件,文件指针自动后移

成功:返回成功写入的字符总数失败:返回负数

1. 必须先判断文件是否打开成功(fp == NULL

2. 仅适用于文本文件,二进制文件不推荐使用

3. 写入结束后必须用fclose关闭文件,否则数据可能丢失

sprintf 内存字符串输出格式化数据

int sprintf

(char *str, const char *format, 输出列表);

内存中的字符数组 字符串拼接、格式化生成新字符串、数字转字符串、字符串组装

1. 输出目标是内存里的字符数组,不涉及屏幕 / 文件操作

2. 自动在生成的字符串末尾添加结束符\0

3. 可实现复杂的字符串格式化拼接,是字符串处理高频函数

成功:返回成功写入的字符总数(不含末尾的\0)失败:返回负数

1. 第一个参数是目标字符数组首地址,不是文件指针

2. 目标数组必须足够大,否则会造成缓冲区溢出(核心易错点)

3. 常与sscanf配对使用,实现字符串的格式化读写


文件的随机读写

基本概念

  1. 顺序读写:从文件开头往后依次读写,文件指针只能向后移动,不能跳转。
  2. 随机读写:可人为移动文件指针,直接跳到文件任意位置读写,不用从头开始读。
  • 核心:靠 文件位置指针定位
  • 适用:二进制文件、结构体、数组(考试重点)

注意:随机读写一般搭配 二进制文件rb/wb),文本文件很少用。

表格

函数 功能 原型
fseek 移动文件位置指针(定位) int fseek(FILE *fp, long offset, int origin);
ftell 获取当前指针距离文件开头的字节数 long ftell(FILE *fp);
rewind 把指针重置到文件开头 void rewind(FILE *fp);

1. rewind () 重置文件指针到开头

1)功能

将文件位置指针直接跳回文件起始位置

2)语法

rewind(FILE *fp);
  • 无返回值,无额外参数。

3)使用场景

读完一遍文件后,想从头再读一次,不用重新 fopen

示例

rewind(fp);  // 指针回到文件开头

2. ftell () 获取当前指针位置

1)功能

计算当前文件指针 距离文件首部的字节数,返回字节偏移量。

2)语法

long ftell(FILE *fp);
  • 返回值:long 类型,当前偏移字节数。

3)典型用途

文件总大小

// 1.指针移到文件末尾
fseek(fp, 0, SEEK_END);
// 2.获取总字节数
long size = ftell(fp);
printf("文件大小:%ld 字节\n", size);

3. fseek () 重点(随机读写核心)

1)函数原型

int fseek(FILE *fp, long offset, int origin);

三个参数逐个拆解(必背

参数 1:FILE *fp

已打开的文件指针(一般二进制文件 rb/wb

参数 2:long offset 偏移量
  • 正数:向文件尾部 向后移动
  • 负数:向文件头部 向前移动
  • 单位:字节
参数 3:int origin 参考起点(三种固定宏,必须记)

表格

宏名 数值 参考位置
SEEK_SET 0 文件开头(最常用)
SEEK_CUR 1 当前指针位置
SEEK_END 2 文件末尾

2)组合写法(高频定位)

  1. 文件开头向后移 10 字节
fseek(fp, 10, SEEK_SET);
  1. 当前位置向后移 5 字节
fseek(fp, 5, SEEK_CUR);
  1. 文件末尾向前移 8 字节(读最后一段数据)
fseek(fp, -8, SEEK_END);
  1. 等价于 rewind(回到开头)
fseek(fp, 0, SEEK_SET);

3)返回值

  • 定位成功:返回 0
  • 定位失败:返回 非 0 值

文件缓冲区

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐