D20—C语言文件操作详解:从基础到高级应用

文章目录

引言

[1. 为什么使用文件?](#1. 为什么使用文件?)

[2. 什么是文件?](#2. 什么是文件?)

[2.1 文件分类](#2.1 文件分类)

[2.2 文件名](#2.2 文件名)

[3. 二进制文件和文本文件](#3. 二进制文件和文本文件)

[3.1 区别](#3.1 区别)

[3.2 示例对比](#3.2 示例对比)

[4. 文件的打开和关闭](#4. 文件的打开和关闭)

[4.1 流和标准流](#4.1 流和标准流)

[4.2 文件指针](#4.2 文件指针)

[4.3 打开和关闭文件](#4.3 打开和关闭文件)

[4.4 文件打开模式](#4.4 文件打开模式)

[5. 文件的顺序读写](#5. 文件的顺序读写)

[5.1 顺序读写函数](#5.1 顺序读写函数)

[5.2 函数对比](#5.2 函数对比)

[6. 文件的随机读写](#6. 文件的随机读写)

[6.1 fseek函数](#6.1 fseek函数)

[6.2 ftell函数](#6.2 ftell函数)

[6.3 rewind函数](#6.3 rewind函数)

[7. 文件读取结束的判定](#7. 文件读取结束的判定)

[7.1 错误使用feof的误区](#7.1 错误使用feof的误区)

[7.2 正确的判断方法](#7.2 正确的判断方法)

[7.3 示例代码](#7.3 示例代码)

[8. 文件缓冲区](#8. 文件缓冲区)

[8.1 缓冲文件系统](#8.1 缓冲文件系统)

[8.2 缓冲区的重要性](#8.2 缓冲区的重要性)

[8.3 刷新缓冲区](#8.3 刷新缓冲区)

[8.4 缓冲模式设置](#8.4 缓冲模式设置)

[9. 总结与最佳实践](#9. 总结与最佳实践)

[9.1 文件操作流程](#9.1 文件操作流程)

[9.2 错误处理要点](#9.2 错误处理要点)

[9.3 性能优化建议](#9.3 性能优化建议)

[9.4 常见陷阱与解决方案](#9.4 常见陷阱与解决方案)

结语


引言

在C语言编程中,文件操作是数据持久化的核心。程序运行时产生的数据如果仅存储在内存中,程序结束后就会丢失。文件操作使得数据能够长期保存在磁盘上,实现数据的持久化存储。本文将全面介绍C语言中的文件操作,从基本概念到高级应用,帮助你掌握文件处理的各项技能。

1. 为什么使用文件?

程序中的数据通常存储在内存中,当程序退出时,内存被回收,数据也随之丢失。为了实现数据的长期保存和重复使用,我们需要将数据存储到磁盘文件中。文件操作使得程序能够:

  • 持久化保存数据

  • 在不同运行实例间共享数据

  • 处理大规模数据而不受内存限制


2. 什么是文件?

2.1 文件分类

从功能角度,文件可分为两类:

  • 程序文件:包含程序代码的文件,如源文件(.c)、目标文件(.obj)、可执行文件(.exe)

  • 数据文件:程序运行时读写的数据文件,用于存储输入输出数据

本章主要讨论数据文件。

2.2 文件名

文件名由三部分组成:文件路径 + 文件名主干 + 文件后缀

例如:c:\code\test.txt


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

3.1 区别

  • 文本文件:以ASCII码形式存储,每个字符占1字节,便于人类阅读

  • 二进制文件:以二进制形式存储,与内存中数据格式一致,节省空间

3.2 示例对比

整数10000的存储:

  • 文本文件:ASCII形式存储"10000",占用5字节

  • 二进制文件:二进制形式存储,占用4字节(假设int为4字节)

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

int main() 
{
    int a = 10000;
    FILE* pf = fopen("test.dat", "wb");
    fwrite(&a, sizeof(int), 1, pf);  // 二进制形式写入
    fclose(pf);
    return 0;
}

4. 文件的打开和关闭

4.1 流和标准流

C语言通过的概念来统一各种输入输出设备的操作。程序启动时自动打开三个标准流:

  • stdin:标准输入流(通常为键盘)

  • stdout:标准输出流(通常为显示器)

  • stderr:标准错误流(通常为显示器)

4.2 文件指针

FILE结构体存储文件的详细信息,通过FILE*指针访问文件。

4.3 打开和关闭文件

cpp 复制代码
// 打开文件
FILE* fopen(const char* filename, const char* mode);

// 关闭文件
int fclose(FILE* stream);

4.4 文件打开模式

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

int main() 
{
    FILE* pFile = fopen("myfile.txt", "w");
    if (pFile != NULL) 
    {
        fputs("Hello, World!", pFile);
        fclose(pFile);
    }
    return 0;
}

5. 文件的顺序读写

5.1 顺序读写函数

5.2 函数对比

cpp 复制代码
// 格式化输入函数家族
scanf();    // 从标准输入读取
fscanf();   // 从指定流读取
sscanf();   // 从字符串读取

// 格式化输出函数家族
printf();   // 向标准输出写入
fprintf();  // 向指定流写入
sprintf();  // 向字符串写入

示例

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

int main() {
    FILE* fp = fopen("data.txt", "w");
    if (fp) {
        fprintf(fp, "Number: %d, Float: %.2f, String: %s\n", 42, 3.14, "Hello");
        fclose(fp);
    }
    
    // 从文件读取
    fp = fopen("data.txt", "r");
    if (fp) {
        int num;
        float f;
        char str[20];
        fscanf(fp, "Number: %d, Float: %f, String: %s\n", &num, &f, str);
        printf("Read: %d, %.2f, %s\n", num, f, str);
        fclose(fp);
    }
    return 0;
}

6. 文件的随机读写

6.1 fseek函数

cpp 复制代码
int fseek(FILE* stream, long int offset, int origin);

参数origin的取值:

  • SEEK_SET:文件开头

  • SEEK_CUR:当前位置

  • SEEK_END:文件结尾

示例

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

int main() {
    FILE* pFile = fopen("example.txt", "wb");
    fputs("This is an apple.", pFile);
    fseek(pFile, 9, SEEK_SET);  // 移动到第9字节
    fputs("sam", pFile);         // 覆盖"apple"为"sam"
    fclose(pFile);
    return 0;
}
// 结果:文件内容变为"This is a sample."

6.2 ftell函数

cpp 复制代码
long int ftell(FILE* stream);

返回文件指针相对于起始位置的偏移量。

示例

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

int main() {
    FILE* pFile = fopen("myfile.txt", "rb");
    if (pFile) {
        fseek(pFile, 0, SEEK_END);
        long size = ftell(pFile);
        printf("File size: %ld bytes\n", size);
        fclose(pFile);
    }
    return 0;
}

6.3 rewind函数

cpp 复制代码
void rewind(FILE* stream);

将文件指针重新定位到文件开头。

示例

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

int main() {
    FILE* pFile = fopen("myfile.txt", "w+");
    for (char c = 'A'; c <= 'Z'; c++) {
        fputc(c, pFile);
    }
    
    rewind(pFile);  // 回到文件开头
    
    char buffer[27];
    fread(buffer, 1, 26, pFile);
    buffer[26] = '\0';
    printf("%s\n", buffer);  // 输出ABCDEFGHIJKLMNOPQRSTUVWXYZ
    
    fclose(pFile);
    return 0;
}

7. 文件读取结束的判定

7.1 错误使用feof的误区

feof()函数不能直接用于判断文件是否结束,它用于判断文件结束的原因是否为遇到文件尾。

7.2 正确的判断方法

文本文件

  • fgetc:判断返回值是否为EOF

  • fgets:判断返回值是否为NULL

二进制文件

  • fread:判断返回值是否小于实际要读的个数

7.3 示例代码

文本文件读取结束判断

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE* fp = fopen("test.txt", "r");
    if (!fp) {
        perror("File opening failed");
        return EXIT_FAILURE;
    }
    
    int c;
    while ((c = fgetc(fp)) != EOF) {
        putchar(c);
    }
    
    if (ferror(fp)) {
        puts("I/O error when reading");
    } else if (feof(fp)) {
        puts("End of file reached successfully");
    }
    
    fclose(fp);
    return 0;
}

二进制文件读取结束判断

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

#define SIZE 5

int main() {
    double a[SIZE] = {1., 2., 3., 4., 5.};
    FILE* fp = fopen("test.bin", "wb");
    fwrite(a, sizeof(*a), SIZE, fp);
    fclose(fp);
    
    double b[SIZE];
    fp = fopen("test.bin", "rb");
    size_t ret_code = fread(b, sizeof(*b), SIZE, fp);
    
    if (ret_code == SIZE) {
        puts("Array read successfully");
        for (int i = 0; i < SIZE; i++) {
            printf("%f ", b[i]);
        }
        putchar('\n');
    } else {
        if (feof(fp)) {
            printf("Unexpected end of file\n");
        } else if (ferror(fp)) {
            perror("Error reading file");
        }
    }
    
    fclose(fp);
    return 0;
}

8. 文件缓冲区

8.1 缓冲文件系统

ANSIC标准采用缓冲文件系统,系统自动为每个打开的文件在内存中创建文件缓冲区。

工作原理

  • 输出时:数据先送到缓冲区,装满后才一起写入磁盘

  • 输入时:数据从磁盘读入缓冲区,再从缓冲区送到程序数据区

8.2 缓冲区的重要性

缓冲区机制提高了文件操作效率,但也带来了一些注意事项:

  1. 需要适时刷新缓冲区

  2. 关闭文件时会自动刷新缓冲区

  3. 程序异常结束时可能丢失缓冲区数据

8.3 刷新缓冲区

cpp 复制代码
#include <stdio.h>
#include <windows.h>

int main() {
    FILE* pf = fopen("test.txt", "w");
    fputs("abcdef", pf);  // 数据写入缓冲区
    
    printf("睡眠10秒 - 此时打开test.txt可能看不到内容\n");
    Sleep(10000);
    
    printf("刷新缓冲区\n");
    fflush(pf);  // 强制刷新缓冲区到磁盘
    
    printf("再睡眠10秒 - 现在文件有内容了\n");
    Sleep(10000);
    
    fclose(pf);  // 关闭文件也会刷新缓冲区
    pf = NULL;
    
    return 0;
}

8.4 缓冲模式设置

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

int main() {
    // 设置缓冲区大小为1024字节
    char buffer[1024];
    FILE* fp = fopen("test.txt", "w");
    setvbuf(fp, buffer, _IOFBF, sizeof(buffer));  // 全缓冲
    
    fputs("This is a test", fp);
    
    // 立即刷新缓冲区
    fflush(fp);
    
    fclose(fp);
    return 0;
}

9. 总结与最佳实践

9.1 文件操作流程

  1. 打开文件 :使用fopen(),检查返回值是否为NULL

  2. 读写操作:根据需求选择合适的读写函数

  3. 关闭文件 :使用fclose(),确保资源释放

9.2 错误处理要点

cpp 复制代码
FILE* fp = fopen("file.txt", "r");
if (fp == NULL) {
    perror("Error opening file");
    // 处理错误
}

// 读写操作...

if (ferror(fp)) {
    perror("Error reading/writing file");
}

fclose(fp);

9.3 性能优化建议

  1. 批量读写 :使用fread/fwrite批量操作,减少系统调用

  2. 合理缓冲区:根据数据量设置合适的缓冲区大小

  3. 避免频繁开关:对同一文件多次操作时,保持文件打开

  4. 使用二进制格式:大数据量时使用二进制格式提高效率

9.4 常见陷阱与解决方案

结语

掌握C语言文件操作是成为合格程序员的重要一步。通过理解文件系统的基本原理、熟练使用各种文件操作函数,并遵循最佳实践,你可以编写出高效、健壮的文件处理程序。文件操作不仅在数据处理、日志记录等常规场景中至关重要,在系统编程、嵌入式开发等领域也有广泛应用。


欢迎在评论区交流讨论,如果觉得有帮助,请点赞收藏支持!

更多C语言技术文章,请访问我的博客主页:我能坚持多久-CSDN博客

相关推荐
橘子师兄5 小时前
C++AI大模型接入SDK—ChatSDK封装
开发语言·c++·人工智能·后端
上天_去_做颗惺星 EVE_BLUE5 小时前
Docker高效使用指南:从基础到实战模板
开发语言·ubuntu·docker·容器·mac·虚拟环境
2401_857683545 小时前
C++中的原型模式
开发语言·c++·算法
s1hiyu5 小时前
C++动态链接库开发
开发语言·c++·算法
(❁´◡`❁)Jimmy(❁´◡`❁)5 小时前
CF2188 C. Restricted Sorting
c语言·开发语言·算法
星火开发设计5 小时前
C++ 预处理指令:#include、#define 与条件编译
java·开发语言·c++·学习·算法·知识
许泽宇的技术分享5 小时前
第 1 章:认识 Claude Code
开发语言·人工智能·python
想放学的刺客5 小时前
单片机嵌入式试题(第27期)设计可移植、可配置的外设驱动框架的关键要点
c语言·stm32·单片机·嵌入式硬件·物联网
AIFQuant6 小时前
如何利用免费股票 API 构建量化交易策略:实战分享
开发语言·python·websocket·金融·restful