第七章 【C语言篇:文件】 文件全面解析

目录

[1. 文件操作](#1. 文件操作)

[1.1 C文件概述](#1.1 C文件概述)

[1.1.1 文件的基本概念](#1.1.1 文件的基本概念)

[1.1.2 文件缓冲区](#1.1.2 文件缓冲区)

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

[1.2.1 FILE结构体](#1.2.1 FILE结构体)

[1.2.2 标准文件指针](#1.2.2 标准文件指针)

[1.2.3 文件指针的声明](#1.2.3 文件指针的声明)

[1.3 文件的打开与关闭](#1.3 文件的打开与关闭)

[1.3.1 文件打开函数(fopen)](#1.3.1 文件打开函数(fopen))

[1.3.2 文件关闭函数(fclose)](#1.3.2 文件关闭函数(fclose))

[1.3.3 文件打开与关闭的最佳实践](#1.3.3 文件打开与关闭的最佳实践)

[1.4 文件的读写](#1.4 文件的读写)

[1.4.1 字符读写函数(fgetc和fputc)](#1.4.1 字符读写函数(fgetc和fputc))

[1.4.2 字符串读写函数(fgets和fputs)](#1.4.2 字符串读写函数(fgets和fputs))

[1.4.3 数据块读写函数(fread和fwrite)](#1.4.3 数据块读写函数(fread和fwrite))

[1.4.4 格式化读写函数(fscanf和fprintf)](#1.4.4 格式化读写函数(fscanf和fprintf))

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

[1.5.1 文件定位函数](#1.5.1 文件定位函数)

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

[1.5.3 Linux下的文件定位](#1.5.3 Linux下的文件定位)

[1.6 文件检测函数](#1.6 文件检测函数)

[1.6.1 文件结束检测函数(feof)](#1.6.1 文件结束检测函数(feof))

[1.6.2 读写文件出错检测函数(ferror)](#1.6.2 读写文件出错检测函数(ferror))

[1.6.3 文件出错标志和文件结束标志置0函数(clearerr)](#1.6.3 文件出错标志和文件结束标志置0函数(clearerr))

[1.7 C库文件与系统文件](#1.7 C库文件与系统文件)

[1.7.1 标准C库文件函数 vs 系统调用](#1.7.1 标准C库文件函数 vs 系统调用)

[1.7.2 文件描述符与文件指针的转换](#1.7.2 文件描述符与文件指针的转换)

[1.8 本章小结](#1.8 本章小结)

[1.8.1 文件操作核心函数总结](#1.8.1 文件操作核心函数总结)

[1.8.2 最佳实践](#1.8.2 最佳实践)

[1.8.3 Linux特有功能](#1.8.3 Linux特有功能)

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

[1.8.5 常见错误与调试技巧](#1.8.5 常见错误与调试技巧)

[1.8.6 实际应用场景](#1.8.6 实际应用场景)


本文摘要主要介绍了C语言中的文件操作相关知识,内容涵盖文件基本概念、文件指针、文件打开与关闭、文件读写、随机访问、错误检测以及Linux系统特有功能等核心内容。

  1. 文件基础:介绍了文本文件与二进制文件的区别,以及文件缓冲区的概念和工作原理。

  2. 文件操作核心

    • 详细讲解了文件指针FILE结构体
    • 标准文件指针(stdin/stdout/stderr)
    • 文件打开(fopen)和关闭(fclose)的各种模式
    • 字符(fgetc/fputc)、字符串(fgets/fputs)、数据块(fread/fwrite)和格式化(fscanf/fprintf)读写方法
  3. 高级功能

    • 文件随机访问(ftell/fseek/rewind)
    • 错误检测(feof/ferror/clearerr)
    • Linux系统特有功能(文件描述符操作、内存映射等)
  4. 最佳实践

    • 提供了错误处理、资源清理、缓冲区管理等实用建议
    • 针对不同场景的性能优化技巧
    • 常见错误排查方法
  5. 实际应用

    • 演示了配置文件解析、日志系统、数据持久化等常见应用场景的实现方法

全文通过大量代码示例详细说明了各种文件操作的使用方法和注意事项,是C语言文件编程的全面参考指南。

1. 文件操作

1.1 C文件概述

1.1.1 文件的基本概念

在C语言中,文件是存储在外部介质(如硬盘、U盘等)上的一组相关数据的集合。文件操作是C语言中重要的输入输出方式,用于数据的持久化存储。

文件的分类

  1. 按存储形式

    • 文本文件(ASCII文件):以字符形式存储,每个字符对应一个ASCII码

    • 二进制文件:以二进制形式存储,与内存中的数据形式一致

  2. 按存取方式

    • 顺序存取文件:只能按顺序从头到尾访问

    • 随机存取文件:可以任意位置访问

文本文件 vs 二进制文件对比

特性 文本文件 二进制文件
存储形式 ASCII字符 二进制数据
可读性 可直接用文本编辑器查看 需特定程序解读
大小 通常较大 通常较小
处理速度 较慢(需转换) 较快(直接读写)
示例 .txt, .c, .h文件 .exe, .jpg, .dat文件

1.1.2 文件缓冲区

为了提高文件操作效率,C语言使用缓冲区机制:

  • 输入缓冲区:从文件读取数据时,先读入缓冲区,再传给程序

  • 输出缓冲区:程序输出的数据先存入缓冲区,再一次性写入文件

缓冲区工作流程

复制代码
程序 → 输出缓冲区 → 文件
程序 ← 输入缓冲区 ← 文件

1.2 文件指针

1.2.1 FILE结构体

C语言使用FILE结构体表示文件流,它包含了文件操作所需的所有信息。

FILE结构体定义(通常定义在stdio.h中):

cpp 复制代码
typedef struct _iobuf {
    char*   _ptr;        // 缓冲区当前指针
    int     _cnt;        // 剩余字符数
    char*   _base;       // 缓冲区基地址
    int     _flag;       // 文件状态标志
    int     _file;       // 文件描述符
    int     _charbuf;    // 单字符缓冲区
    int     _bufsiz;     // 缓冲区大小
    char*   _tmpfname;   // 临时文件名
} FILE;

1.2.2 标准文件指针

C语言预定义了3个标准文件指针:

  • stdin:标准输入(键盘),文件描述符为0

  • stdout:标准输出(屏幕),文件描述符为1

  • stderr:标准错误(屏幕),文件描述符为2

示例

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

int main() {
    // 使用标准文件指针
    fprintf(stdout, "这是标准输出\n");
    fprintf(stderr, "这是标准错误输出\n");
    
    // stdin示例
    char input[100];
    printf("请输入文本: ");
    fgets(input, sizeof(input), stdin);
    printf("你输入的是: %s", input);
    
    return 0;
}

1.2.3 文件指针的声明

声明文件指针

cpp 复制代码
FILE *fp;  // 声明一个文件指针

文件指针的状态

  • 指向NULL:未指向任何文件

  • 指向有效FILE结构:已打开文件

  • 指向无效地址:文件指针错误


1.3 文件的打开与关闭

1.3.1 文件打开函数(fopen)

函数原型

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

参数说明

  • filename:要打开的文件名(包含路径)

  • mode:打开模式

打开模式

模式 说明 文件必须存在 文件被清空 可读 可写 位置
"r" 只读 开头
"w" 只写 开头
"a" 追加 结尾
"r+" 读写 开头
"w+" 读写 开头
"a+" 读写 结尾

二进制模式(在模式字符串后加'b'):

  • "rb":二进制只读

  • "wb":二进制只写

  • "ab":二进制追加

  • "rb+":二进制读写

  • "wb+":二进制读写

  • "ab+":二进制读写

示例

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

int main() {
    FILE *fp;
    
    // 1. 以只读方式打开文本文件
    fp = fopen("test.txt", "r");
    if (fp == NULL) {
        printf("无法打开文件 test.txt(可能文件不存在)\n");
    } else {
        printf("成功打开文件 test.txt\n");
        fclose(fp);
    }
    
    // 2. 以写入方式打开文件(如果不存在则创建)
    fp = fopen("output.txt", "w");
    if (fp == NULL) {
        printf("无法创建文件 output.txt\n");
        exit(1);
    }
    printf("成功创建文件 output.txt\n");
    fclose(fp);
    
    // 3. 以追加方式打开文件
    fp = fopen("log.txt", "a");
    if (fp == NULL) {
        printf("无法打开日志文件\n");
    } else {
        printf("成功打开日志文件,可以在末尾添加内容\n");
        fclose(fp);
    }
    
    // 4. 以二进制写入方式打开文件
    fp = fopen("data.dat", "wb");
    if (fp == NULL) {
        printf("无法创建二进制文件\n");
    } else {
        printf("成功创建二进制文件 data.dat\n");
        fclose(fp);
    }
    
    // 5. 使用完整路径打开文件
    #ifdef _WIN32
        fp = fopen("C:\\temp\\file.txt", "w");
    #else
        fp = fopen("/tmp/file.txt", "w");
    #endif
    
    if (fp != NULL) {
        printf("成功在临时目录创建文件\n");
        fclose(fp);
    }
    
    return 0;
}

1.3.2 文件关闭函数(fclose)

函数原型

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

返回值

  • 0:成功关闭

  • EOF(-1):关闭失败

重要性

  1. 释放文件资源

  2. 刷新缓冲区,确保数据写入文件

  3. 避免文件描述符泄漏

示例

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

int main() {
    FILE *fp1, *fp2, *fp3;
    int close_result;
    
    // 打开多个文件
    fp1 = fopen("file1.txt", "w");
    fp2 = fopen("file2.txt", "w");
    fp3 = fopen("file3.txt", "w");
    
    if (fp1 == NULL || fp2 == NULL || fp3 == NULL) {
        printf("打开文件失败\n");
        exit(1);
    }
    
    // 向文件写入数据
    fprintf(fp1, "这是文件1的内容\n");
    fprintf(fp2, "这是文件2的内容\n");
    fprintf(fp3, "这是文件3的内容\n");
    
    // 逐个关闭文件并检查结果
    close_result = fclose(fp1);
    if (close_result == 0) {
        printf("成功关闭 file1.txt\n");
    } else {
        printf("关闭 file1.txt 失败\n");
    }
    
    close_result = fclose(fp2);
    if (close_result == 0) {
        printf("成功关闭 file2.txt\n");
    } else {
        printf("关闭 file2.txt 失败\n");
    }
    
    close_result = fclose(fp3);
    if (close_result == 0) {
        printf("成功关闭 file3.txt\n");
    } else {
        printf("关闭 file3.txt 失败\n");
    }
    
    // 错误示例:多次关闭同一文件
    // fclose(fp1);  // 错误!文件已关闭
    
    return 0;
}

1.3.3 文件打开与关闭的最佳实践

示例:安全的文件操作模式

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

// 安全打开文件的函数
FILE* safe_fopen(const char* filename, const char* mode, const char* error_msg) {
    FILE* fp = fopen(filename, mode);
    if (fp == NULL) {
        perror(error_msg);  // 使用perror显示错误信息
        exit(EXIT_FAILURE);
    }
    return fp;
}

// 安全关闭文件的函数
void safe_fclose(FILE* fp, const char* filename) {
    if (fclose(fp) != 0) {
        fprintf(stderr, "警告:关闭文件 %s 时发生错误\n", filename);
    }
}

int main() {
    FILE *input_fp = NULL, *output_fp = NULL;
    
    // 使用安全函数打开文件
    input_fp = safe_fopen("input.txt", "r", "打开输入文件失败");
    output_fp = safe_fopen("output.txt", "w", "打开输出文件失败");
    
    printf("成功打开文件\n");
    
    // 文件操作...
    
    // 使用安全函数关闭文件
    safe_fclose(input_fp, "input.txt");
    safe_fclose(output_fp, "output.txt");
    
    // 确保文件指针置NULL,避免悬空指针
    input_fp = NULL;
    output_fp = NULL;
    
    return 0;
}

错误处理示例

cpp 复制代码
#include <stdio.h>
#include <errno.h>  // 包含errno变量
#include <string.h> // 包含strerror函数

int main() {
    FILE *fp;
    
    // 尝试打开不存在的文件
    fp = fopen("nonexistent.txt", "r");
    if (fp == NULL) {
        // 打印错误信息
        printf("打开文件失败!\n");
        printf("错误代码: %d\n", errno);
        printf("错误描述: %s\n", strerror(errno));
        
        // 根据错误类型进行不同处理
        switch (errno) {
            case ENOENT:  // 文件不存在
                printf("文件不存在,是否要创建?\n");
                // 尝试创建文件
                fp = fopen("nonexistent.txt", "w");
                if (fp != NULL) {
                    printf("已创建新文件\n");
                    fclose(fp);
                }
                break;
                
            case EACCES:  // 权限不足
                printf("没有访问权限\n");
                break;
                
            case EMFILE:  // 打开文件过多
                printf("打开文件过多,请关闭一些文件\n");
                break;
                
            default:
                printf("未知错误\n");
                break;
        }
    }
    
    // 测试权限不足的情况(在Linux下)
    #ifdef __linux__
        fp = fopen("/etc/shadow", "r");
        if (fp == NULL) {
            printf("尝试打开/etc/shadow失败: %s\n", strerror(errno));
        }
    #endif
    
    return 0;
}

1.4 文件的读写

1.4.1 字符读写函数(fgetc和fputc)

fgetc函数

cpp 复制代码
int fgetc(FILE *stream);
  • 从文件中读取一个字符

  • 返回读取的字符(转换为int)

  • 到达文件末尾或出错时返回EOF

fputc函数

c

复制代码
int fputc(int c, FILE *stream);
  • 将一个字符写入文件

  • 返回写入的字符

  • 出错时返回EOF

示例:文件复制(字符方式)

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

// 使用fgetc和fputc复制文件
void copy_file_char_by_char(const char* source, const char* destination) {
    FILE *src_fp = NULL, *dest_fp = NULL;
    int ch;
    
    // 打开源文件(只读)
    src_fp = fopen(source, "r");
    if (src_fp == NULL) {
        printf("无法打开源文件: %s\n", source);
        return;
    }
    
    // 打开目标文件(只写)
    dest_fp = fopen(destination, "w");
    if (dest_fp == NULL) {
        printf("无法创建目标文件: %s\n", destination);
        fclose(src_fp);
        return;
    }
    
    printf("正在复制 %s 到 %s...\n", source, destination);
    
    // 逐个字符复制
    long count = 0;
    while ((ch = fgetc(src_fp)) != EOF) {
        fputc(ch, dest_fp);
        count++;
    }
    
    printf("复制完成,共复制 %ld 个字符\n", count);
    
    // 关闭文件
    fclose(src_fp);
    fclose(dest_fp);
}

// 统计文件中的字符数、行数和单词数
void analyze_file(const char* filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        printf("无法打开文件: %s\n", filename);
        return;
    }
    
    int ch;
    int char_count = 0;
    int line_count = 0;
    int word_count = 0;
    int in_word = 0;  // 是否在单词中
    
    printf("正在分析文件 %s...\n", filename);
    
    while ((ch = fgetc(fp)) != EOF) {
        char_count++;
        
        if (ch == '\n') {
            line_count++;
        }
        
        // 判断单词边界
        if (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r') {
            if (in_word) {
                word_count++;
                in_word = 0;
            }
        } else {
            in_word = 1;
        }
    }
    
    // 如果文件以单词结尾
    if (in_word) {
        word_count++;
    }
    
    // 如果文件不为空但没有换行符,行数至少为1
    if (char_count > 0 && line_count == 0) {
        line_count = 1;
    }
    
    printf("分析结果:\n");
    printf("  字符数: %d\n", char_count);
    printf("  行数: %d\n", line_count);
    printf("  单词数: %d\n", word_count);
    
    fclose(fp);
}

int main() {
    // 创建测试文件
    FILE *fp = fopen("test_input.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "这是一个测试文件\n");
        fprintf(fp, "用于演示字符读写操作\n");
        fprintf(fp, "Hello World!\n");
        fprintf(fp, "C语言文件操作示例\n");
        fclose(fp);
    }
    
    // 复制文件
    copy_file_char_by_char("test_input.txt", "test_output.txt");
    
    printf("\n");
    
    // 分析文件
    analyze_file("test_input.txt");
    
    // 另一个示例:将大写字母转换为小写字母
    printf("\n=== 大写转小写示例 ===\n");
    
    fp = fopen("upper.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "THIS IS A TEST FILE.\n");
        fprintf(fp, "IT CONTAINS UPPERCASE LETTERS.\n");
        fprintf(fp, "WE WILL CONVERT THEM TO LOWERCASE.\n");
        fclose(fp);
    }
    
    // 读取大写文件,写入小写文件
    FILE *src = fopen("upper.txt", "r");
    FILE *dest = fopen("lower.txt", "w");
    
    if (src != NULL && dest != NULL) {
        int ch;
        while ((ch = fgetc(src)) != EOF) {
            if (ch >= 'A' && ch <= 'Z') {
                ch = ch - 'A' + 'a';  // 转换为小写
            }
            fputc(ch, dest);
        }
        fclose(src);
        fclose(dest);
        printf("已转换大写字母为小写字母\n");
    }
    
    // 显示转换结果
    printf("\n转换前(upper.txt):\n");
    src = fopen("upper.txt", "r");
    if (src != NULL) {
        int ch;
        while ((ch = fgetc(src)) != EOF) {
            putchar(ch);
        }
        fclose(src);
    }
    
    printf("\n\n转换后(lower.txt):\n");
    dest = fopen("lower.txt", "r");
    if (dest != NULL) {
        int ch;
        while ((ch = fgetc(dest)) != EOF) {
            putchar(ch);
        }
        fclose(dest);
    }
    
    return 0;
}

1.4.2 字符串读写函数(fgets和fputs)

fgets函数

cpp 复制代码
char *fgets(char *str, int n, FILE *stream);
  • 从文件中读取最多n-1个字符到str

  • 遇到换行符或EOF时停止

  • 保留换行符(如果读取到)

  • 返回str,出错或到文件末尾返回NULL

fputs函数

cpp 复制代码
int fputs(const char *str, FILE *stream);
  • 将字符串写入文件(不自动添加换行符)

  • 成功返回非负值,出错返回EOF

示例:文本文件处理

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

// 使用fgets和fputs复制文件
void copy_file_line_by_line(const char* source, const char* destination) {
    FILE *src_fp = NULL, *dest_fp = NULL;
    char buffer[256];
    
    src_fp = fopen(source, "r");
    if (src_fp == NULL) {
        printf("无法打开源文件: %s\n", source);
        return;
    }
    
    dest_fp = fopen(destination, "w");
    if (dest_fp == NULL) {
        printf("无法创建目标文件: %s\n", destination);
        fclose(src_fp);
        return;
    }
    
    printf("正在逐行复制文件...\n");
    int line_count = 0;
    
    // 逐行读取和写入
    while (fgets(buffer, sizeof(buffer), src_fp) != NULL) {
        fputs(buffer, dest_fp);
        line_count++;
    }
    
    printf("复制完成,共复制 %d 行\n", line_count);
    
    fclose(src_fp);
    fclose(dest_fp);
}

// 在文件中搜索包含特定字符串的行
void search_in_file(const char* filename, const char* search_str) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        printf("无法打开文件: %s\n", filename);
        return;
    }
    
    char buffer[256];
    int line_num = 0;
    int found_count = 0;
    
    printf("在文件 %s 中搜索 \"%s\":\n", filename, search_str);
    
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        line_num++;
        
        // 移除换行符
        buffer[strcspn(buffer, "\n")] = '\0';
        
        // 搜索字符串
        if (strstr(buffer, search_str) != NULL) {
            printf("  第 %d 行: %s\n", line_num, buffer);
            found_count++;
        }
    }
    
    if (found_count == 0) {
        printf("未找到包含 \"%s\" 的行\n", search_str);
    } else {
        printf("共找到 %d 行\n", found_count);
    }
    
    fclose(fp);
}

// 合并多个文件
void merge_files(const char** filenames, int file_count, const char* output_file) {
    FILE *output_fp = fopen(output_file, "w");
    if (output_fp == NULL) {
        printf("无法创建输出文件: %s\n", output_file);
        return;
    }
    
    printf("正在合并 %d 个文件到 %s...\n", file_count, output_file);
    
    for (int i = 0; i < file_count; i++) {
        FILE *input_fp = fopen(filenames[i], "r");
        if (input_fp == NULL) {
            printf("无法打开文件: %s,跳过\n", filenames[i]);
            continue;
        }
        
        // 添加文件标题
        fprintf(output_fp, "\n=== 文件: %s ===\n", filenames[i]);
        
        char buffer[256];
        int line_count = 0;
        
        // 复制文件内容
        while (fgets(buffer, sizeof(buffer), input_fp) != NULL) {
            fputs(buffer, output_fp);
            line_count++;
        }
        
        fprintf(output_fp, "=== 结束: %s (%d 行) ===\n", filenames[i], line_count);
        
        fclose(input_fp);
        printf("  已添加: %s (%d 行)\n", filenames[i], line_count);
    }
    
    fclose(output_fp);
    printf("合并完成\n");
}

// 创建CSV文件
void create_csv_file() {
    FILE *fp = fopen("data.csv", "w");
    if (fp == NULL) {
        printf("无法创建CSV文件\n");
        return;
    }
    
    // 写入CSV表头
    fputs("姓名,年龄,城市,薪资\n", fp);
    
    // 写入数据行
    fputs("张三,30,北京,8000\n", fp);
    fputs("李四,25,上海,7500\n", fp);
    fputs("王五,35,广州,9000\n", fp);
    fputs("赵六,28,深圳,8500\n", fp);
    
    fclose(fp);
    printf("已创建CSV文件: data.csv\n");
}

// 读取并解析CSV文件
void read_csv_file() {
    FILE *fp = fopen("data.csv", "r");
    if (fp == NULL) {
        printf("无法打开CSV文件\n");
        return;
    }
    
    char buffer[256];
    int line_num = 0;
    
    printf("CSV文件内容:\n");
    printf("=============\n");
    
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        line_num++;
        
        // 移除换行符
        buffer[strcspn(buffer, "\n")] = '\0';
        
        if (line_num == 1) {
            printf("表头: %s\n", buffer);
            continue;
        }
        
        // 解析CSV行
        printf("第%d行数据: ", line_num - 1);
        
        char *token = strtok(buffer, ",");
        int field_num = 0;
        
        while (token != NULL) {
            switch (field_num) {
                case 0: printf("姓名: %s, ", token); break;
                case 1: printf("年龄: %s, ", token); break;
                case 2: printf("城市: %s, ", token); break;
                case 3: printf("薪资: %s", token); break;
            }
            token = strtok(NULL, ",");
            field_num++;
        }
        printf("\n");
    }
    
    fclose(fp);
}

int main() {
    // 创建测试文件
    FILE *fp = fopen("test1.txt", "w");
    if (fp != NULL) {
        fputs("这是第一行\n", fp);
        fputs("这是第二行\n", fp);
        fputs("这是包含测试字符串的行\n", fp);
        fputs("第四行文本\n", fp);
        fputs("这是另一个测试\n", fp);
        fclose(fp);
    }
    
    // 逐行复制文件
    copy_file_line_by_line("test1.txt", "test2.txt");
    printf("\n");
    
    // 搜索文件内容
    search_in_file("test1.txt", "测试");
    printf("\n");
    
    // 创建第二个测试文件
    fp = fopen("doc1.txt", "w");
    if (fp != NULL) {
        fputs("文档1的第一行\n", fp);
        fputs("文档1的第二行\n", fp);
        fclose(fp);
    }
    
    fp = fopen("doc2.txt", "w");
    if (fp != NULL) {
        fputs("文档2的内容\n", fp);
        fputs("这是另一个文档\n", fp);
        fclose(fp);
    }
    
    // 合并文件
    const char *files[] = {"test1.txt", "doc1.txt", "doc2.txt"};
    merge_files(files, 3, "merged.txt");
    printf("\n");
    
    // CSV文件示例
    create_csv_file();
    printf("\n");
    read_csv_file();
    
    // fgets的缓冲区处理示例
    printf("\n=== fgets缓冲区示例 ===\n");
    
    fp = fopen("buffer_test.txt", "w");
    if (fp != NULL) {
        // 写入一行很长的文本
        for (int i = 0; i < 10; i++) {
            fprintf(fp, "这是第%d段文本,", i+1);
        }
        fprintf(fp, "\n");
        fprintf(fp, "短行\n");
        fclose(fp);
    }
    
    // 使用不同大小的缓冲区读取
    fp = fopen("buffer_test.txt", "r");
    if (fp != NULL) {
        printf("使用10字节缓冲区读取:\n");
        char small_buffer[10];
        while (fgets(small_buffer, sizeof(small_buffer), fp) != NULL) {
            printf("  读取到: [%s]\n", small_buffer);
        }
        
        rewind(fp);  // 回到文件开头
        
        printf("\n使用100字节缓冲区读取:\n");
        char large_buffer[100];
        while (fgets(large_buffer, sizeof(large_buffer), fp) != NULL) {
            printf("  读取到: [%s]\n", large_buffer);
        }
        
        fclose(fp);
    }
    
    return 0;
}

1.4.3 数据块读写函数(fread和fwrite)

fread函数

cpp 复制代码
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
  • 从文件读取数据块

  • ptr:存储数据的缓冲区

  • size:每个数据项的大小(字节)

  • count:要读取的数据项数量

  • 返回实际读取的数据项数量

fwrite函数

cpp 复制代码
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
  • 将数据块写入文件

  • 参数与fread相同

  • 返回实际写入的数据项数量

示例:二进制文件操作

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

// 学生结构体
typedef struct {
    int id;
    char name[50];
    int age;
    float score;
} Student;

// 保存学生数组到二进制文件
void save_students_to_file(const char* filename, Student* students, int count) {
    FILE *fp = fopen(filename, "wb");
    if (fp == NULL) {
        printf("无法创建文件: %s\n", filename);
        return;
    }
    
    // 先写入学生数量
    fwrite(&count, sizeof(int), 1, fp);
    
    // 写入学生数据
    size_t written = fwrite(students, sizeof(Student), count, fp);
    
    if (written == count) {
        printf("成功保存 %d 个学生到 %s\n", count, filename);
    } else {
        printf("保存不完全,只写了 %zu 个学生\n", written);
    }
    
    fclose(fp);
}

// 从二进制文件读取学生数组
Student* load_students_from_file(const char* filename, int* out_count) {
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) {
        printf("无法打开文件: %s\n", filename);
        return NULL;
    }
    
    // 读取学生数量
    int count;
    if (fread(&count, sizeof(int), 1, fp) != 1) {
        printf("读取学生数量失败\n");
        fclose(fp);
        return NULL;
    }
    
    // 分配内存
    Student* students = (Student*)malloc(count * sizeof(Student));
    if (students == NULL) {
        printf("内存分配失败\n");
        fclose(fp);
        return NULL;
    }
    
    // 读取学生数据
    size_t read = fread(students, sizeof(Student), count, fp);
    
    if (read == count) {
        printf("成功从 %s 读取 %d 个学生\n", filename, count);
    } else {
        printf("读取不完全,只读了 %zu 个学生\n", read);
        // 调整实际读取的数量
        count = read;
    }
    
    fclose(fp);
    *out_count = count;
    return students;
}

// 显示学生信息
void print_students(Student* students, int count) {
    printf("学生列表:\n");
    printf("ID\t姓名\t\t年龄\t成绩\n");
    printf("--------------------------------------\n");
    
    for (int i = 0; i < count; i++) {
        printf("%d\t%s\t%d\t%.2f\n",
               students[i].id,
               students[i].name,
               students[i].age,
               students[i].score);
    }
}

// 保存和加载整数数组
void save_int_array(const char* filename, int* array, int size) {
    FILE *fp = fopen(filename, "wb");
    if (fp == NULL) return;
    
    fwrite(&size, sizeof(int), 1, fp);
    fwrite(array, sizeof(int), size, fp);
    
    fclose(fp);
    printf("已保存整数数组到 %s\n", filename);
}

int* load_int_array(const char* filename, int* out_size) {
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) return NULL;
    
    int size;
    fread(&size, sizeof(int), 1, fp);
    
    int* array = (int*)malloc(size * sizeof(int));
    fread(array, sizeof(int), size, fp);
    
    fclose(fp);
    *out_size = size;
    return array;
}

// 直接复制二进制文件(使用fread/fwrite)
void copy_binary_file(const char* source, const char* destination) {
    FILE *src_fp = fopen(source, "rb");
    FILE *dest_fp = fopen(destination, "wb");
    
    if (src_fp == NULL || dest_fp == NULL) {
        printf("无法打开文件\n");
        if (src_fp) fclose(src_fp);
        if (dest_fp) fclose(dest_fp);
        return;
    }
    
    // 使用缓冲区
    char buffer[4096];
    size_t bytes_read;
    long total_bytes = 0;
    
    while ((bytes_read = fread(buffer, 1, sizeof(buffer), src_fp)) > 0) {
        size_t bytes_written = fwrite(buffer, 1, bytes_read, dest_fp);
        if (bytes_written != bytes_read) {
            printf("写入错误\n");
            break;
        }
        total_bytes += bytes_written;
    }
    
    printf("复制完成,共复制 %ld 字节\n", total_bytes);
    
    fclose(src_fp);
    fclose(dest_fp);
}

int main() {
    // 示例1:学生数据操作
    printf("=== 学生数据二进制文件操作 ===\n");
    
    // 创建测试学生数据
    Student students[] = {
        {1001, "张三", 20, 85.5},
        {1002, "李四", 21, 92.0},
        {1003, "王五", 22, 78.5},
        {1004, "赵六", 20, 88.0},
        {1005, "孙七", 21, 95.5}
    };
    
    int student_count = sizeof(students) / sizeof(students[0]);
    
    // 显示原始数据
    printf("原始学生数据:\n");
    print_students(students, student_count);
    
    // 保存到文件
    save_students_to_file("students.dat", students, student_count);
    
    // 从文件加载
    int loaded_count;
    Student* loaded_students = load_students_from_file("students.dat", &loaded_count);
    
    if (loaded_students != NULL) {
        printf("\n从文件加载的学生数据:\n");
        print_students(loaded_students, loaded_count);
        free(loaded_students);
    }
    
    // 示例2:整数数组操作
    printf("\n=== 整数数组二进制文件操作 ===\n");
    
    int numbers[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
    int num_count = sizeof(numbers) / sizeof(numbers[0]);
    
    save_int_array("numbers.dat", numbers, num_count);
    
    int loaded_size;
    int* loaded_numbers = load_int_array("numbers.dat", &loaded_size);
    
    if (loaded_numbers != NULL) {
        printf("加载的整数数组: ");
        for (int i = 0; i < loaded_size; i++) {
            printf("%d ", loaded_numbers[i]);
        }
        printf("\n");
        free(loaded_numbers);
    }
    
    // 示例3:文件复制性能比较
    printf("\n=== 文件复制性能比较 ===\n");
    
    // 创建一个大文件用于测试
    printf("创建测试文件...\n");
    FILE *big_file = fopen("bigfile.bin", "wb");
    if (big_file != NULL) {
        // 写入1MB数据
        char data[1024];  // 1KB缓冲区
        for (int i = 0; i < 1024; i++) {
            fwrite(data, 1, sizeof(data), big_file);
        }
        fclose(big_file);
        printf("已创建 1MB 测试文件\n");
    }
    
    // 使用不同缓冲区大小复制文件
    printf("\n使用不同缓冲区大小复制文件:\n");
    
    // 使用小缓冲区(1字节)
    printf("1. 使用1字节缓冲区:\n");
    copy_binary_file("bigfile.bin", "copy1.bin");
    
    // 使用中等缓冲区(1024字节)
    printf("\n2. 使用1024字节缓冲区:\n");
    copy_binary_file("bigfile.bin", "copy2.bin");
    
    // 使用大缓冲区(8192字节)
    printf("\n3. 使用8192字节缓冲区:\n");
    copy_binary_file("bigfile.bin", "copy3.bin");
    
    // 示例4:结构体数组的追加操作
    printf("\n=== 结构体数组追加操作 ===\n");
    
    // 先保存一些数据
    Student initial_students[] = {
        {2001, "学生A", 19, 80.0},
        {2002, "学生B", 20, 85.0}
    };
    
    save_students_to_file("append_test.dat", initial_students, 2);
    
    // 追加更多数据
    FILE *append_fp = fopen("append_test.dat", "ab");
    if (append_fp != NULL) {
        Student new_students[] = {
            {2003, "学生C", 21, 90.0},
            {2004, "学生D", 22, 88.0}
        };
        
        fwrite(new_students, sizeof(Student), 2, append_fp);
        fclose(append_fp);
        printf("已追加2个学生到文件\n");
    }
    
    // 读取整个文件(包括追加的数据)
    int total_count;
    Student* all_students = load_students_from_file("append_test.dat", &total_count);
    
    if (all_students != NULL) {
        printf("文件中的所有学生(共%d个):\n", total_count);
        print_students(all_students, total_count);
        free(all_students);
    }
    
    return 0;
}

1.4.4 格式化读写函数(fscanf和fprintf)

fprintf函数

cpp 复制代码
int fprintf(FILE *stream, const char *format, ...);
  • 格式化输出到文件

  • 用法与printf类似,但输出到文件

fscanf函数

cpp 复制代码
int fscanf(FILE *stream, const char *format, ...);
  • 从文件格式化输入

  • 用法与scanf类似,但从文件读取

示例:格式化文件操作

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

// 产品结构体
typedef struct {
    int id;
    char name[100];
    float price;
    int quantity;
} Product;

// 使用fprintf保存产品信息
void save_products_text(Product* products, int count, const char* filename) {
    FILE *fp = fopen(filename, "w");
    if (fp == NULL) {
        printf("无法创建文件: %s\n", filename);
        return;
    }
    
    // 写入标题
    fprintf(fp, "=== 产品库存 ===\n");
    fprintf(fp, "总数: %d\n\n", count);
    
    // 写入表头
    fprintf(fp, "%-6s %-30s %-10s %-8s\n", 
            "ID", "名称", "价格", "数量");
    fprintf(fp, "----------------------------------------------------------\n");
    
    // 写入产品数据
    for (int i = 0; i < count; i++) {
        fprintf(fp, "%-6d %-30s %-10.2f %-8d\n",
                products[i].id,
                products[i].name,
                products[i].price,
                products[i].quantity);
    }
    
    fclose(fp);
    printf("已保存产品信息到文本文件: %s\n", filename);
}

// 使用fscanf读取产品信息
int load_products_text(Product* products, int max_count, const char* filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        printf("无法打开文件: %s\n", filename);
        return 0;
    }
    
    char buffer[256];
    int count = 0;
    
    // 跳过标题行
    fgets(buffer, sizeof(buffer), fp);  // "=== 产品库存 ==="
    fgets(buffer, sizeof(buffer), fp);  // "总数: X"
    fgets(buffer, sizeof(buffer), fp);  // 空行
    fgets(buffer, sizeof(buffer), fp);  // 表头
    fgets(buffer, sizeof(buffer), fp);  // 分隔线
    
    // 读取产品数据
    while (count < max_count && 
           fscanf(fp, "%d %s %f %d",
                  &products[count].id,
                  products[count].name,
                  &products[count].price,
                  &products[count].quantity) == 4) {
        count++;
        
        // 跳过行尾的换行符
        fgetc(fp);
    }
    
    fclose(fp);
    printf("从文本文件读取了 %d 个产品\n", count);
    return count;
}

// 创建配置文件
void create_config_file() {
    FILE *fp = fopen("config.ini", "w");
    if (fp == NULL) return;
    
    fprintf(fp, "# 应用程序配置文件\n");
    fprintf(fp, "# 创建时间: 2023\n\n");
    
    fprintf(fp, "[Database]\n");
    fprintf(fp, "host = localhost\n");
    fprintf(fp, "port = 3306\n");
    fprintf(fp, "username = admin\n");
    fprintf(fp, "password = 123456\n\n");
    
    fprintf(fp, "[Settings]\n");
    fprintf(fp, "language = zh_CN\n");
    fprintf(fp, "theme = dark\n");
    fprintf(fp, "font_size = 14\n");
    
    fclose(fp);
    printf("已创建配置文件: config.ini\n");
}

// 读取配置文件
void read_config_file() {
    FILE *fp = fopen("config.ini", "r");
    if (fp == NULL) return;
    
    char section[50] = "";
    char key[50], value[100];
    char buffer[256];
    
    printf("配置文件内容:\n");
    printf("==============\n");
    
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        // 移除换行符
        buffer[strcspn(buffer, "\n")] = '\0';
        
        // 跳过空行和注释
        if (buffer[0] == '\0' || buffer[0] == '#') {
            continue;
        }
        
        // 检查是否是节标记 [Section]
        if (buffer[0] == '[' && buffer[strlen(buffer)-1] == ']') {
            strncpy(section, buffer + 1, strlen(buffer) - 2);
            section[strlen(buffer) - 2] = '\0';
            printf("\n[%s]\n", section);
            continue;
        }
        
        // 解析键值对
        if (sscanf(buffer, "%49[^=] = %99[^\n]", key, value) == 2) {
            // 去除键和值两端的空格
            char *key_trim = key;
            while (*key_trim == ' ') key_trim++;
            char *key_end = key_trim + strlen(key_trim) - 1;
            while (key_end > key_trim && *key_end == ' ') key_end--;
            *(key_end + 1) = '\0';
            
            char *value_trim = value;
            while (*value_trim == ' ') value_trim++;
            char *value_end = value_trim + strlen(value_trim) - 1;
            while (value_end > value_trim && *value_end == ' ') value_end--;
            *(value_end + 1) = '\0';
            
            printf("  %s = %s\n", key_trim, value_trim);
        }
    }
    
    fclose(fp);
}

// 使用fscanf进行复杂格式解析
void parse_log_file() {
    // 创建日志文件
    FILE *fp = fopen("app.log", "w");
    if (fp != NULL) {
        fprintf(fp, "2023-10-01 10:30:25 INFO: 应用程序启动\n");
        fprintf(fp, "2023-10-01 10:35:42 ERROR: 数据库连接失败\n");
        fprintf(fp, "2023-10-01 10:40:15 WARNING: 内存使用率超过80%%\n");
        fprintf(fp, "2023-10-01 11:00:00 INFO: 用户登录成功\n");
        fclose(fp);
    }
    
    // 解析日志文件
    fp = fopen("app.log", "r");
    if (fp == NULL) return;
    
    char date[20], time[20], level[20], message[100];
    
    printf("\n日志文件解析:\n");
    printf("==============\n");
    
    while (fscanf(fp, "%s %s %s %99[^\n]", 
                  date, time, level, message) == 4) {
        // 移除level末尾的冒号
        char *colon = strchr(level, ':');
        if (colon) *colon = '\0';
        
        printf("时间: %s %s\n", date, time);
        printf("级别: %s\n", level);
        printf("消息: %s\n", message);
        printf("---\n");
    }
    
    fclose(fp);
}

// 学生成绩统计
void student_grades_example() {
    // 创建成绩文件
    FILE *fp = fopen("grades.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "张三 数学 85 物理 90 化学 88\n");
        fprintf(fp, "李四 数学 92 物理 87 化学 95\n");
        fprintf(fp, "王五 数学 78 物理 85 化学 82\n");
        fprintf(fp, "赵六 数学 95 物理 92 化学 96\n");
        fclose(fp);
    }
    
    // 读取并统计成绩
    fp = fopen("grades.txt", "r");
    if (fp == NULL) return;
    
    char name[50], subject1[20], subject2[20], subject3[20];
    int grade1, grade2, grade3;
    float total_math = 0, total_physics = 0, total_chemistry = 0;
    int count = 0;
    
    printf("\n学生成绩统计:\n");
    printf("==============\n");
    
    while (fscanf(fp, "%s %s %d %s %d %s %d",
                  name, subject1, &grade1, 
                  subject2, &grade2,
                  subject3, &grade3) == 7) {
        
        printf("学生: %s\n", name);
        printf("  %s: %d, %s: %d, %s: %d\n", 
               subject1, grade1, subject2, grade2, subject3, grade3);
        printf("  平均分: %.2f\n", (grade1 + grade2 + grade3) / 3.0);
        
        total_math += grade1;
        total_physics += grade2;
        total_chemistry += grade3;
        count++;
    }
    
    if (count > 0) {
        printf("\n科目平均分:\n");
        printf("  数学: %.2f\n", total_math / count);
        printf("  物理: %.2f\n", total_physics / count);
        printf("  化学: %.2f\n", total_chemistry / count);
    }
    
    fclose(fp);
}

int main() {
    // 示例1:产品库存管理
    printf("=== 产品库存管理 ===\n");
    
    Product products[] = {
        {101, "笔记本电脑", 5999.99, 10},
        {102, "智能手机", 2999.50, 25},
        {103, "平板电脑", 1999.00, 15},
        {104, "智能手表", 899.99, 30},
        {105, "蓝牙耳机", 299.00, 50}
    };
    
    int product_count = sizeof(products) / sizeof(products[0]);
    
    // 保存到文本文件
    save_products_text(products, product_count, "products.txt");
    
    // 从文本文件读取
    Product loaded_products[10];
    int loaded_count = load_products_text(loaded_products, 10, "products.txt");
    
    // 显示读取的产品
    if (loaded_count > 0) {
        printf("\n从文件读取的产品:\n");
        for (int i = 0; i < loaded_count; i++) {
            printf("ID: %d, 名称: %s, 价格: %.2f, 数量: %d\n",
                   loaded_products[i].id,
                   loaded_products[i].name,
                   loaded_products[i].price,
                   loaded_products[i].quantity);
        }
    }
    
    // 示例2:配置文件操作
    printf("\n=== 配置文件操作 ===\n");
    create_config_file();
    read_config_file();
    
    // 示例3:日志文件解析
    parse_log_file();
    
    // 示例4:学生成绩统计
    student_grades_example();
    
    // 示例5:fscanf的返回值处理
    printf("\n=== fscanf返回值处理 ===\n");
    
    FILE *test_fp = fopen("test_data.txt", "w");
    if (test_fp != NULL) {
        fprintf(test_fp, "100 200 300\n");
        fprintf(test_fp, "400 abc 500\n");  // 包含非数字
        fprintf(test_fp, "600 700 800\n");
        fclose(test_fp);
    }
    
    test_fp = fopen("test_data.txt", "r");
    if (test_fp != NULL) {
        int a, b, c;
        int line_num = 0;
        
        while (!feof(test_fp)) {
            int result = fscanf(test_fp, "%d %d %d", &a, &b, &c);
            line_num++;
            
            if (result == 3) {
                printf("第%d行: 成功读取3个整数: %d, %d, %d\n", 
                       line_num, a, b, c);
            } else if (result == EOF) {
                printf("第%d行: 文件结束\n", line_num);
            } else {
                printf("第%d行: 只读取了%d个整数\n", line_num, result);
                
                // 跳过错误行
                char buffer[100];
                fgets(buffer, sizeof(buffer), test_fp);
            }
        }
        
        fclose(test_fp);
    }
    
    return 0;
}

1.5 文件的随机读写

1.5.1 文件定位函数

ftell函数

cpp 复制代码
long ftell(FILE *stream);
  • 返回当前文件位置指示器的位置(字节偏移)

  • 出错时返回-1L

fseek函数

cpp 复制代码
int fseek(FILE *stream, long offset, int origin);
  • 设置文件位置指示器

  • offset:偏移量

  • origin:起始位置

    • SEEK_SET:文件开头

    • SEEK_CUR:当前位置

    • SEEK_END:文件末尾

rewind函数

cpp 复制代码
void rewind(FILE *stream);
  • 将文件位置指示器重置到文件开头

  • 相当于fseek(fp, 0, SEEK_SET)

1.5.2 文件的随机读写

示例:随机访问文件

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

// 学生记录结构(固定长度)
typedef struct {
    int id;
    char name[30];
    int age;
    float score;
} StudentRecord;

// 创建测试文件
void create_student_file(const char* filename, int record_count) {
    FILE *fp = fopen(filename, "wb");
    if (fp == NULL) {
        printf("无法创建文件: %s\n", filename);
        return;
    }
    
    // 写入固定数量的空记录
    StudentRecord empty_record = {0, "", 0, 0.0};
    
    for (int i = 0; i < record_count; i++) {
        fwrite(&empty_record, sizeof(StudentRecord), 1, fp);
    }
    
    fclose(fp);
    printf("已创建包含 %d 个记录的文件: %s\n", record_count, filename);
}

// 在指定位置写入学生记录
int write_student_record(const char* filename, int record_num, StudentRecord *record) {
    FILE *fp = fopen(filename, "rb+");  // 读写模式
    if (fp == NULL) {
        printf("无法打开文件: %s\n", filename);
        return 0;
    }
    
    // 计算偏移量
    long offset = record_num * sizeof(StudentRecord);
    
    // 定位到指定记录
    if (fseek(fp, offset, SEEK_SET) != 0) {
        printf("定位失败\n");
        fclose(fp);
        return 0;
    }
    
    // 写入记录
    size_t written = fwrite(record, sizeof(StudentRecord), 1, fp);
    
    fclose(fp);
    return (written == 1);
}

// 从指定位置读取学生记录
int read_student_record(const char* filename, int record_num, StudentRecord *record) {
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) {
        printf("无法打开文件: %s\n", filename);
        return 0;
    }
    
    // 计算偏移量
    long offset = record_num * sizeof(StudentRecord);
    
    // 定位到指定记录
    if (fseek(fp, offset, SEEK_SET) != 0) {
        printf("定位失败\n");
        fclose(fp);
        return 0;
    }
    
    // 读取记录
    size_t read = fread(record, sizeof(StudentRecord), 1, fp);
    
    fclose(fp);
    return (read == 1);
}

// 显示学生记录
void print_student_record(StudentRecord *record) {
    if (record->id == 0) {
        printf("[空记录]\n");
    } else {
        printf("ID: %d, 姓名: %s, 年龄: %d, 成绩: %.2f\n",
               record->id, record->name, record->age, record->score);
    }
}

// 在文件末尾追加记录
int append_student_record(const char* filename, StudentRecord *record) {
    FILE *fp = fopen(filename, "ab");
    if (fp == NULL) {
        printf("无法打开文件: %s\n", filename);
        return 0;
    }
    
    size_t written = fwrite(record, sizeof(StudentRecord), 1, fp);
    
    fclose(fp);
    return (written == 1);
}

// 获取文件中的记录数量
int get_record_count(const char* filename) {
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) {
        return 0;
    }
    
    // 定位到文件末尾
    fseek(fp, 0, SEEK_END);
    long file_size = ftell(fp);
    
    fclose(fp);
    
    return file_size / sizeof(StudentRecord);
}

// 显示文件中的所有记录
void display_all_records(const char* filename) {
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) {
        printf("无法打开文件: %s\n", filename);
        return;
    }
    
    StudentRecord record;
    int record_num = 0;
    
    printf("文件中的所有记录:\n");
    printf("=================\n");
    
    while (fread(&record, sizeof(StudentRecord), 1, fp) == 1) {
        printf("记录 %d: ", record_num);
        print_student_record(&record);
        record_num++;
    }
    
    fclose(fp);
}

// 删除记录(标记为删除)
int delete_student_record(const char* filename, int record_num) {
    FILE *fp = fopen(filename, "rb+");
    if (fp == NULL) {
        printf("无法打开文件: %s\n", filename);
        return 0;
    }
    
    // 计算偏移量
    long offset = record_num * sizeof(StudentRecord);
    
    // 定位到指定记录
    if (fseek(fp, offset, SEEK_SET) != 0) {
        printf("定位失败\n");
        fclose(fp);
        return 0;
    }
    
    // 创建空记录(标记为删除)
    StudentRecord empty_record = {0, "", 0, 0.0};
    
    // 写入空记录
    size_t written = fwrite(&empty_record, sizeof(StudentRecord), 1, fp);
    
    fclose(fp);
    return (written == 1);
}

// 更新记录
int update_student_record(const char* filename, int record_num, StudentRecord *record) {
    return write_student_record(filename, record_num, record);
}

// 搜索记录(按ID)
int search_record_by_id(const char* filename, int id, StudentRecord *result) {
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL) {
        printf("无法打开文件: %s\n", filename);
        return -1;
    }
    
    StudentRecord record;
    int found_index = -1;
    int current_index = 0;
    
    while (fread(&record, sizeof(StudentRecord), 1, fp) == 1) {
        if (record.id == id) {
            *result = record;
            found_index = current_index;
            break;
        }
        current_index++;
    }
    
    fclose(fp);
    return found_index;
}

int main() {
    const char* filename = "students.dat";
    
    // 创建包含10个记录的文件
    create_student_file(filename, 10);
    
    // 在随机位置写入记录
    printf("\n=== 写入随机记录 ===\n");
    
    StudentRecord records[] = {
        {1001, "张三", 20, 85.5},
        {1002, "李四", 21, 92.0},
        {1003, "王五", 22, 78.5},
        {1004, "赵六", 20, 88.0},
        {1005, "孙七", 21, 95.5}
    };
    
    // 在随机位置写入
    int positions[] = {2, 5, 7, 1, 8};
    
    for (int i = 0; i < 5; i++) {
        if (write_student_record(filename, positions[i], &records[i])) {
            printf("在位置 %d 写入记录: ", positions[i]);
            print_student_record(&records[i]);
        }
    }
    
    // 显示所有记录
    printf("\n=== 显示所有记录 ===\n");
    display_all_records(filename);
    
    // 随机读取记录
    printf("\n=== 随机读取记录 ===\n");
    
    StudentRecord read_record;
    int read_positions[] = {1, 2, 5, 7, 8};
    
    for (int i = 0; i < 5; i++) {
        if (read_student_record(filename, read_positions[i], &read_record)) {
            printf("从位置 %d 读取: ", read_positions[i]);
            print_student_record(&read_record);
        }
    }
    
    // 使用ftell和fseek
    printf("\n=== 使用ftell和fseek ===\n");
    
    FILE *fp = fopen(filename, "rb");
    if (fp != NULL) {
        // 获取文件大小
        fseek(fp, 0, SEEK_END);
        long file_size = ftell(fp);
        printf("文件大小: %ld 字节\n", file_size);
        printf("记录数量: %ld\n", file_size / sizeof(StudentRecord));
        
        // 回到文件开头
        rewind(fp);
        
        // 读取并显示每个记录的位置
        StudentRecord rec;
        int index = 0;
        
        while (fread(&rec, sizeof(StudentRecord), 1, fp) == 1) {
            long current_pos = ftell(fp);
            printf("记录 %d 结束位置: %ld\n", index, current_pos);
            index++;
        }
        
        fclose(fp);
    }
    
    // 搜索记录
    printf("\n=== 搜索记录 ===\n");
    
    StudentRecord found_record;
    int search_id = 1003;
    int found_index = search_record_by_id(filename, search_id, &found_record);
    
    if (found_index != -1) {
        printf("找到ID为 %d 的记录在位置 %d: ", search_id, found_index);
        print_student_record(&found_record);
    } else {
        printf("未找到ID为 %d 的记录\n", search_id);
    }
    
    // 更新记录
    printf("\n=== 更新记录 ===\n");
    
    StudentRecord updated_record = {1002, "李四(已更新)", 22, 96.0};
    if (update_student_record(filename, 5, &updated_record)) {
        printf("更新位置 5 的记录为: ");
        print_student_record(&updated_record);
    }
    
    // 删除记录
    printf("\n=== 删除记录 ===\n");
    if (delete_student_record(filename, 1)) {
        printf("已删除位置 1 的记录\n");
    }
    
    // 显示更新后的所有记录
    printf("\n=== 更新后的所有记录 ===\n");
    display_all_records(filename);
    
    // 追加记录
    printf("\n=== 追加记录 ===\n");
    
    StudentRecord new_record = {1006, "周八", 19, 89.5};
    if (append_student_record(filename, &new_record)) {
        printf("已追加新记录: ");
        print_student_record(&new_record);
    }
    
    // 显示最终记录数量
    int final_count = get_record_count(filename);
    printf("\n最终记录数量: %d\n", final_count);
    
    // 复杂示例:二进制文件的索引访问
    printf("\n=== 二进制文件索引访问示例 ===\n");
    
    // 创建索引文件和数据文件
    FILE *data_fp = fopen("data.bin", "wb");
    FILE *index_fp = fopen("index.bin", "wb");
    
    if (data_fp != NULL && index_fp != NULL) {
        // 写入数据
        char *data[] = {"第一条数据", "第二条数据", "第三条数据", "第四条数据"};
        long offsets[4];
        
        for (int i = 0; i < 4; i++) {
            // 记录数据偏移
            offsets[i] = ftell(data_fp);
            
            // 写入数据长度和数据
            int len = strlen(data[i]) + 1;  // 包括字符串结束符
            fwrite(&len, sizeof(int), 1, data_fp);
            fwrite(data[i], 1, len, data_fp);
        }
        
        // 写入索引
        fwrite(offsets, sizeof(long), 4, index_fp);
        
        fclose(data_fp);
        fclose(index_fp);
        
        // 随机读取数据
        printf("创建了带索引的数据文件\n");
        printf("现在随机读取数据:\n");
        
        data_fp = fopen("data.bin", "rb");
        index_fp = fopen("index.bin", "rb");
        
        if (data_fp != NULL && index_fp != NULL) {
            // 读取索引
            long read_offsets[4];
            fread(read_offsets, sizeof(long), 4, index_fp);
            
            // 随机访问第三条数据
            fseek(data_fp, read_offsets[2], SEEK_SET);
            
            int len;
            fread(&len, sizeof(int), 1, data_fp);
            
            char buffer[100];
            fread(buffer, 1, len, data_fp);
            
            printf("第三条数据: %s\n", buffer);
            
            fclose(data_fp);
            fclose(index_fp);
        }
    }
    
    return 0;
}

1.5.3 Linux下的文件定位

Linux特有的文件定位函数

cpp 复制代码
#include <stdio.h>
#include <unistd.h>  // 包含lseek函数

// 使用系统调用进行文件定位
off_t lseek(int fd, off_t offset, int whence);

示例:Linux下的文件操作

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

// 使用系统调用打开文件
int linux_open_file(const char* filename, int flags, mode_t mode) {
    int fd = open(filename, flags, mode);
    if (fd == -1) {
        perror("打开文件失败");
        return -1;
    }
    return fd;
}

// 使用系统调用读取文件
ssize_t linux_read_file(int fd, void* buffer, size_t count) {
    ssize_t bytes_read = read(fd, buffer, count);
    if (bytes_read == -1) {
        perror("读取文件失败");
    }
    return bytes_read;
}

// 使用系统调用写入文件
ssize_t linux_write_file(int fd, const void* buffer, size_t count) {
    ssize_t bytes_written = write(fd, buffer, count);
    if (bytes_written == -1) {
        perror("写入文件失败");
    }
    return bytes_written;
}

// 使用lseek进行文件定位
off_t linux_seek_file(int fd, off_t offset, int whence) {
    off_t result = lseek(fd, offset, whence);
    if (result == (off_t)-1) {
        perror("文件定位失败");
    }
    return result;
}

// 获取文件信息
void get_file_info(const char* filename) {
    struct stat file_stat;
    
    if (stat(filename, &file_stat) == -1) {
        perror("获取文件信息失败");
        return;
    }
    
    printf("文件: %s\n", filename);
    printf("  大小: %ld 字节\n", file_stat.st_size);
    printf("  inode: %ld\n", file_stat.st_ino);
    printf("  硬链接数: %ld\n", file_stat.st_nlink);
    printf("  权限: %o\n", file_stat.st_mode & 0777);
    printf("  用户ID: %d\n", file_stat.st_uid);
    printf("  组ID: %d\n", file_stat.st_gid);
    printf("  设备ID: %ld\n", file_stat.st_dev);
    printf("  块大小: %ld\n", file_stat.st_blksize);
    printf("  块数量: %ld\n", file_stat.st_blocks);
    
    // 文件类型
    printf("  类型: ");
    if (S_ISREG(file_stat.st_mode)) printf("普通文件\n");
    else if (S_ISDIR(file_stat.st_mode)) printf("目录\n");
    else if (S_ISCHR(file_stat.st_mode)) printf("字符设备\n");
    else if (S_ISBLK(file_stat.st_mode)) printf("块设备\n");
    else if (S_ISFIFO(file_stat.st_mode)) printf("FIFO/管道\n");
    else if (S_ISLNK(file_stat.st_mode)) printf("符号链接\n");
    else if (S_ISSOCK(file_stat.st_mode)) printf("套接字\n");
    else printf("未知\n");
    
    // 访问时间
    printf("  最后访问: %s", ctime(&file_stat.st_atime));
    printf("  最后修改: %s", ctime(&file_stat.st_mtime));
    printf("  最后状态改变: %s", ctime(&file_stat.st_ctime));
}

// 使用文件描述符进行随机访问
void random_access_with_fd(const char* filename) {
    int fd = linux_open_file(filename, O_RDWR | O_CREAT, 0644);
    if (fd == -1) return;
    
    // 写入一些数据
    char data[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    linux_write_file(fd, data, strlen(data));
    
    // 使用lseek定位并读取
    printf("使用文件描述符随机访问:\n");
    
    // 读取第10个字节
    linux_seek_file(fd, 10, SEEK_SET);
    char buffer[10];
    linux_read_file(fd, buffer, 5);
    buffer[5] = '\0';
    printf("  位置10-14: %s\n", buffer);
    
    // 从当前位置向后移动5字节
    linux_seek_file(fd, 5, SEEK_CUR);
    linux_read_file(fd, buffer, 5);
    buffer[5] = '\0';
    printf("  位置20-24: %s\n", buffer);
    
    // 从文件末尾向前移动10字节
    linux_seek_file(fd, -10, SEEK_END);
    linux_read_file(fd, buffer, 5);
    buffer[5] = '\0';
    printf("  位置26-30: %s\n", buffer);
    
    // 获取当前位置
    off_t current_pos = linux_seek_file(fd, 0, SEEK_CUR);
    printf("  当前位置: %ld\n", current_pos);
    
    // 获取文件大小
    off_t file_size = linux_seek_file(fd, 0, SEEK_END);
    printf("  文件大小: %ld 字节\n", file_size);
    
    close(fd);
}

// 创建空洞文件(sparse file)
void create_sparse_file(const char* filename) {
    int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        return;
    }
    
    // 在文件开头写入数据
    write(fd, "开头数据", strlen("开头数据"));
    
    // 跳过1MB的空洞
    lseek(fd, 1024 * 1024, SEEK_CUR);
    
    // 在1MB后写入数据
    write(fd, "中间数据", strlen("中间数据"));
    
    // 再跳过1MB
    lseek(fd, 1024 * 1024, SEEK_CUR);
    
    // 在2MB后写入数据
    write(fd, "结尾数据", strlen("结尾数据"));
    
    close(fd);
    
    // 检查文件大小
    struct stat st;
    stat(filename, &st);
    printf("空洞文件: %s\n", filename);
    printf("  逻辑大小: %ld 字节\n", st.st_size);
    printf("  实际占用: %ld 块(%ld 字节)\n", 
           st.st_blocks, st.st_blocks * 512);
}

// 内存映射文件(mmap)示例
#ifdef __linux__
#include <sys/mman.h>

void memory_map_example(const char* filename) {
    int fd = open(filename, O_RDWR);
    if (fd == -1) {
        perror("打开文件失败");
        return;
    }
    
    // 获取文件大小
    struct stat st;
    fstat(fd, &st);
    size_t file_size = st.st_size;
    
    // 内存映射
    char* mapped = mmap(NULL, file_size, PROT_READ | PROT_WRITE, 
                        MAP_SHARED, fd, 0);
    if (mapped == MAP_FAILED) {
        perror("内存映射失败");
        close(fd);
        return;
    }
    
    printf("文件已映射到内存地址: %p\n", mapped);
    printf("文件大小: %zu 字节\n", file_size);
    
    // 直接通过内存访问文件内容
    printf("文件内容(前100字节):\n");
    for (size_t i = 0; i < file_size && i < 100; i++) {
        putchar(mapped[i]);
    }
    printf("\n");
    
    // 修改文件内容
    if (file_size > 10) {
        printf("修改文件前5个字符为'HELLO'\n");
        strncpy(mapped, "HELLO", 5);
    }
    
    // 取消映射
    munmap(mapped, file_size);
    close(fd);
}
#endif

int main() {
    printf("=== Linux下的文件操作 ===\n\n");
    
    // 1. 使用系统调用进行文件操作
    printf("1. 使用系统调用进行文件操作\n");
    
    const char* test_file = "linux_test.txt";
    
    // 打开文件
    int fd = linux_open_file(test_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd != -1) {
        // 写入数据
        char* text = "这是Linux下的文件操作测试\n";
        linux_write_file(fd, text, strlen(text));
        
        // 关闭文件
        close(fd);
        printf("  已创建文件: %s\n", test_file);
    }
    
    // 重新打开文件读取
    fd = linux_open_file(test_file, O_RDONLY, 0);
    if (fd != -1) {
        char buffer[100];
        ssize_t bytes_read = linux_read_file(fd, buffer, sizeof(buffer)-1);
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("  文件内容: %s", buffer);
        }
        close(fd);
    }
    
    printf("\n");
    
    // 2. 获取文件信息
    printf("2. 获取文件信息\n");
    get_file_info(test_file);
    
    printf("\n");
    
    // 3. 随机访问
    printf("3. 使用文件描述符随机访问\n");
    random_access_with_fd("random_access.bin");
    
    printf("\n");
    
    // 4. 创建空洞文件
    printf("4. 创建空洞文件\n");
    create_sparse_file("sparse_file.bin");
    get_file_info("sparse_file.bin");
    
    printf("\n");
    
    #ifdef __linux__
    // 5. 内存映射文件(仅Linux)
    printf("5. 内存映射文件示例\n");
    
    // 创建测试文件
    FILE* fp = fopen("mmap_test.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "这是一个用于内存映射测试的文件。\n");
        fprintf(fp, "Memory mapping allows direct access to file data.\n");
        fprintf(fp, "这可以提高大文件访问的效率。\n");
        fclose(fp);
    }
    
    memory_map_example("mmap_test.txt");
    #endif
    
    printf("\n");
    
    // 6. 标准输入输出的文件描述符
    printf("6. 标准文件描述符\n");
    printf("  标准输入文件描述符: %d\n", STDIN_FILENO);
    printf("  标准输出文件描述符: %d\n", STDOUT_FILENO);
    printf("  标准错误文件描述符: %d\n", STDERR_FILENO);
    
    // 使用文件描述符进行标准输出
    const char* msg = "这是通过文件描述符写入标准输出的消息\n";
    write(STDOUT_FILENO, msg, strlen(msg));
    
    printf("\n");
    
    // 7. 文件锁定(fcntl)
    printf("7. 文件锁定示例\n");
    
    fd = open("lock_test.txt", O_RDWR | O_CREAT, 0644);
    if (fd != -1) {
        struct flock lock;
        
        // 设置写锁
        lock.l_type = F_WRLCK;    // 写锁
        lock.l_whence = SEEK_SET; // 从文件开头
        lock.l_start = 0;         // 偏移0
        lock.l_len = 0;           // 锁定整个文件
        lock.l_pid = getpid();    // 当前进程ID
        
        // 尝试获取锁
        if (fcntl(fd, F_SETLK, &lock) == -1) {
            printf("  无法获取文件锁\n");
        } else {
            printf("  已获取文件写锁\n");
            
            // 写入数据
            write(fd, "锁定期间写入的数据\n", strlen("锁定期间写入的数据\n"));
            sleep(2);  // 保持锁2秒
            
            // 释放锁
            lock.l_type = F_UNLCK;
            fcntl(fd, F_SETLK, &lock);
            printf("  已释放文件锁\n");
        }
        
        close(fd);
    }
    
    // 8. 目录操作
    printf("\n8. 目录操作示例\n");
    
    // 创建目录
    if (mkdir("test_dir", 0755) == 0) {
        printf("  已创建目录: test_dir\n");
    }
    
    // 改变当前工作目录
    char cwd[1024];
    if (getcwd(cwd, sizeof(cwd)) != NULL) {
        printf("  当前工作目录: %s\n", cwd);
    }
    
    // 切换到新目录
    if (chdir("test_dir") == 0) {
        getcwd(cwd, sizeof(cwd));
        printf("  切换到目录: %s\n", cwd);
        
        // 创建文件
        fd = open("dir_test.txt", O_WRONLY | O_CREAT, 0644);
        if (fd != -1) {
            write(fd, "在test_dir中创建的文件\n", 
                  strlen("在test_dir中创建的文件\n"));
            close(fd);
        }
        
        // 返回原目录
        chdir("..");
    }
    
    // 删除目录
    if (rmdir("test_dir") == 0) {
        printf("  已删除目录: test_dir\n");
    } else {
        printf("  删除目录失败(可能目录非空)\n");
        // 先删除文件
        unlink("test_dir/dir_test.txt");
        rmdir("test_dir");
    }
    
    // 清理临时文件
    unlink(test_file);
    unlink("random_access.bin");
    unlink("sparse_file.bin");
    #ifdef __linux__
    unlink("mmap_test.txt");
    #endif
    unlink("lock_test.txt");
    
    return 0;
}

1.6 文件检测函数

1.6.1 文件结束检测函数(feof)

feof函数

cpp 复制代码
int feof(FILE *stream);
  • 检查文件结束标志

  • 返回非零值表示已到达文件末尾

  • 在尝试读取失败后检查才有意义

示例

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

// 错误的feof用法
void wrong_feof_usage() {
    FILE *fp = fopen("test.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "Line 1\nLine 2\nLine 3\n");
        fclose(fp);
    }
    
    fp = fopen("test.txt", "r");
    if (fp == NULL) return;
    
    printf("错误的feof用法:\n");
    
    char buffer[100];
    while (!feof(fp)) {  // 错误!feof在读操作之前检查
        fgets(buffer, sizeof(buffer), fp);
        printf("读取: %s", buffer);  // 最后一行会重复输出
    }
    
    fclose(fp);
}

// 正确的feof用法
void correct_feof_usage() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) return;
    
    printf("\n正确的feof用法:\n");
    
    char buffer[100];
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("读取: %s", buffer);
    }
    
    // 检查是否因为到达文件末尾而结束
    if (feof(fp)) {
        printf("已到达文件末尾\n");
    } else {
        printf("因错误而停止\n");
    }
    
    fclose(fp);
}

// feof和ferror结合使用
void feof_and_ferror_example() {
    FILE *fp = fopen("test.bin", "wb");
    if (fp != NULL) {
        int data[] = {1, 2, 3, 4, 5};
        fwrite(data, sizeof(int), 5, fp);
        fclose(fp);
    }
    
    fp = fopen("test.bin", "rb");
    if (fp == NULL) return;
    
    printf("\nfeof和ferror示例:\n");
    
    int value;
    int count = 0;
    
    while (1) {
        size_t read = fread(&value, sizeof(int), 1, fp);
        
        if (read == 0) {
            // 读取失败,检查原因
            if (feof(fp)) {
                printf("已读取所有数据(共%d个)\n", count);
                break;
            } else if (ferror(fp)) {
                printf("读取时发生错误\n");
                break;
            }
        } else {
            printf("读取数据[%d]: %d\n", count, value);
            count++;
        }
    }
    
    // 清除错误标志
    clearerr(fp);
    
    fclose(fp);
}

// 逐字符读取直到文件结束
void read_until_eof() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) return;
    
    printf("\n逐字符读取直到文件结束:\n");
    
    int ch;
    int char_count = 0;
    int line_count = 0;
    
    while ((ch = fgetc(fp)) != EOF) {
        char_count++;
        if (ch == '\n') line_count++;
        putchar(ch);
    }
    
    // 检查结束原因
    if (feof(fp)) {
        printf("\n\n文件统计:\n");
        printf("  字符数: %d\n", char_count);
        printf("  行数: %d\n", line_count);
        printf("  正常到达文件末尾\n");
    } else {
        printf("\n读取因错误而中断\n");
    }
    
    fclose(fp);
}

int main() {
    // 创建测试文件
    FILE *fp = fopen("test.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "第一行文本\n");
        fprintf(fp, "第二行文本\n");
        fprintf(fp, "第三行文本\n");
        fclose(fp);
    }
    
    // 演示feof的正确和错误用法
    wrong_feof_usage();
    correct_feof_usage();
    
    // 其他示例
    feof_and_ferror_example();
    read_until_eof();
    
    // 清理
    remove("test.txt");
    remove("test.bin");
    
    return 0;
}

1.6.2 读写文件出错检测函数(ferror)

ferror函数

cpp 复制代码
int ferror(FILE *stream);
  • 检查文件错误标志

  • 返回非零值表示发生了错误

  • 错误标志会持续存在,直到调用clearerr()或rewind()

示例

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

// 模拟文件读取错误
void simulate_read_error() {
    printf("=== 模拟读取错误 ===\n");
    
    // 创建一个文件
    FILE *fp = fopen("error_test.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "正常数据\n");
        fclose(fp);
    }
    
    // 以只读方式打开
    fp = fopen("error_test.txt", "r");
    if (fp == NULL) {
        printf("无法打开文件\n");
        return;
    }
    
    // 正常读取
    char buffer[100];
    if (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("正常读取: %s", buffer);
    }
    
    // 尝试写入(应该失败,因为以只读方式打开)
    printf("\n尝试写入只读文件...\n");
    if (fputs("尝试写入", fp) == EOF) {
        printf("写入失败\n");
        
        // 检查错误
        if (ferror(fp)) {
            printf("文件错误标志已设置\n");
            printf("错误代码: %d\n", errno);
            printf("错误描述: %s\n", strerror(errno));
            
            // 清除错误标志
            clearerr(fp);
            printf("已清除错误标志\n");
            
            // 再次检查
            if (ferror(fp)) {
                printf("错误标志仍然存在\n");
            } else {
                printf("错误标志已清除\n");
            }
        }
    }
    
    fclose(fp);
}

// 检查磁盘空间不足错误
void simulate_disk_full() {
    printf("\n=== 模拟磁盘空间不足 ===\n");
    
    // 尝试写入大量数据(可能触发磁盘满错误)
    FILE *fp = fopen("large_file.bin", "wb");
    if (fp == NULL) {
        printf("无法创建文件\n");
        return;
    }
    
    char buffer[1024];  // 1KB缓冲区
    long total_written = 0;
    
    // 尝试写入大量数据
    for (int i = 0; i < 1000000; i++) {  // 尝试写入1GB
        size_t written = fwrite(buffer, 1, sizeof(buffer), fp);
        total_written += written;
        
        if (written < sizeof(buffer)) {
            // 写入不完整,检查错误
            if (ferror(fp)) {
                printf("写入错误发生在 %ld KB\n", total_written / 1024);
                printf("错误代码: %d\n", errno);
                printf("错误描述: %s\n", strerror(errno));
                break;
            } else if (feof(fp)) {
                printf("意外到达文件末尾\n");
                break;
            }
        }
        
        // 每10MB显示一次进度
        if (i % 10240 == 0 && i > 0) {
            printf("已写入: %ld MB\n", total_written / (1024 * 1024));
        }
    }
    
    fclose(fp);
    printf("总共写入: %ld MB\n", total_written / (1024 * 1024));
    
    // 清理
    remove("large_file.bin");
}

// 处理文件权限错误
void handle_permission_error() {
    printf("\n=== 处理文件权限错误 ===\n");
    
    #ifdef __linux__
    // 尝试打开需要root权限的文件
    FILE *fp = fopen("/etc/shadow", "r");
    if (fp == NULL) {
        printf("无法打开 /etc/shadow\n");
        printf("错误代码: %d\n", errno);
        printf("错误描述: %s\n", strerror(errno));
        
        if (errno == EACCES) {
            printf("权限不足,需要root权限\n");
        }
    } else {
        printf("成功打开文件(你必须有root权限)\n");
        fclose(fp);
    }
    #else
    printf("此示例仅适用于Linux系统\n");
    #endif
}

// 处理无效文件指针错误
void handle_invalid_file_pointer() {
    printf("\n=== 处理无效文件指针 ===\n");
    
    FILE *fp = NULL;
    
    // 对NULL指针进行操作
    printf("尝试对NULL文件指针进行读取...\n");
    char buffer[100];
    if (fgets(buffer, sizeof(buffer), fp) == NULL) {
        printf("读取失败\n");
        
        // 检查错误
        if (ferror(fp)) {
            printf("错误标志已设置\n");
        }
    }
    
    // 已关闭的文件指针
    fp = fopen("temp.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "测试数据\n");
        fclose(fp);
        
        // 尝试使用已关闭的文件指针
        printf("\n尝试使用已关闭的文件指针...\n");
        if (fputs("更多数据", fp) == EOF) {
            printf("写入失败\n");
            
            // 检查错误
            if (ferror(fp)) {
                printf("错误标志已设置\n");
            }
        }
    }
    
    remove("temp.txt");
}

// 综合错误处理函数
void safe_file_operation(const char* filename, const char* mode) {
    FILE *fp = fopen(filename, mode);
    if (fp == NULL) {
        fprintf(stderr, "无法打开文件 %s: %s\n", 
                filename, strerror(errno));
        return;
    }
    
    printf("成功打开文件 %s\n", filename);
    
    // 根据模式进行不同的操作
    if (strchr(mode, 'r') != NULL) {
        // 读取操作
        char buffer[256];
        int line_count = 0;
        
        while (fgets(buffer, sizeof(buffer), fp) != NULL) {
            line_count++;
            printf("第%d行: %s", line_count, buffer);
        }
        
        // 检查结束原因
        if (ferror(fp)) {
            fprintf(stderr, "读取文件时发生错误: %s\n", 
                    strerror(errno));
            clearerr(fp);
        } else if (feof(fp)) {
            printf("正常读取完成,共 %d 行\n", line_count);
        }
    }
    
    if (strchr(mode, 'w') != NULL || strchr(mode, 'a') != NULL) {
        // 写入操作
        const char* data = "这是测试数据\n";
        if (fputs(data, fp) == EOF) {
            fprintf(stderr, "写入文件时发生错误: %s\n", 
                    strerror(errno));
            
            if (ferror(fp)) {
                printf("文件错误标志已设置\n");
                clearerr(fp);
            }
        } else {
            printf("成功写入数据\n");
        }
    }
    
    // 关闭文件
    if (fclose(fp) == EOF) {
        fprintf(stderr, "关闭文件时发生错误: %s\n", 
                strerror(errno));
    } else {
        printf("成功关闭文件\n");
    }
}

int main() {
    // 创建测试文件
    FILE *fp = fopen("test_error.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "第一行\n第二行\n第三行\n");
        fclose(fp);
    }
    
    // 运行各种错误处理示例
    simulate_read_error();
    // simulate_disk_full();  // 谨慎使用,会创建大文件
    handle_permission_error();
    handle_invalid_file_pointer();
    
    printf("\n=== 综合错误处理示例 ===\n");
    safe_file_operation("test_error.txt", "r");
    printf("\n");
    safe_file_operation("test_error.txt", "a");
    printf("\n");
    safe_file_operation("/nonexistent/path/file.txt", "r");
    
    // 清理
    remove("test_error.txt");
    remove("error_test.txt");
    
    return 0;
}

1.6.3 文件出错标志和文件结束标志置0函数(clearerr)

clearerr函数

cpp 复制代码
void clearerr(FILE *stream);
  • 清除文件的错误标志和文件结束标志

  • 使文件可以继续操作

  • 没有返回值

示例

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

// 演示clearerr的基本用法
void basic_clearerr_example() {
    printf("=== clearerr基本用法 ===\n");
    
    // 创建测试文件
    FILE *fp = fopen("clearerr_test.txt", "w+");
    if (fp == NULL) {
        printf("无法创建文件\n");
        return;
    }
    
    // 写入一些数据
    fprintf(fp, "第一行数据\n");
    fprintf(fp, "第二行数据\n");
    
    // 回到文件开头
    rewind(fp);
    
    // 读取直到文件末尾
    char buffer[100];
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("读取: %s", buffer);
    }
    
    // 检查文件结束标志
    if (feof(fp)) {
        printf("文件结束标志已设置\n");
    }
    
    // 尝试继续读取(应该失败)
    printf("\n尝试在文件结束后读取...\n");
    if (fgets(buffer, sizeof(buffer), fp) == NULL) {
        printf("读取失败\n");
        if (feof(fp)) {
            printf("文件结束标志仍然存在\n");
        }
    }
    
    // 清除文件结束标志
    printf("\n清除文件结束标志...\n");
    clearerr(fp);
    
    // 再次检查
    if (feof(fp)) {
        printf("文件结束标志仍然存在\n");
    } else {
        printf("文件结束标志已清除\n");
    }
    
    // 现在可以继续读取吗?不,需要重新定位
    printf("\n尝试在清除标志后读取...\n");
    if (fgets(buffer, sizeof(buffer), fp) == NULL) {
        printf("仍然读取失败(需要重新定位)\n");
    }
    
    // 重新定位到文件开头
    rewind(fp);
    printf("\n重新定位后读取:\n");
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("读取: %s", buffer);
    }
    
    fclose(fp);
}

// 处理错误并恢复
void error_recovery_example() {
    printf("\n=== 错误恢复示例 ===\n");
    
    // 创建只读文件
    FILE *fp = fopen("readonly.txt", "w");
    if (fp != NULL) {
        fprintf(fp, "只读测试文件\n");
        fclose(fp);
    }
    
    // 以只读方式打开
    fp = fopen("readonly.txt", "r");
    if (fp == NULL) {
        printf("无法打开文件\n");
        return;
    }
    
    // 尝试写入(应该失败)
    printf("尝试写入只读文件...\n");
    if (fputs("尝试写入", fp) == EOF) {
        printf("写入失败\n");
        
        if (ferror(fp)) {
            printf("文件错误标志已设置\n");
            
            // 清除错误标志
            clearerr(fp);
            
            if (ferror(fp)) {
                printf("错误标志仍然存在\n");
            } else {
                printf("错误标志已清除\n");
                
                // 现在可以继续读取操作
                printf("\n错误恢复后继续读取:\n");
                rewind(fp);
                
                char buffer[100];
                if (fgets(buffer, sizeof(buffer), fp) != NULL) {
                    printf("成功读取: %s", buffer);
                }
            }
        }
    }
    
    fclose(fp);
}

// 循环读取直到成功
void retry_until_success() {
    printf("\n=== 重试直到成功 ===\n");
    
    // 模拟可能失败的操作
    for (int attempt = 1; attempt <= 3; attempt++) {
        printf("\n尝试 %d:\n", attempt);
        
        FILE *fp = fopen("retry_test.txt", "r");
        if (fp == NULL) {
            printf("无法打开文件,错误: %s\n", strerror(errno));
            
            if (attempt == 1) {
                // 第一次尝试,创建文件
                fp = fopen("retry_test.txt", "w");
                if (fp != NULL) {
                    fprintf(fp, "重试测试文件内容\n");
                    fclose(fp);
                    printf("已创建文件\n");
                }
            }
            continue;
        }
        
        // 成功打开,读取内容
        char buffer[100];
        if (fgets(buffer, sizeof(buffer), fp) != NULL) {
            printf("成功读取: %s", buffer);
            fclose(fp);
            break;  // 成功,退出循环
        } else {
            // 读取失败
            if (ferror(fp)) {
                printf("读取错误,错误: %s\n", strerror(errno));
                clearerr(fp);  // 清除错误标志
            }
            fclose(fp);
        }
    }
    
    remove("retry_test.txt");
}

// 监控文件状态变化
void monitor_file_status() {
    printf("\n=== 监控文件状态 ===\n");
    
    FILE *fp = fopen("monitor.txt", "w+");
    if (fp == NULL) {
        printf("无法创建文件\n");
        return;
    }
    
    // 记录初始状态
    printf("初始状态:\n");
    printf("  feof(): %d\n", feof(fp));
    printf("  ferror(): %d\n", ferror(fp));
    
    // 写入数据
    fprintf(fp, "监控测试数据\n");
    
    // 回到开头并读取
    rewind(fp);
    char buffer[100];
    fgets(buffer, sizeof(buffer), fp);
    printf("读取数据后状态:\n");
    printf("  feof(): %d\n", feof(fp));
    printf("  ferror(): %d\n", ferror(fp));
    
    // 尝试读取超出文件末尾
    fgets(buffer, sizeof(buffer), fp);
    printf("尝试读取超出末尾后状态:\n");
    printf("  feof(): %d\n", feof(fp));
    printf("  ferror(): %d\n", ferror(fp));
    
    // 清除标志
    clearerr(fp);
    printf("清除标志后状态:\n");
    printf("  feof(): %d\n", feof(fp));
    printf("  ferror(): %d\n", ferror(fp));
    
    // 尝试无效操作(写入但文件以读写方式打开)
    // 实际上可以写入,因为是以"w+"方式打开
    rewind(fp);
    fputs("新数据", fp);
    printf("写入后状态:\n");
    printf("  feof(): %d\n", feof(fp));
    printf("  ferror(): %d\n", ferror(fp));
    
    fclose(fp);
    remove("monitor.txt");
}

// 在多步骤操作中处理错误
void multi_step_operation() {
    printf("\n=== 多步骤操作错误处理 ===\n");
    
    FILE *fp = fopen("multistep.txt", "w+");
    if (fp == NULL) {
        printf("无法创建文件\n");
        return;
    }
    
    int step = 1;
    int success = 1;
    
    // 步骤1:写入数据
    printf("步骤%d: 写入数据\n", step++);
    if (fprintf(fp, "数据1\n数据2\n数据3\n") < 0) {
        printf("  写入失败\n");
        if (ferror(fp)) {
            printf("  错误: %s\n", strerror(errno));
            clearerr(fp);
        }
        success = 0;
    }
    
    if (success) {
        // 步骤2:回到文件开头
        printf("步骤%d: 回到文件开头\n", step++);
        rewind(fp);
        
        // 步骤3:读取数据
        printf("步骤%d: 读取数据\n", step++);
        char buffer[100];
        int line = 1;
        
        while (fgets(buffer, sizeof(buffer), fp) != NULL) {
            printf("  行%d: %s", line++, buffer);
        }
        
        // 检查读取结束原因
        if (ferror(fp)) {
            printf("  读取时发生错误\n");
            clearerr(fp);
            success = 0;
        }
    }
    
    if (success) {
        // 步骤4:追加数据
        printf("步骤%d: 追加数据\n", step++);
        if (fprintf(fp, "追加的数据\n") < 0) {
            printf("  追加失败\n");
            if (ferror(fp)) {
                printf("  错误: %s\n", strerror(errno));
                clearerr(fp);
            }
            success = 0;
        }
    }
    
    if (success) {
        printf("所有步骤完成成功\n");
    } else {
        printf("操作在步骤 %d 失败\n", step - 1);
    }
    
    fclose(fp);
    remove("multistep.txt");
}

int main() {
    basic_clearerr_example();
    error_recovery_example();
    retry_until_success();
    monitor_file_status();
    multi_step_operation();
    
    // 清理
    remove("clearerr_test.txt");
    remove("readonly.txt");
    
    return 0;
}

1.7 C库文件与系统文件

1.7.1 标准C库文件函数 vs 系统调用

对比表

特性 标准C库文件函数 系统调用
接口 高级,流式接口 低级,文件描述符接口
缓冲 有缓冲区 无缓冲区(直接系统调用)
移植性 高(标准C) 低(操作系统相关)
性能 较好(缓冲区优化) 可能更好(无缓冲开销)
示例 fopen, fread, fwrite open, read, write
错误处理 通过返回值/ferror 通过errno全局变量

1.7.2 文件描述符与文件指针的转换

示例

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

// 文件描述符转换为文件指针
FILE* fd_to_fp(int fd, const char* mode) {
    // 使用fdopen将文件描述符转换为文件指针
    FILE* fp = fdopen(fd, mode);
    if (fp == NULL) {
        perror("fdopen失败");
        close(fd);
    }
    return fp;
}

// 文件指针转换为文件描述符
int fp_to_fd(FILE* fp) {
    // 使用fileno获取文件描述符
    int fd = fileno(fp);
    if (fd == -1) {
        perror("fileno失败");
    }
    return fd;
}

// 混合使用系统调用和C库函数
void mixed_file_operations() {
    printf("=== 混合使用系统调用和C库函数 ===\n");
    
    // 使用系统调用创建文件
    int fd = open("mixed.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open失败");
        return;
    }
    
    printf("1. 使用系统调用写入数据...\n");
    const char* syscall_data = "这是使用系统调用写入的数据\n";
    write(fd, syscall_data, strlen(syscall_data));
    
    // 将文件描述符转换为文件指针
    FILE* fp = fd_to_fp(fd, "a");  // 追加模式
    if (fp == NULL) {
        return;
    }
    
    printf("2. 使用C库函数追加数据...\n");
    const char* clib_data = "这是使用C库函数追加的数据\n";
    fprintf(fp, "%s", clib_data);
    
    // 刷新缓冲区,确保数据写入
    fflush(fp);
    
    // 获取文件描述符
    int new_fd = fp_to_fd(fp);
    printf("3. 文件描述符: %d\n", new_fd);
    
    // 使用系统调用继续写入
    printf("4. 再次使用系统调用写入...\n");
    const char* more_data = "再次使用系统调用写入\n";
    write(new_fd, more_data, strlen(more_data));
    
    // 关闭文件
    fclose(fp);  // 这会同时关闭文件描述符
    
    // 重新打开文件读取
    printf("\n5. 读取文件内容:\n");
    fp = fopen("mixed.txt", "r");
    if (fp != NULL) {
        char buffer[256];
        while (fgets(buffer, sizeof(buffer), fp) != NULL) {
            printf("   %s", buffer);
        }
        fclose(fp);
    }
    
    // 清理
    remove("mixed.txt");
}

// 使用dup复制文件描述符
void duplicate_file_descriptor() {
    printf("\n=== 复制文件描述符 ===\n");
    
    // 创建文件
    int fd1 = open("dup_test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd1 == -1) {
        perror("open失败");
        return;
    }
    
    // 复制文件描述符
    int fd2 = dup(fd1);
    printf("原始文件描述符: %d\n", fd1);
    printf("复制的文件描述符: %d\n", fd2);
    
    // 使用原始描述符写入
    const char* data1 = "通过fd1写入的数据\n";
    write(fd1, data1, strlen(data1));
    
    // 使用复制描述符写入
    const char* data2 = "通过fd2写入的数据\n";
    write(fd2, data2, strlen(data2));
    
    // 关闭文件描述符
    close(fd1);
    close(fd2);
    
    // 读取文件验证
    printf("\n文件内容:\n");
    FILE* fp = fopen("dup_test.txt", "r");
    if (fp != NULL) {
        char buffer[256];
        while (fgets(buffer, sizeof(buffer), fp) != NULL) {
            printf("   %s", buffer);
        }
        fclose(fp);
    }
    
    remove("dup_test.txt");
}

// 标准输入输出的文件描述符操作
void stdio_descriptor_operations() {
    printf("\n=== 标准输入输出文件描述符操作 ===\n");
    
    // 获取标准输出的文件描述符
    int stdout_fd = fileno(stdout);
    printf("标准输出文件描述符: %d\n", stdout_fd);
    
    // 使用文件描述符写入标准输出
    const char* msg = "通过文件描述符写入标准输出\n";
    write(stdout_fd, msg, strlen(msg));
    
    // 将标准输出重定向到文件
    printf("\n将标准输出重定向到文件...\n");
    
    int saved_stdout = dup(STDOUT_FILENO);  // 保存原始标准输出
    
    int file_fd = open("redirect.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (file_fd != -1) {
        // 将标准输出重定向到文件
        dup2(file_fd, STDOUT_FILENO);
        close(file_fd);
        
        // 现在printf会输出到文件
        printf("这行文字会被写入文件而不是屏幕\n");
        printf("另一行文字\n");
        
        // 恢复标准输出
        fflush(stdout);  // 确保缓冲区刷新
        dup2(saved_stdout, STDOUT_FILENO);
        close(saved_stdout);
        
        printf("标准输出已恢复\n");
        
        // 显示文件内容
        printf("\n重定向期间写入的文件内容:\n");
        FILE* fp = fopen("redirect.txt", "r");
        if (fp != NULL) {
            char buffer[256];
            while (fgets(buffer, sizeof(buffer), fp) != NULL) {
                printf("   %s", buffer);
            }
            fclose(fp);
        }
        
        remove("redirect.txt");
    }
}

// 文件锁定示例
void file_locking_example() {
    printf("\n=== 文件锁定示例 ===\n");
    
    const char* filename = "lockfile.txt";
    
    // 创建并锁定文件
    int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("创建文件失败");
        return;
    }
    
    // 设置文件锁
    struct flock lock;
    lock.l_type = F_WRLCK;     // 写锁
    lock.l_whence = SEEK_SET;  // 从文件开头
    lock.l_start = 0;          // 偏移0
    lock.l_len = 0;            // 锁定整个文件
    
    printf("尝试获取文件锁...\n");
    if (fcntl(fd, F_SETLK, &lock) == -1) {
        perror("获取锁失败");
        close(fd);
        return;
    }
    
    printf("已获取文件锁\n");
    
    // 写入数据
    const char* data = "锁定期间写入的数据\n";
    write(fd, data, strlen(data));
    
    printf("写入数据完成,等待5秒...\n");
    sleep(5);  // 保持锁5秒
    
    // 释放锁
    lock.l_type = F_UNLCK;
    fcntl(fd, F_SETLK, &lock);
    printf("已释放文件锁\n");
    
    close(fd);
    
    // 显示文件内容
    printf("\n文件内容:\n");
    FILE* fp = fopen(filename, "r");
    if (fp != NULL) {
        char buffer[256];
        while (fgets(buffer, sizeof(buffer), fp) != NULL) {
            printf("   %s", buffer);
        }
        fclose(fp);
    }
    
    remove(filename);
}

int main() {
    mixed_file_operations();
    duplicate_file_descriptor();
    stdio_descriptor_operations();
    file_locking_example();
    
    return 0;
}

1.8 本章小结

1.8.1 文件操作核心函数总结

文件打开与关闭

  • fopen() / fclose():标准C库函数

  • open() / close():系统调用(Linux/Unix)

文件读写

  • 字符读写:fgetc() / fputc()

  • 字符串读写:fgets() / fputs()

  • 数据块读写:fread() / fwrite()

  • 格式化读写:fscanf() / fprintf()

  • 系统调用:read() / write()

文件定位

  • fseek():设置文件位置

  • ftell():获取当前位置

  • rewind():回到文件开头

  • lseek():系统调用的文件定位

文件状态检测

  • feof():检查文件结束

  • ferror():检查文件错误

  • clearerr():清除错误标志

1.8.2 最佳实践

  1. 错误处理

    cpp 复制代码
    FILE *fp = fopen("file.txt", "r");
    if (fp == NULL) {
        perror("打开文件失败");
        exit(EXIT_FAILURE);
    }
  2. 资源清理

    cpp 复制代码
    FILE *fp = fopen("file.txt", "r");
    if (fp != NULL) {
        // 操作文件
        fclose(fp);
        fp = NULL;  // 避免悬空指针
    }
  3. 缓冲区管理

    cpp 复制代码
    // 及时刷新缓冲区
    fflush(fp);
    
    // 设置缓冲区大小
    char buffer[8192];
    setvbuf(fp, buffer, _IOFBF, sizeof(buffer));
  4. 二进制文件处理

    cpp 复制代码
    // 使用二进制模式
    FILE *fp = fopen("data.bin", "wb");
    // 处理结构体时考虑内存对齐
    #pragma pack(push, 1)
    typedef struct { ... } Data;
    #pragma pack(pop)

1.8.3 Linux特有功能

  1. 文件描述符操作

    • dup() / dup2():复制文件描述符

    • fcntl():文件控制操作(锁定等)

  2. 内存映射

    • mmap():将文件映射到内存

    • munmap():取消内存映射

  3. 文件状态

    • stat():获取文件信息

    • fstat():获取已打开文件的信息

  4. 目录操作

    • opendir() / readdir() / closedir():目录遍历

    • mkdir() / rmdir():创建/删除目录

1.8.4 性能优化建议

  1. 缓冲区大小:适当增大缓冲区可以提高I/O性能

  2. 批量读写 :使用fread()/fwrite()代替单字符读写

  3. 减少系统调用:使用带缓冲的C库函数

  4. 内存映射 :对于大文件,使用mmap()可以提高访问速度

  5. 异步I/O:对于高并发场景,考虑使用异步I/O

1.8.5 常见错误与调试技巧

  1. 文件打开失败

    • 检查文件路径和权限

    • 使用perror()strerror(errno)查看错误信息

  2. 文件读写错误

    • 检查文件打开模式

    • 验证返回值

    • 使用ferror()检查错误标志

  3. 内存泄漏

    • 确保每个fopen()都有对应的fclose()

    • 使用工具如Valgrind检测内存泄漏

  4. 文件损坏

    • 二进制文件:检查字节序和结构体对齐

    • 文本文件:注意换行符差异(Windows vs Unix)

1.8.6 实际应用场景

  1. 配置文件读取 :使用fscanf()解析INI/JSON格式

  2. 日志系统 :使用fprintf()写入格式化日志

  3. 数据持久化 :使用fwrite()保存结构体数据

  4. 文件上传/下载 :使用fread()/fwrite()处理网络数据

  5. 数据库底层:使用随机访问实现简单数据库

相关推荐
愚者游世9 小时前
Delegating Constructor(委托构造函数)各版本异同
开发语言·c++·程序人生·面试·改行学it
梵刹古音9 小时前
【C语言】 指针基础与定义
c语言·开发语言·算法
Ekehlaft9 小时前
这款国产 AI,让 Python 小白也能玩转编程
开发语言·人工智能·python·ai·aipy
rit84324999 小时前
MATLAB中Teager能量算子提取与解调信号的实现
开发语言·matlab
唐装鼠9 小时前
Linux 下 malloc 内存分配机制详解
linux·malloc
予枫的编程笔记9 小时前
【Linux入门篇】Linux运维必学:Vim核心操作详解,告别编辑器依赖
linux·人工智能·linux运维·vim操作教程·程序员工具·编辑器技巧·新手学vim
开源技术10 小时前
Python GeoPandas基础知识:地图、投影和空间连接
开发语言·ide·python
Cult Of10 小时前
Alicea Wind的个人网站开发日志(2)
开发语言·python·vue
我找到地球的支点啦10 小时前
通信扩展——扩频技术(超级详细,附带Matlab代码)
开发语言·matlab