初识C语言(文件操作)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

本文主要讲解文件操作相关的知识。

一、文件是什么?

1.文件的作用与分类

持久化存储数据

程序运行时产生的数据存储在内存中,程序退出后内存被回收,数据丢失。文件将数据保存到磁盘(硬盘),实现数据的持久化存储,确保程序多次运行时可访问历史数据。

文件的分类

从功能角度分为程序文件和数据文件:

  • 程序文件 :包括源代码(.c)、目标文件(.obj)、可执行程序(.exe)。
  • 数据文件:存储程序运行时读写的数据,如输入数据或输出结果。本章重点讨论数据文件。

2.文件名结构

文件标识由三部分组成,例如 c:\code\test.txt

  • 文件路径c:\code\
  • 文件名主干test
  • 文件后缀.txt

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

根据数据组织形式分为两类:

  • 文本文件 :以ASCII码存储字符数据。例如整数10000以ASCII形式存储占5字节(每个字符1字节)。
  • 二进制文件 :直接以内存中的二进制形式存储数据。同一整数10000仅占4字节(按整型存储)。

存储差异示例

  • 文本文件:字符按ASCII存储,数值可转为ASCII或二进制。
  • 二进制文件:数据无转换,存储紧凑,效率更高。

4.数据文件的应用场景

  • 终端交互:默认从键盘输入、输出到显示器。
  • 磁盘文件:需长期保存或大量数据时,读写磁盘文件更高效。

二、文件的打开和关闭(C语言)

在C语言中,文件操作是程序与外部存储设备交互的重要方式。要操作文件,首先需要打开文件,操作完成后需要关闭文件。以下是关于文件打开和关闭的详细说明:

1. 文件打开(fopen函数)

基本语法

c 复制代码
FILE *fopen(const char *filename, const char *mode);

参数说明

  • filename:要打开的文件名(包含路径)
  • mode:打开模式,指定文件的操作方式

常用打开模式

模式 描述 文件存在 文件不存在
"r" 只读 打开成功 返回NULL
"w" 只写 清空内容 创建新文件
"a" 追加 追加内容 创建新文件
"r+" 读写 打开成功 返回NULL
"w+" 读写 清空内容 创建新文件
"a+" 读写 追加内容 创建新文件

二进制文件模式

在以上模式后加"b"表示二进制模式,如"rb"、"wb+"等

返回值

  • 成功:返回FILE指针(文件指针)
  • 失败:返回NULL

示例代码

c 复制代码
FILE *fp;
fp = fopen("example.txt", "r");
if(fp == NULL) {
    printf("文件打开失败\n");
    exit(1);  // 退出程序
}

2. 文件关闭(fclose函数)

基本语法

c 复制代码
int fclose(FILE *stream);

参数说明

  • stream:要关闭的文件指针

返回值

  • 成功:返回0
  • 失败:返回EOF(-1)

示例代码

c 复制代码
if(fclose(fp) != 0) {
    printf("文件关闭失败\n");
}

3. 注意事项

  1. 错误处理:每次打开文件后都应检查返回值是否为NULL
  2. 资源释放:打开的文件必须关闭,否则可能导致资源泄露
  3. 缓冲区刷新:fclose会刷新缓冲区,确保数据写入文件
  4. 路径表示:Windows中使用"\"或"/"表示路径,如"C:\data\file.txt"

4. 完整示例

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

int main() {
    FILE *fp;
    
    // 打开文件
    fp = fopen("data.txt", "w");
    if(fp == NULL) {
        printf("无法创建文件\n");
        return 1;
    }
    
    // 文件操作...
    fprintf(fp, "这是写入文件的内容\n");
    
    // 关闭文件
    if(fclose(fp) != 0) {
        printf("文件关闭出错\n");
        return 1;
    }
    
    return 0;
}

5. 常见问题

  1. 权限问题:如果没有写权限,以写模式打开文件会失败
  2. 文件锁定:某些系统会锁定正在使用的文件
  3. 路径问题:相对路径是相对于程序运行时的当前目录
  4. 缓冲区问题:未关闭文件可能导致数据未完全写入

三、文件的顺序读写

1. 基本概念

顺序读写是指按照文件中数据的物理存储顺序依次进行读写操作。与随机读写不同,顺序读写每次操作都会从当前位置开始,完成后文件指针会自动移动到下一个位置。

2. 顺序读写的函数

C语言提供了多个用于顺序读写文件的函数:

2.1 字符读写

  • fgetc(): 从文件中读取一个字符

    c 复制代码
    int ch = fgetc(fp);  // fp为文件指针
  • fputc(): 向文件写入一个字符

    c 复制代码
    fputc('A', fp);  // 向文件写入字符'A'

2.2 字符串读写

  • fgets(): 从文件中读取一行字符串

    c 复制代码
    char str[100];
    fgets(str, 100, fp);  // 最多读取99个字符
  • fputs(): 向文件写入字符串

    c 复制代码
    fputs("Hello World", fp);

2.3 格式化读写

  • fscanf(): 从文件格式化读取

    c 复制代码
    int age;
    fscanf(fp, "%d", &age);  // 从文件读取整数
  • fprintf(): 向文件格式化写入

    c 复制代码
    fprintf(fp, "Name: %s, Age: %d", "John", 25);

3. 应用示例

3.1 文件复制

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

int main() {
    FILE *src = fopen("source.txt", "r");
    FILE *dest = fopen("dest.txt", "w");
    
    if(src == NULL || dest == NULL) {
        printf("文件打开失败");
        return 1;
    }
    
    int ch;
    while((ch = fgetc(src)) != EOF) {
        fputc(ch, dest);
    }
    
    fclose(src);
    fclose(dest);
    return 0;
}

3.2 日志记录

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

void log_message(const char *msg) {
    FILE *log = fopen("app.log", "a");
    if(log == NULL) return;
    
    time_t now;
    time(&now);
    fprintf(log, "[%s] %s\n", ctime(&now), msg);
    fclose(log);
}

4. 注意事项

  1. 每次读写操作后,文件指针会自动后移
  2. 到达文件末尾时,fgetc()会返回EOF
  3. 打开文件后必须检查文件指针是否为NULL
  4. 操作完成后必须调用fclose()关闭文件
  5. 对于文本文件,不同操作系统可能有不同的换行符表示方式

5. 性能考虑

  • 对于大文件,逐个字符读写效率较低
  • 可以考虑使用缓冲区或块读写提高性能
  • 频繁打开关闭文件会影响性能,应尽量减少操作次数

四、文件的随机读写

1. 文件指针的概念

文件指针是一个指示当前读写位置的标记,它记录了在文件中进行操作的位置。在随机读写文件时,我们需要通过移动文件指针来定位到特定的位置进行操作。

文件指针具有以下特性:

  1. 打开文件时,指针默认指向文件开头(位置0)
  2. 每次读写操作后,指针会自动移动到下一个位置
  3. 可以通过特定函数手动移动指针位置

2. 文件指针定位函数

fseek函数

c 复制代码
int fseek(FILE *stream, long offset, int origin);
  • 参数说明:
    • stream:文件指针
    • offset:偏移量(字节数),可正可负
    • origin:基准位置,可取以下值:
      • SEEK_SET:文件开头
      • SEEK_CUR:当前位置
      • SEEK_END:文件末尾

示例:

c 复制代码
fseek(fp, 10, SEEK_SET);  // 将指针移动到距文件开头10字节处
fseek(fp, -5, SEEK_END);  // 将指针移动到距文件末尾前5字节处

ftell函数

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

返回当前文件指针的位置(相对于文件开头的字节偏移量)

示例:

c 复制代码
long pos = ftell(fp);  // 获取当前指针位置

rewind函数

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

将文件指针重置到文件开头,相当于:

c 复制代码
fseek(fp, 0, SEEK_SET);

3. 随机读写示例

示例1:读取文件中间部分内容

c 复制代码
FILE *fp = fopen("data.txt", "r");
if(fp == NULL) {
    perror("文件打开失败");
    return;
}

// 定位到文件中间位置
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, size/2, SEEK_SET);

// 读取中间部分内容
char buffer[100];
fread(buffer, 1, 50, fp);

fclose(fp);

示例2:修改二进制文件中的特定记录

c 复制代码
typedef struct {
    int id;
    char name[20];
    float score;
} Student;

// 修改第3条记录的成绩
FILE *fp = fopen("students.dat", "r+");
if(fp == NULL) {
    perror("文件打开失败");
    return;
}

// 定位到第3条记录
fseek(fp, 2 * sizeof(Student), SEEK_SET);

Student stu;
fread(&stu, sizeof(Student), 1, fp);

// 修改成绩
stu.score = 95.5;

// 写回文件
fseek(fp, 2 * sizeof(Student), SEEK_SET);
fwrite(&stu, sizeof(Student), 1, fp);

fclose(fp);

4. 注意事项

  1. 二进制文件与文本文件:

    • 文本文件的随机读写可能会因系统不同而产生差异
    • 二进制文件的随机读写更加可靠
  2. 边界检查:

    • 移动指针时要注意不要超出文件范围
    • 可以使用ftell和fseek结合检查文件大小
  3. 错误处理:

    • 检查fseek的返回值(成功返回0,失败返回非0)
    • 处理可能的文件定位错误
  4. 性能考虑:

    • 频繁的随机访问可能会降低IO性能
    • 对于大量随机访问,考虑使用内存缓存

五、文件缓冲区(C语言)

1. 缓冲区的概念与作用

文件缓冲区是内存中的一块区域,用于临时存储文件读写的数据。缓冲区的主要作用包括:

  1. 提高I/O效率:减少直接访问磁盘的次数,通过批量读写提高性能
  2. 协调速度差异:解决CPU处理速度与磁盘I/O速度不匹配的问题
  3. 数据暂存:为流式I/O操作提供数据中转站

示例场景:当程序调用fwrite()写入100字节数据时,数据不会立即写入磁盘,而是先存入缓冲区,待缓冲区满或显式刷新时才执行实际磁盘写入。

2. 缓冲区的类型

C语言中主要有三种缓冲模式:

  1. 全缓冲(Fully Buffered)

    • 缓冲区满时才执行实际I/O操作
    • 典型应用:普通文件读写
    • 缓冲区大小通常为BUFSIZ(在stdio.h中定义,通常为512或4096字节)
  2. 行缓冲(Line Buffered)

    • 遇到换行符'\n'或缓冲区满时刷新
    • 典型应用:终端I/O(stdin/stdout)
    • 示例:printf()输出通常会在遇到\n或程序正常结束时自动刷新
  3. 无缓冲(Unbuffered)

    • 立即执行I/O操作
    • 典型应用:stderr错误输出
    • 示例:perror()会立即将错误信息输出到屏幕

3. 缓冲区相关函数

  1. 设置缓冲模式

    c 复制代码
    int setvbuf(FILE *stream, char *buf, int mode, size_t size);
    • 参数说明:
      • stream:文件指针
      • buf:用户提供的缓冲区(为NULL时由库自动分配)
      • mode:_IOFBF(全缓冲)/_IOLBF(行缓冲)/_IONBF(无缓冲)
      • size:缓冲区大小
  2. 强制刷新缓冲区

    c 复制代码
    int fflush(FILE *stream);
    • 将缓冲区内容立即写入文件
    • stream为NULL时刷新所有输出流
  3. 关闭文件时的自动刷新

    c 复制代码
    int fclose(FILE *stream);
    • 关闭文件前会自动调用fflush()
    • 示例:程序异常终止时可能丢失未刷新的缓冲区数据

4. 缓冲区使用注意事项

  1. 数据一致性问题

    • 重要数据应及时fflush(),防止程序崩溃导致数据丢失
    • 示例:数据库日志记录应先fflush()再执行关键操作
  2. 性能调优

    • 大文件处理时可适当增大缓冲区提高性能
    • 示例:视频处理程序可设置8KB或更大的缓冲区
  3. 跨平台差异

    • 不同系统下默认缓冲区大小可能不同
    • 示例:Windows和Linux的BUFSIZ可能不同
  4. 线程安全

    • 多线程环境下操作同一文件需加锁保护
    • 示例:使用flockfile()/funlockfile()函数族

5. 缓冲区示例代码

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

int main() {
    FILE *fp = fopen("test.dat", "w");
    if (!fp) {
        perror("fopen failed");
        return EXIT_FAILURE;
    }

    // 设置8KB的全缓冲区
    char buf[8192];
    if (setvbuf(fp, buf, _IOFBF, sizeof(buf)) != 0) {
        perror("setvbuf failed");
        fclose(fp);
        return EXIT_FAILURE;
    }

    // 写入数据(先存入缓冲区)
    for (int i = 0; i < 1000; i++) {
        fprintf(fp, "Record %d\n", i);
    }

    // 强制刷新缓冲区
    fflush(fp);
    
    // 修改为行缓冲模式
    setvbuf(fp, NULL, _IOLBF, 0);
    fprintf(fp, "This will be line buffered\n");
    
    fclose(fp);
    return EXIT_SUCCESS;
}

六、更新文件(C语言)

1. 文件更新概述

在C语言中,文件更新操作主要包括以下几种方式:

  • 追加内容到文件末尾
  • 修改文件中特定位置的内容
  • 替换文件中的部分内容
  • 插入新内容到文件指定位置

2. 基本操作函数

2.1 fopen()函数

c 复制代码
FILE *fopen(const char *filename, const char *mode);

常用打开模式:

  • "r":只读
  • "w":只写(会清空原文件)
  • "a":追加(在文件末尾写入)
  • "r+":读写(从文件头开始)
  • "w+":读写(清空原文件)
  • "a+":读写(从文件末尾开始)

2.2 fseek()函数

c 复制代码
int fseek(FILE *stream, long offset, int whence);

参数说明:

  • whence取值:
    • SEEK_SET:从文件头开始
    • SEEK_CUR:从当前位置开始
    • SEEK_END:从文件末尾开始

3. 常见更新操作示例

3.1 追加内容

c 复制代码
FILE *fp = fopen("example.txt", "a");
if(fp != NULL) {
    fprintf(fp, "This will be appended to the end.\n");
    fclose(fp);
}

3.2 修改指定位置内容

c 复制代码
FILE *fp = fopen("example.dat", "r+");
if(fp != NULL) {
    fseek(fp, 10, SEEK_SET); // 定位到第10字节
    fwrite("new data", 1, 8, fp); // 写入8字节新数据
    fclose(fp);
}

3.3 插入内容

c 复制代码
// 1. 打开源文件和临时文件
FILE *src = fopen("source.txt", "r");
FILE *tmp = fopen("temp.txt", "w");

// 2. 复制前半部分到临时文件
int pos = 0;
while(pos < insert_position) {
    int ch = fgetc(src);
    fputc(ch, tmp);
    pos++;
}

// 3. 写入要插入的内容
fputs("New inserted content\n", tmp);

// 4. 复制剩余部分
int ch;
while((ch = fgetc(src)) != EOF) {
    fputc(ch, tmp);
}

// 5. 关闭文件并替换
fclose(src);
fclose(tmp);
remove("source.txt");
rename("temp.txt", "source.txt");

4. 注意事项

  1. 错误处理:所有文件操作都应检查返回值
  2. 缓冲区刷新:必要时使用fflush()确保数据写入
  3. 文件锁定:在多进程/线程环境中可能需要文件锁定
  4. 性能考虑:频繁的小规模更新可能影响性能

5. 高级技巧

5.1 内存映射文件

对于大文件更新,可以使用内存映射提高效率:

c 复制代码
#include <sys/mman.h>
// 使用mmap()函数将文件映射到内存

5.2 事务处理

实现简单的文件更新事务:

  1. 创建临时文件
  2. 执行所有更新操作
  3. 确认无误后替换原文件
  4. 出现错误则回滚

6. 实际应用场景

  1. 日志文件更新:追加新日志记录
  2. 配置文件修改:更新特定配置项
  3. 数据库操作:修改数据记录
  4. 游戏存档:更新玩家进度

总结

文件操作对于信息的储存和再利用非常重要。

相关推荐
kaozhengpro2 小时前
Microsoft DP-700 考試戰報|Fabric 資料工程師一次通過心得
运维·microsoft·fabric
智者知已应修善业2 小时前
【删除有序数组中的重复项 II之O(N)算法】2024-1-31
c语言·c++·经验分享·笔记·算法
代码游侠3 小时前
应用——管道与文件描述符
linux·服务器·c语言·学习·算法
GoWjw3 小时前
C语言高级特性
c语言·开发语言·算法
不染尘.3 小时前
虚拟网络环境及socket概述
linux·c语言·网络·windows·计算机网络
加成BUFF3 小时前
C++入门详解2:数据类型、运算符与表达式
c语言·c++·计算机
程序员zgh4 小时前
C++常用设计模式
c语言·数据结构·c++·设计模式
消失的旧时光-19434 小时前
从 Android 回调到 C 接口:函数指针 + void* self 的一次彻底理解
android·c语言·开发语言
尘诞辰4 小时前
【C语言】数据在内存中的储存
c语言·开发语言·数据结构·c++