《零基础学 C 语言文件顺序读写:fputc/fgetc 到 fread/fwrite 函数详解》

发布于:2025-08-30 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

1. fputc函数和fgetc函数

fputc函数

fgetc函数

2. feof和ferror函数

feof 函数

ferror 函数

3.   fputs函数和fgets函数

fputs 函数

fgets 函数

4.  fprintf函数和fscanf函数

fprintf 函数

fscanf 函数

5. fread函数和fwrite函数

fwrite 函数

fread 函数


1. fputc函数和fgetc函数

fputc函数

在 C 语言中,fputc 函数用于向指定的文件流写入单个字符,是文件字符输出的基础函数。

函数原型

#include <stdio.h>
int fputc(int character, FILE *stream);

参数说明

  • character:要写入的字符(虽然声明为 int,但实际只使用低 8 位,即 char 范围)。

  • stream:文件指针(FILE* 类型),指定写入的目标流(如文件流、标准输出流 stdout 等)。

返回值

  • 成功:返回写入的字符(以 int 类型表示)。

  • 失败:返回 EOF(通常为 -1,表示写入错误或文件结束)。

基本用法

1. 向文件写入字符

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "w");  // 以只写模式打开文件
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }

    // 写入单个字符
    fputc('A', fp);       // 写入字符 'A'
    fputc('\n', fp);      // 写入换行符
    fputc('B', fp);       // 写入字符 'B'

    fclose(fp);
    return 0;
}

运行后,test.txt 文件内容为:

A
B

2. 向标准输出流(屏幕)写入字符

fputc 也可用于标准流,例如向屏幕输出:

#include <stdio.h>

int main() {
    // 向 stdout(屏幕)输出字符,等价于 putchar('H')
    fputc('H', stdout);
    fputc('i', stdout);
    fputc('\n', stdout);  // 输出换行
    return 0;
}

运行后屏幕输出:

Hi

特点与注意事项

  1. 逐个字符写入fputc 每次只能写一个字符,适合处理文本文件的逐字符输出。

  2. 缓冲机制:写入文件时,数据会先存入缓冲区,缓冲区满或调用 fclose/fflush 时才会真正写入磁盘。

  3. 与 putchar 的关系putchar(c) 本质是 fputc(c, stdout) 的宏定义,专门用于向屏幕输出单个字符。

  4. 错误处理:需检查返回值是否为 EOF 以判断写入是否成功,例如:

    if (fputc('C', fp) == EOF) {
        perror("写入失败");
    }
    

  5. 二进制文件适用fputc 同样可用于二进制文件(打开模式带 b),直接写入字符的 ASCII 码值(原始字节)。

fgetc函数

在 C 语言中,fgetc 函数用于从指定的文件流中读取一个字符,是进行文件字符输入操作的常用函数。

函数原型

#include <stdio.h>
int fgetc(FILE *stream);

参数说明

stream:文件指针(FILE* 类型),指向要从中读取字符的文件流。可以是通过 fopen 函数打开的文件流,也可以是标准输入流 stdin 等。

返回值

  • 成功:返回读取到的字符(以 int 类型表示,实际使用低 8 位来存储字符的 ASCII 码值 )。
  • 失败或到达文件末尾:返回 EOF(通常被定义为 -1 )。

基本用法示例

1. 从文件中读取字符

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }

    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);  // 将读取到的字符输出到屏幕
    }

    fclose(fp);
    return 0;
}

上述代码中,先以只读模式打开 test.txt 文件,然后使用 fgetc 函数循环读取文件中的字符,直到读取到文件末尾(返回 EOF ),并通过 putchar 函数将读取到的字符逐个输出到屏幕上。

2. 从标准输入流(键盘)读取字符

#include <stdio.h>

int main() {
    int ch;
    printf("请输入一些字符,按 Ctrl+Z(Windows)或 Ctrl+D(Linux/macOS)结束输入:\n");
    while ((ch = fgetc(stdin)) != EOF) {
        putchar(ch);
    }
    return 0;
}

这段代码通过 fgetc 从标准输入流 stdin 中读取用户从键盘输入的字符,并将其输出到屏幕上。在 Windows 系统中,用户按下 Ctrl+Z 再按回车键表示输入结束;在 Linux 和 macOS 系统中,按下 Ctrl+D 表示输入结束。

注意事项

1.错误判断:在使用 fgetc 读取字符后,一定要检查返回值是否为 EOF ,以此判断是读取到文件末尾还是发生了读取错误。

2.类型转换:虽然 fgetc 的返回值是 int 类型,但在将返回值赋值给 char 类型变量时,需要进行合适的类型转换,并且要注意处理 EOF 的情况,避免误将 EOF 当作普通字符处理。

3.缓冲区影响:文件流存在缓冲区,fgetc 读取字符时,先从缓冲区获取数据。当缓冲区为空时,才会真正从磁盘文件中读取数据填充缓冲区。

4.二进制文件读取fgetc 也可以用于读取二进制文件,但由于 EOF 也是一个合法的返回值,所以在读取二进制文件时,不能单纯通过判断返回值是否为 EOF 来判断读取是否结束,通常需要结合文件大小等其他信息来判断。

2. feof和ferror函数

在 C 语言文件操作中,feofferror是用于判断文件操作状态的两个重要函数,前者主要用于检测文件是否到达结尾,后者用于判断文件操作过程中是否出现错误,以下是详细介绍:

feof 函数

  • 函数原型

#include <stdio.h>
int feof(FILE *stream);
  • 参数说明stream是一个文件指针,指向要检测状态的文件流,这个文件流通常是通过fopen函数打开文件后返回的指针。

  • 返回值

    • 文件未到达结尾:函数返回 0。

    • 文件到达结尾:函数返回一个非零值(通常是 1)。

  • 使用场景及示例
    在使用循环从文件中读取数据时,判断是否读取到文件末尾。

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }
    int ch;
    while (!feof(fp)) {
        ch = fgetc(fp);
        putchar(ch);
    }
    fclose(fp);
    return 0;
}

上述代码中,通过feof函数判断是否到达文件末尾,若未到达则继续读取字符并输出。但需要注意,feof函数是在读取操作后才更新文件结束标志,因此在读取文件时,不能单纯依赖feof函数作为循环读取的条件,因为可能会多读一次,更好的做法是先读取,再判断返回值是否为EOF,如:

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }
    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }
    fclose(fp);
    return 0;
}

ferror 函数

  • 函数原型

#include <stdio.h>
int ferror(FILE *stream);
  • 参数说明stream同样是一个文件指针,指向要检测状态的文件流。

  • 返回值

    • 文件操作未出错:函数返回 0。

    • 文件操作出现错误:函数返回一个非零值(通常是 1)。

  • 使用场景及示例
    在对文件进行读写操作后,判断是否出现错误。

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "w");
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }
    if (fputs("Hello, world!", fp) == EOF) {
        if (ferror(fp)) {
            perror("写入文件时出错");
        }
    }
    fclose(fp);
    return 0;
}

上述代码中,在使用fputs向文件写入数据后,判断返回值是否为EOF,若为EOF则调用ferror函数判断是否是因为写入错误导致的,若是则输出错误信息。

注意事项

  • feof函数:不能在未进行读取操作前就用它判断文件是否结束,要结合实际读取操作和返回值判断。

  • ferror函数:它检测的是最近一次文件操作是否出错,在进行新的文件操作前,需要根据需要进行错误处理,例如可以使用clearerr函数清除错误标志,以便后续正确检测文件操作状态。

clearerr(fp); // 清除文件流fp的错误和文件结束标志

3.   fputs函数和fgets函数

在 C 语言中,fputs 和 fgets 是用于字符串级文件读写的标准库函数,分别用于向文件流写入字符串和从文件流读取字符串,比单字符操作的 fputc/fgetc 更高效。

fputs 函数

向文件流写入字符串

函数原型

#include <stdio.h>
int fputs(const char *str, FILE *stream);

参数说明

  • str:要写入的字符串(以 \0 结尾的字符数组)。

  • stream:目标文件流(如文件指针、stdout 等)。

返回值

  • 成功:返回非负值(通常为非零)。

  • 失败:返回 EOF-1)。

特点与用法

  1. 写入规则
    只写入字符串中的有效字符(从首字符到 \0 前的字符),不自动添加换行符,也不写入 \0 本身。

  2. 示例 1:向文件写入字符串

    #include <stdio.h>
    
    int main() {
        FILE *fp = fopen("test.txt", "w");
        if (fp == NULL) {
            perror("文件打开失败");
            return 1;
        }
        
        // 写入字符串(需手动添加换行符)
        fputs("Hello, fputs!", fp);
        fputs("\n这是第二行", fp);  // 手动加 '\n' 换行
        
        fclose(fp);
        return 0;
    }
    

    结果(test.txt):

    Hello, fputs!
    这是第二行
    
  3. 示例 2:向标准输出流(屏幕)写入
    等价于 puts 但不自动换行:

    fputs("Hello, stdout!", stdout);  // 输出到屏幕,无换行
    fputs("\n", stdout);              // 手动换行
    

fgets 函数

从文件流读取字符串

函数原型

#include <stdio.h>
char *fgets(char *str, int size, FILE *stream);

参数说明

  • str:用于存储读取结果的字符数组。

  • size:最大读取长度(包含结尾的 \0,实际最多读 size-1 个字符)。

  • stream:源文件流(如文件指针、stdin 等)。

返回值

  • 成功:返回 str(读取到的字符串的地址)。

  • 失败或文件结束:返回 NULL

特点与用法

  1. 读取规则

    • 最多读取 size-1 个字符,或遇到换行符 \n 时停止(会保留换行符)。

    • 读取结束后自动在末尾添加 \0,确保字符串完整。

  2. 示例 1:从文件读取字符串

    #include <stdio.h>
    
    int main() {
        FILE *fp = fopen("test.txt", "r");
        if (fp == NULL) {
            perror("文件打开失败");
            return 1;
        }
        
        char buffer[100];
        // 循环读取,直到文件结束
        while (fgets(buffer, 100, fp) != NULL) {
            printf("读取到:%s", buffer);  // buffer 已包含换行符
        }
        
        fclose(fp);
        return 0;
    }
    

    若 test.txt 内容为:

    第一行内容
    第二行内容
    

    输出结果:

    读取到:第一行内容
    读取到:第二行内容
    
  3. 示例 2:从标准输入流(键盘)读取
    可读取一行输入(含空格):

    char input[50];
    printf("请输入一行文字:");
    fgets(input, 50, stdin);  // 读取键盘输入,最多49个字符
    printf("你输入了:%s", input);
    

三、注意事项

  1. fputs 的坑点

    • 不会自动添加换行符,需手动写入 \n

    • 若字符串中包含 \0(如二进制数据),会提前终止写入(只写 \0 前的内容)。

  2. fgets 的坑点

    • 若一行内容超过 size-1,会截断读取(剩余内容留在流中,下次读取继续)。

    • 会保留换行符 \n,如需去除可手动处理:

// 去除 fgets 读取的换行符
char buffer[100];
fgets(buffer, 100, fp);
buffer[strcspn(buffer, "\n")] = '\0';  // 用 \0 替换 \n

读取到文件末尾或错误时返回 NULL,需结合 feof/ferror 判断原因。

  1. 与 puts/gets 的区别

    • puts(str) 等价于 fputs(str, stdout) + fputs("\n", stdout)(自动换行)。

    • gets 因无长度限制存在缓冲区溢出风险,已被弃用,推荐用 fgets 替代。

4.  fprintf函数和fscanf函数

在 C 语言中,fprintf函数和fscanf函数是用于格式化输入输出的函数,和标准输入输出函数printfscanf 功能类似,区别在于fprintffscanf操作的是文件流,可以实现向文件中进行格式化写入以及从文件中格式化读取数据。

fprintf 函数

fprintf函数用于向指定的文件流中按照指定的格式写入数据。

函数原型

#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...);

参数说明

  • stream:指向要写入数据的文件流,可以是通过fopen函数打开的文件指针,也可以是标准输出流stdout(表示输出到屏幕)、标准错误流stderr 。

  • format:格式化字符串,用于指定输出数据的格式,包含普通字符、转义字符以及格式说明符,如%d(表示整数)、%f(表示浮点数)、%s(表示字符串)等。

  • ...:可变参数列表,根据format中格式说明符的数量和类型,传入相应的要输出的数据。

返回值

  • 成功:返回实际写入的字符个数。

  • 失败:返回一个负数。

示例代码

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "w");
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }

    int num = 10;
    float fnum = 3.14;
    char str[] = "Hello";

    // 向文件中格式化写入数据
    int written_count = fprintf(fp, "整数: %d, 浮点数: %f, 字符串: %s\n", num, fnum, str);
    if (written_count < 0) {
        perror("写入失败");
    }

    fclose(fp);
    return 0;
}

上述代码以写入模式打开文件test.txt,然后使用fprintf将整数、浮点数和字符串按照指定格式写入文件中,最后关闭文件。

fscanf 函数

fscanf函数用于从指定的文件流中按照指定的格式读取数据。

函数原型

#include <stdio.h>
int fscanf(FILE *stream, const char *format, ...);

参数说明

  • stream:指向要读取数据的文件流,通常是通过fopen函数打开的文件指针,也可以是标准输入流stdin(表示从键盘读取)。

  • format:格式化字符串,用于指定读取数据的格式,与fprintf中的format类似,包含格式说明符等。

  • ...:可变参数列表,是指向用于存储读取数据的变量的指针,参数的数量和类型要与format中的格式说明符相匹配。

返回值

  • 成功:返回成功匹配和赋值的输入项的个数。

  • 失败:返回EOF(通常为-1)。

示例代码

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }

    int num;
    float fnum;
    char str[20];

    // 从文件中格式化读取数据
    int read_count = fscanf(fp, "整数: %d, 浮点数: %f, 字符串: %s", &num, &fnum, str);
    if (read_count == EOF) {
        perror("读取失败");
    } else {
        printf("读取到的数据:整数: %d, 浮点数: %f, 字符串: %s\n", num, fnum, str);
    }

    fclose(fp);
    return 0;
}

上述代码以读取模式打开文件test.txt,然后使用fscanf按照指定格式从文件中读取整数、浮点数和字符串,根据读取结果进行相应处理,最后关闭文件。

注意事项

  • 文件打开模式:使用fprintf写入文件时,文件需以写模式(如"w""w+"等)打开;使用fscanf读取文件时,文件需以读模式(如"r""r+"等)打开。

  • 格式匹配fprintffscanfformat里的格式说明符要与实际数据类型严格匹配,否则可能导致写入或读取的数据不正确,甚至程序崩溃。

  • 缓冲区影响:文件流存在缓冲区机制,读写操作可能不会立即反映到文件中,在合适的时候(如关闭文件前)可以使用fflush函数刷新缓冲区,确保数据正确写入文件。

  • 错误处理:要根据函数的返回值及时判断操作是否成功,并进行相应的错误处理,以增强程序的健壮性。

5. fread函数和fwrite函数

在 C 语言中,fread 和 fwrite 是用于二进制文件读写的核心函数,支持对任意数据类型(如结构体、数组等)进行块级读写,比字符 / 字符串级函数(如 fputc/fgets)更高效,尤其适合处理二进制数据(如图片、音频、自定义结构体等)。

fwrite 函数

向文件流写入二进制数据

函数原型

#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

参数说明

  • ptr:指向要写入数据的内存地址(源数据的起始地址)。

  • size单个数据单元的字节大小(如 sizeof(int) 表示每个 int 占 4 字节)。

  • count:要写入的数据单元数量(如写入 3 个 int,则 count=3)。

  • stream:目标文件流(需以二进制模式打开,如 "wb"/"ab" 等)。

返回值

  • 成功:返回实际写入的数据单元数量(小于等于 count)。

  • 失败 / 写入不完整:返回值小于 count(需结合 ferror 判断错误)。

特点与用法

  1. 二进制写入:直接按内存中的原始字节写入,不进行任何格式转换(如不转换换行符),适合非文本数据。

  2. 批量操作:一次可写入多个数据单元(如数组、结构体数组),效率高。

示例 1:写入单个结构体

#include <stdio.h>

// 定义一个结构体
typedef struct {
    int id;
    char name[20];
    float score;
} Student;

int main() {
    FILE *fp = fopen("students.dat", "wb");  // 二进制写模式
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }

    Student s = {101, "ZhangSan", 95.5f};
    // 写入1个Student类型数据(每个占sizeof(Student)字节)
    size_t written = fwrite(&s, sizeof(Student), 1, fp);
    if (written != 1) {
        perror("写入失败");
    }

    fclose(fp);
    return 0;
}

示例 2:写入数组

// 写入一个int数组
int nums[] = {1, 2, 3, 4, 5};
int len = sizeof(nums) / sizeof(nums[0]);
// 写入5个int(每个4字节)
fwrite(nums, sizeof(int), len, fp);

fread 函数

从文件流读取二进制数据

函数原型

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

参数说明

  • ptr:指向用于存储读取数据的内存地址(目标缓冲区)。

  • size:单个数据单元的字节大小(同 fwrite)。

  • count:计划读取的数据单元数量。

  • stream:源文件流(需以二进制读模式打开,如 "rb"/"rb+" 等)。

返回值

  • 成功:返回实际读取的数据单元数量(等于 count 表示读满,小于 count 可能是文件结束或出错)。

  • 失败:返回 0(需结合 feof/ferror 判断是文件结束还是错误)。

特点与用法

  1. 二进制读取:按原始字节读取,与写入时的内存布局完全一致,确保数据完整还原。

  2. 精准还原:可直接读取结构体、数组等复杂类型,无需手动解析格式。

示例:读取之前写入的结构体

#include <stdio.h>

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

int main() {
    FILE *fp = fopen("students.dat", "rb");  // 二进制读模式
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }

    Student s;
    // 读取1个Student类型数据
    size_t read = fread(&s, sizeof(Student), 1, fp);
    if (read == 1) {
        // 成功读取,打印数据
        printf("ID: %d, Name: %s, Score: %.1f\n", s.id, s.name, s.score);
    } else if (feof(fp)) {
        printf("已到达文件末尾\n");
    } else if (ferror(fp)) {
        perror("读取失败");
    }

    fclose(fp);
    return 0;
}

三、关键注意事项

  1. 必须用二进制模式打开文件
    读写时需指定 b 模式(如 "wb"/"rb"),否则在 Windows 系统中可能因换行符转换破坏二进制数据。

  2. 数据一致性
    写入和读取时的 size 和数据类型必须完全一致(如写入 sizeof(Student),读取也必须用 sizeof(Student)),否则会导致数据解析错误。

  3. 返回值判断

    • 不能仅通过返回值是否为 0 判断失败(可能是文件结束),需用 feof 区分 “文件结束” 和 “读取错误”。

    • 示例:
      size_t n = fread(buf, size, count, fp);
      if (n < count) {
          if (feof(fp)) {
              // 正常结束:已读到文件末尾
          } else if (ferror(fp)) {
              // 读取错误
          }
      }
      

  4. 跨平台兼容性
    结构体在不同编译器 / 平台可能有字节对齐差异,如需跨平台传输,需手动处理对齐(如用 #pragma pack)。

  5. 与文本函数的区别

    • fwrite/fread 适合二进制数据(图片、音频、结构体等),按字节精准操作。

    • fprintf/fscanf 适合文本数据,会进行格式转换(如整数转字符串)。