C语言文件操作与预处理详解

发布于:2025-06-14 ⋅ 阅读:(18) ⋅ 点赞:(0)


在C语言编程中,文件操作和预处理是两个重要的组成部分。文件操作允许程序与外部存储设备交互,而预处理则在编译前对源代码进行文本处理。这两个功能为C程序提供了强大的扩展性和灵活性。

文件操作

文件基本概念

在C语言中,文件是存储在外部介质(如硬盘、U盘)上的数据集合。C语言将文件视为字节序列,并提供了两种文件处理模式:

  1. 文本模式:以字符为单位处理文件,自动处理换行符(Windows:\r\n ↔ Unix:\n
  2. 二进制模式:以字节为单位处理文件,不进行任何转换

文件指针

文件操作通过文件指针(FILE*)实现,它指向一个包含文件信息的结构体:

#include <stdio.h>

FILE *fp;  // 声明文件指针

// 打开文件
fp = fopen("example.txt", "r");  // 以只读模式打开文本文件

if (fp == NULL) {
    printf("无法打开文件\n");
    return 1;
}

// 文件操作...

// 关闭文件
fclose(fp);

文件打开模式

模式 描述
“r” 只读模式,文件必须存在
“w” 写入模式,创建新文件或覆盖
“a” 追加模式,在文件末尾添加内容
“r+” 读写模式,文件必须存在
“w+” 读写模式,创建新文件或覆盖
“a+” 读写模式,在文件末尾添加内容

文件读取操作

字符读取
#include <stdio.h>

int main() {
    FILE *fp = fopen("example.txt", "r");
    if (fp == NULL) {
        printf("无法打开文件\n");
        return 1;
    }
    
    int ch;
    // 逐个字符读取,EOF表示文件结束
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);  // 输出到屏幕
    }
    
    fclose(fp);
    return 0;
}
字符串读取
#include <stdio.h>

int main() {
    FILE *fp = fopen("example.txt", "r");
    if (fp == NULL) {
        printf("无法打开文件\n");
        return 1;
    }
    
    char buffer[100];
    // 读取一行文本(最多99个字符)
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("%s", buffer);
    }
    
    fclose(fp);
    return 0;
}
格式化读取
#include <stdio.h>

int main() {
    FILE *fp = fopen("data.txt", "r");
    if (fp == NULL) {
        printf("无法打开文件\n");
        return 1;
    }
    
    int num;
    float f;
    char str[50];
    
    // 格式化读取
    while (fscanf(fp, "%d %f %s", &num, &f, str) == 3) {
        printf("读取: %d, %.2f, %s\n", num, f, str);
    }
    
    fclose(fp);
    return 0;
}
二进制读取
#include <stdio.h>

typedef struct {
    int id;
    char name[50];
    float score;
} Student;

int main() {
    FILE *fp = fopen("students.dat", "rb");  // 二进制读取
    if (fp == NULL) {
        printf("无法打开文件\n");
        return 1;
    }
    
    Student s;
    // 读取结构体数据
    while (fread(&s, sizeof(Student), 1, fp) == 1) {
        printf("ID: %d, 姓名: %s, 分数: %.2f\n", s.id, s.name, s.score);
    }
    
    fclose(fp);
    return 0;
}

文件写入操作

字符写入
#include <stdio.h>

int main() {
    FILE *fp = fopen("output.txt", "w");
    if (fp == NULL) {
        printf("无法创建文件\n");
        return 1;
    }
    
    char text[] = "Hello, World!";
    for (int i = 0; text[i] != '\0'; i++) {
        fputc(text[i], fp);  // 写入单个字符
    }
    
    fclose(fp);
    return 0;
}
字符串写入
#include <stdio.h>

int main() {
    FILE *fp = fopen("output.txt", "w");
    if (fp == NULL) {
        printf("无法创建文件\n");
        return 1;
    }
    
    char *text = "这是一行文本\n";
    fputs(text, fp);  // 写入字符串(不自动添加换行符)
    
    fclose(fp);
    return 0;
}
格式化写入
#include <stdio.h>

int main() {
    FILE *fp = fopen("data.txt", "w");
    if (fp == NULL) {
        printf("无法创建文件\n");
        return 1;
    }
    
    int num = 42;
    float f = 3.14;
    char *str = "Hello";
    
    // 格式化写入
    fprintf(fp, "%d %.2f %s\n", num, f, str);
    
    fclose(fp);
    return 0;
}
二进制写入
#include <stdio.h>

typedef struct {
    int id;
    char name[50];
    float score;
} Student;

int main() {
    FILE *fp = fopen("students.dat", "wb");  // 二进制写入
    if (fp == NULL) {
        printf("无法创建文件\n");
        return 1;
    }
    
    Student s = {1, "张三", 85.5};
    // 写入结构体数据
    fwrite(&s, sizeof(Student), 1, fp);
    
    fclose(fp);
    return 0;
}

文件定位操作

#include <stdio.h>

int main() {
    FILE *fp = fopen("example.txt", "r");
    if (fp == NULL) {
        printf("无法打开文件\n");
        return 1;
    }
    
    // 获取文件位置
    long pos = ftell(fp);  // 初始位置为0
    
    // 移动文件指针
    fseek(fp, 10, SEEK_SET);  // 从文件开头移动10个字节
    fseek(fp, 5, SEEK_CUR);   // 从当前位置移动5个字节
    fseek(fp, -20, SEEK_END); // 从文件末尾向前移动20个字节
    
    // 重置文件指针到开头
    rewind(fp);
    
    fclose(fp);
    return 0;
}

文件错误处理

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main() {
    FILE *fp = fopen("nonexistent.txt", "r");
    
    if (fp == NULL) {
        // 打印错误信息
        printf("错误: %s\n", strerror(errno));
        return 1;
    }
    
    // 检查文件操作错误
    if (ferror(fp)) {
        printf("文件操作错误\n");
        clearerr(fp);  // 清除错误标志
    }
    
    fclose(fp);
    return 0;
}

预处理

预处理基本概念

预处理是C编译过程的第一步,由预处理器(Preprocessor)执行。预处理指令以#开头,它们在编译前被处理,用于修改源代码文本。

常见预处理指令

  1. 文件包含#include
  2. 宏定义#define#undef
  3. 条件编译#if#ifdef#ifndef#elif#else#endif
  4. 错误处理#error
  5. 行号和文件名#line
  6. 编译控制#pragma

文件包含指令

// 标准库头文件
#include <stdio.h>    // 从标准库目录查找
#include <string.h>

// 自定义头文件
#include "myheader.h" // 从当前目录或指定目录查找

宏定义

简单宏
#define PI 3.14159
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main() {
    float radius = 5.0;
    float area = PI * radius * radius;
    
    int x = 10, y = 20;
    int max_val = MAX(x, y);
    
    return 0;
}
带参数的宏
#define SQUARE(x) ((x) * (x))
#define PRINT_INT(x) printf("Value: %d\n", x)

int main() {
    int a = 5;
    int result = SQUARE(a + 1);  // 展开为 ((a + 1) * (a + 1))
    
    PRINT_INT(result);  // 展开为 printf("Value: %d\n", result)
    
    return 0;
}
字符串化操作符(#)
#define STR(x) #x

int main() {
    printf(STR(Hello World!));  // 展开为 printf("Hello World!");
    printf(STR(1 + 2));         // 展开为 printf("1 + 2");
    
    return 0;
}
标记粘贴操作符(##)
#define CONCAT(a, b) a##b

int main() {
    int xy = 100;
    printf("%d\n", CONCAT(x, y));  // 展开为 printf("%d\n", xy);
    
    return 0;
}

条件编译

#ifdef 和 #ifndef
#ifdef DEBUG
    printf("调试信息: 变量x = %d\n", x);
#endif

#ifndef MAX_SIZE
    #define MAX_SIZE 100
#endif
#if 和 #elif
#define VERSION 2

#if VERSION == 1
    printf("使用版本1\n");
#elif VERSION == 2
    printf("使用版本2\n");
#else
    printf("未知版本\n");
#endif
条件编译示例:平台特定代码
#ifdef _WIN32
    // Windows平台代码
    #include <windows.h>
    #define LINE_END "\r\n"
#elif __linux__
    // Linux平台代码
    #include <unistd.h>
    #define LINE_END "\n"
#else
    #error "不支持的平台"
#endif

其他预处理指令

#error 指令
#if !defined(__STDC__)
    #error "需要标准C编译器"
#endif
#line 指令
#line 100 "custom_file.c"
// 从这里开始,行号从100开始,文件名显示为custom_file.c
#pragma 指令
#pragma once  // 保证头文件只被包含一次

#pragma GCC diagnostic ignored "-Wunused-variable"  // 忽略未使用变量警告

预处理的优缺点

优点
  1. 代码复用:通过宏和头文件实现代码重用
  2. 条件编译:支持跨平台开发和调试版本
  3. 代码生成:在编译前自动生成代码
  4. 性能优化:宏替换可以减少函数调用开销
缺点
  1. 可读性降低:过度使用宏会使代码难以理解
  2. 调试困难:错误可能出现在预处理后的代码中
  3. 潜在副作用:宏参数可能被多次求值
  4. 命名冲突:宏定义可能与其他标识符冲突

预处理与编译的区别

特性 预处理阶段 编译阶段
执行时间 编译前 预处理后
处理内容 文本替换、文件包含、条件编译 词法分析、语法分析、代码生成
输出结果 修改后的源代码 目标代码(汇编或机器码)
工具 预处理器(cpp) 编译器(cc、gcc)
指令形式 以#开头的预处理指令 C语言语句

实际应用示例

文件操作示例:学生成绩管理系统

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_NAME_LEN 50
#define MAX_STUDENTS 100

typedef struct {
    int id;
    char name[MAX_NAME_LEN];
    float score;
} Student;

// 保存学生信息到文件
void saveStudents(Student students[], int count) {
    FILE *fp = fopen("students.dat", "wb");
    if (fp == NULL) {
        printf("无法创建文件\n");
        return;
    }
    
    fwrite(&count, sizeof(int), 1, fp);  // 写入学生数量
    fwrite(students, sizeof(Student), count, fp);  // 写入学生数据
    
    fclose(fp);
    printf("已保存 %d 名学生信息\n", count);
}

// 从文件加载学生信息
int loadStudents(Student students[]) {
    FILE *fp = fopen("students.dat", "rb");
    if (fp == NULL) {
        printf("无法打开文件或文件不存在\n");
        return 0;
    }
    
    int count;
    fread(&count, sizeof(int), 1, fp);  // 读取学生数量
    fread(students, sizeof(Student), count, fp);  // 读取学生数据
    
    fclose(fp);
    printf("已加载 %d 名学生信息\n", count);
    return count;
}

int main() {
    Student students[MAX_STUDENTS];
    int count = 0;
    
    // 添加学生信息
    students[count].id = 1;
    strcpy(students[count].name, "张三");
    students[count].score = 85.5;
    count++;
    
    students[count].id = 2;
    strcpy(students[count].name, "李四");
    students[count].score = 92.0;
    count++;
    
    // 保存到文件
    saveStudents(students, count);
    
    // 清空数组
    memset(students, 0, sizeof(students));
    count = 0;
    
    // 从文件加载
    count = loadStudents(students);
    
    // 显示学生信息
    for (int i = 0; i < count; i++) {
        printf("ID: %d, 姓名: %s, 分数: %.1f\n", 
               students[i].id, students[i].name, students[i].score);
    }
    
    return 0;
}

预处理示例:调试宏

#ifdef DEBUG
    #define DEBUG_PRINT(fmt, ...) printf("DEBUG: " fmt, __VA_ARGS__)
    #define DEBUG_LINE() printf("DEBUG: Line %d in %s\n", __LINE__, __FILE__)
#else
    #define DEBUG_PRINT(fmt, ...) do {} while(0)
    #define DEBUG_LINE() do {} while(0)
#endif

// 平台检测
#ifdef _WIN32
    #define PLATFORM "Windows"
    #include <windows.h>
#elif __linux__
    #define PLATFORM "Linux"
    #include <unistd.h>
#elif __APPLE__
    #define PLATFORM "macOS"
    #include <unistd.h>
#else
    #define PLATFORM "Unknown"
#endif

int main() {
    DEBUG_LINE();
    DEBUG_PRINT("程序开始运行,平台: %s\n", PLATFORM);
    
    int x = 42;
    DEBUG_PRINT("变量x的值: %d\n", x);
    
    // 正常代码...
    
    return 0;
}

总结

文件操作和预处理是C语言中两个重要的功能,它们分别在程序运行时和编译前发挥作用:

  • 文件操作允许程序与外部存储设备交互,支持文本和二进制两种模式
  • 预处理在编译前对源代码进行文本处理,提供宏定义、文件包含和条件编译等功能
  • 文件操作通过文件指针和标准库函数实现,需要注意文件打开模式和错误处理
  • 预处理指令以#开头,它们不是C语言语句,而是由预处理器单独处理

掌握文件操作和预处理技术,能够使C程序更加灵活、可移植,并支持复杂的数据处理和代码组织。