18_C语言文件操作_文本文件二进制文件与文件定位

C语言文件操作:文本文件、二进制文件与文件定位

一、本篇文章要解决什么问题

你之前写的所有程序都有一个共同特点:数据存在变量里,程序一关,所有数据就没了。真实世界的程序需要持久化存储------把数据存到硬盘上,下次打开程序数据还在。

这篇文章帮你搞定三件事:

  1. C 语言怎么读写文件------fopen、fclose、fprintf、fscanf 的基本用法
  2. 文本文件和二进制文件有什么区别,分别用什么函数读写
  3. 怎么在文件里"跳来跳去"------fseek 和 ftell 的用法

二、先用一个简单例子理解

2.1 文件就是"笔记本"

  • 打开文件(fopen):翻开笔记本到某一页,准备好笔
  • 写入(fprintf):在纸上写字
  • 读取(fscanf):读纸上的字
  • 关闭(fclose) :合上笔记本放回书架------这一步非常重要,不合上可能导致内容丢失

2.2 文本文件 vs 二进制文件

  • 文本文件:像 Word 文档------内容是人类可读的字符。用记事本打开能看到内容。
  • 二进制文件:像 MP3 文件------内容是字节序列。用记事本打开看到的是乱码,但程序能精确解读。

三、核心知识点讲解

3.1 文件指针 FILE 和 fopen

C 语言通过 FILE * 类型的指针来操作文件:

c 复制代码
FILE *fp = fopen("data.txt", "w");   // 以"写入"模式打开
if (fp == NULL)
{
    printf("文件打开失败!\n");
    return 1;    // 必须检查!
}

fopen 的常用模式:

模式 含义 文件不存在时 文件存在时
"r" 只读 返回 NULL 从开头读
"w" 只写 创建新文件 清空内容重写
"a" 追加写入 创建新文件 在末尾追加
"r+" 读写 返回 NULL 从开头读写
"w+" 读写 创建新文件 清空内容
"rb" / "wb" 二进制读 / 写 同上 同上(二进制模式)

每次 fopen 之后必须检查返回值是否为 NULL------文件可能因为权限、路径不存在等原因打开失败。

图18-1 程序与文件交互图:帮读者建立"程序操作文件"的完整概念模型。

图18-2 fopen 各模式行为表:供读者速查 fopen 模式的差异。

3.2 fprintf 和 fscanf------文本文件的读写

c 复制代码
#include <stdio.h>

int main(void)
{
    FILE *fp;

    // 写入
    fp = fopen("score.txt", "w");
    if (fp == NULL)
    {
        printf("无法创建文件\n");
        return 1;
    }
    fprintf(fp, "Tom 90\n");
    fprintf(fp, "Jerry 85\n");
    fclose(fp);

    // 读取
    fp = fopen("score.txt", "r");
    if (fp == NULL)
    {
        printf("无法打开文件\n");
        return 1;
    }
    char name[20];
    int score;
    while (fscanf(fp, "%19s %d", name, &score) == 2)
    {
        printf("%s: %d 分\n", name, score);
    }
    fclose(fp);

    return 0;
}

运行结果:

text 复制代码
Tom: 90 分
Jerry: 85 分

3.3 fgets 和 fputs------按行读写文本

c 复制代码
// 逐行写入
fputs("第一行内容\n", fp);
fputs("第二行内容\n", fp);

// 逐行读取
char line[100];
while (fgets(line, sizeof(line), fp) != NULL)
{
    printf("%s", line);
}

3.4 fread 和 fwrite------二进制读写

二进制读写按"数据块"来操作,适合结构化数据(如结构体):

c 复制代码
#include <stdio.h>

typedef struct
{
    int id;
    double score;
} Record;

int main(void)
{
    Record r1 = {1001, 95.5};
    Record r2;

    // 二进制写入
    FILE *fp = fopen("record.bin", "wb");
    if (fp == NULL) return 1;
    if (fwrite(&r1, sizeof(Record), 1, fp) != 1)
    {
        printf("写入失败\n");
    }
    fclose(fp);

    // 二进制读取
    fp = fopen("record.bin", "rb");
    if (fp == NULL) return 1;
    if (fread(&r2, sizeof(Record), 1, fp) != 1)
    {
        printf("读取失败\n");
    }
    fclose(fp);

    printf("读取:id=%d, score=%.1f\n", r2.id, r2.score);
    return 0;
}

图18-3 文本文件与二进制文件的存储对比:解释两种文件模式的本质区别。

3.5 fseek 和 ftell------在文件中定位

c 复制代码
fseek(fp, 0, SEEK_SET);    // 跳到文件开头
fseek(fp, 0, SEEK_END);    // 跳到文件末尾
fseek(fp, 5, SEEK_CUR);    // 从当前位置往后跳 5 字节

long pos = ftell(fp);       // 获取当前读写位置

图18-4 fseek 文件定位示意图:直观解释 fseek 的三个定位基准。

四、完整代码示例

一个"日记本"程序,支持追加写入和全文阅读:

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main(void)
{
    FILE *fp;
    char line[200];
    int choice;

    do
    {
        printf("\n===== 简易日记本 =====\n");
        printf("1. 写一篇日记\n");
        printf("2. 阅读所有日记\n");
        printf("0. 退出\n");
        printf("请选择:");
        scanf("%d", &choice);
        int ch;
        while ((ch = getchar()) != '\n' && ch != EOF)  // 安全清缓冲区
        {
            ;
        }

        switch (choice)
        {
        case 1:
            fp = fopen("diary.txt", "a");   // 追加模式
            if (fp == NULL)
            {
                printf("文件打开失败\n");
                break;
            }
            printf("请输入内容(一行):");
            fgets(line, sizeof(line), stdin);
            fprintf(fp, "%s", line);         // fgets 已包含 \n
            fclose(fp);
            printf("已保存!\n");
            break;

        case 2:
            fp = fopen("diary.txt", "r");
            if (fp == NULL)
            {
                printf("还没有日记,先写一篇吧\n");
                break;
            }
            printf("\n===== 历史日记 =====\n");
            while (fgets(line, sizeof(line), fp) != NULL)
            {
                printf("%s", line);
            }
            fclose(fp);
            break;

        case 0:
            printf("再见!\n");
            break;

        default:
            printf("无效选择\n");
        }
    } while (choice != 0);

    return 0;
}

五、运行结果

text 复制代码
===== 简易日记本 =====
1. 写一篇日记
2. 阅读所有日记
0. 退出
请选择:1
请输入内容(一行):今天学习了 C 语言文件操作
已保存!

===== 简易日记本 =====
1. 写一篇日记
2. 阅读所有日记
0. 退出
请选择:2

===== 历史日记 =====
今天学习了 C 语言文件操作

六、代码逐行解析

追加模式打开文件:

c 复制代码
fp = fopen("diary.txt", "a");
  • "a" 模式:文件存在时在末尾追加,不存在时创建
  • 不会覆盖原有内容------这是日记本的关键

scanf 后清缓冲区再接 fgets:

c 复制代码
scanf("%d", &choice);
int ch;
while ((ch = getchar()) != '\n' && ch != EOF)  // 安全清掉残留 \n
{
    ;
}
fgets(line, sizeof(line), stdin);   // 正常读取

这是第 8 篇重点讲过的缓冲区处理模式------在文件操作中同样重要。

fgets 读取到文件末尾自动返回 NULL:

c 复制代码
while (fgets(line, sizeof(line), fp) != NULL)

这是最标准的"按行读取整个文件"的写法------fgets 读到文件末尾时返回 NULL,循环自动停止。


七、初学者常见错误

错误1:fopen 后不检查 NULL 就直接用

c 复制代码
FILE *fp = fopen("noexist.txt", "r");
fprintf(fp, "data");    // fp 是 NULL,程序崩溃!

错误2:忘了 fclose

c 复制代码
fp = fopen("data.txt", "w");
fprintf(fp, "hello");
// 忘了 fclose!数据可能还在缓冲区没写进硬盘

错误3:以 "w" 模式打开一个要保留内容的文件

c 复制代码
// 本来想在原有内容后面追加,结果用了 "w"------旧内容全没了
fp = fopen("data.txt", "w");    // 应该用 "a"

错误4:fscanf 读取时的格式与文件中内容不匹配

c 复制代码
// 文件内容:Tom 90
// 你写的:fscanf(fp, "%d %s", &score, name);  顺序错了!
// 结果 score 读不到数字,返回 EOF

错误5:二进制文件用文本模式打开(或反之)

c 复制代码
// 写二进制数据但用了 "w" 而不是 "wb"
// 在 Windows 上,\n 可能被转换为 \r\n,破坏二进制数据

八、练习题

练习题1:写入并读取数字

让用户输入 5 个整数,程序把它们写入文件 numbers.txt(每行一个数字)。然后重新打开文件,把数字读出来,计算并输出它们的和。

练习题2:学生成绩文件存储

定义结构体 Student(学号、姓名、成绩),用户输入 3 个学生的数据,用 fprintf 写入 students.txt(每个学生一行,数据之间用空格分隔)。然后从文件中读取所有学生数据并显示。

练习题3:文件大小计算

用 fseek 和 ftell 写一个程序,打开任意文件(由用户输入文件名),计算并输出该文件的字节大小。

提示:fseek 跳到文件末尾,ftell 获取当前位置(即文件大小)。


九、本篇总结

  1. 文件操作用 FILE * 指针 ,fopen 打开后必须检查 NULL ,用完必须 fclose
  2. 文本文件 用 fprintf/fscanf 或 fgets/fputs,二进制文件用 fread/fwrite
  3. fopen 模式要选对"r" 只读,"w" 清空重写,"a" 追加
  4. fseek 和 ftell 可以在文件中任意定位,用于获取文件大小、跳过不需要的内容
  5. 文件操作后记得 fclose,否则数据可能丢失

相关推荐
LDR00610 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术10 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
通信小呆呆10 天前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
码云数智-园园10 天前
C++20 Modules 模块详解
java·开发语言·spring
H__Rick10 天前
自动对焦学习-3
人工智能·学习·计算机视觉
Daisy Lee10 天前
量化学习-第1章-什么是量化金融
学习·金融·datawhale
swordbob10 天前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio
源分享11 天前
Java线程同步的多种实现方法(非常详细)
java·开发语言·jvm
Luminous.11 天前
C语言--day30
c语言·开发语言
Alsn8611 天前
等待学习-学习目录:Docker 容器安全攻防
学习·安全·docker