标准IO---核心函数接口延续(嵌入式Linux)

继上次讲解标准IO的核心函数接口后,先为大家再补充两个对于二进制文件的读写函数接口,如下:

1、核心函数接口

1.1 fwrite

1.1.1 函数原型

cpp 复制代码
#include <stdio.h>  // 必须包含的头文件
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

1.1.2 功能

二进制形式将内存中的数据批量写入指定文件流,不做任何格式转换,是处理二进制文件(如结构体、图片、音频)的核心函数。

关键: fwrite直接读写内存字节,与数据类型无关,比文本写入(fprintf)更高效、更精准。

1.1.3 参数

参数名 数据类型 含义
ptr const void * 待写入数据的内存起始地址(可以是数组、结构体、普通变量的地址)
size size_t 单个数据块的字节大小 (如sizeof(int)sizeof(Student)
nmemb size_t 要写入的数据块个数
stream FILE * 目标文件流指针(需通过fopen以二进制模式打开)

1.1.4 返回值

size_t:

成功:返回实际写入的完整数据块个数 (等于nmemb

失败:返回小于nmemb的值(可通过ferror(stream)检查错误)

注意:

总写入字节数:成功时总字节数 = size * nmemb

1.2 fread---二进制批量读取文件流

1.2.1 函数原型

cpp 复制代码
#include <stdio.h>  // 必须包含的头文件
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

1.2.2 功能说明

二进制形式 从指定文件流中批量读取数据到内存,还原为原数据类型(如结构体、数组),是fwrite的配套读取函数。

1.2.3 参数

参数名 数据类型 含义
ptr void * 接收数据的内存起始地址(需提前分配足够空间,如数组、结构体变量)
size size_t 单个数据块的字节大小 (需与写入时的size一致)
nmemb size_t 要读取的数据块个数
stream FILE * 源文件流指针(需通过fopen以二进制模式打开)

1.2.4 返回值

size_t:

成功:返回实际读取的完整数据块个数 (≤ nmemb)读到文件末尾:返回 0

失败:返回小于nmemb的值(可通过ferror(stream)/feof(stream)区分是错误还是到末尾)

1.3 fseek---调整文件指针到指定位置

1.3.1 函数原型

cpp 复制代码
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);

1.3.2 功能

将指定文件流的文件指针移动到「基准位置 + 偏移量」的位置,支持向前 / 向后跳转,是随机读写的核心函数。

1.3.3 参数

参数名 数据类型 含义
stream FILE * 目标文件流指针
offset long 偏移量(字节数):正数 = 向后跳,负数 = 向前跳,0 = 不跳转
whence int 基准位置(系统宏定义): • SEEK_SET:文件开头(0) • SEEK_CUR:当前指针位置(1) • SEEK_END:文件末尾(2)

1.3.4 返回值

返回值 含义
0 成功
-1 失败(如偏移量越界)**注意:**其他都正确的情况下,超出文件大小仍是成功的

注意:

a模式限制:a/a+模式下,fseek调整指针后,写入操作仍会强制跳回文件末尾(仅读取受fseek影响)

1.4 ftell---获取当前文件指针位置(偏移量)

1.4.1 函数原型

cpp 复制代码
#include <stdio.h>
long ftell(FILE *stream);

1.4.2 功能

返回指定文件流的偏移量(当前文件指针位置) (相对于文件开头的字节数),常配合fseek使用(如计算文件大小)。

1.4.3 参数

参数名 数据类型 含义
stream FILE * 目标文件流指针

1.4.4 返回值

返回值类型 含义
long 成功:返回指定文件流的偏移量;失败:返回-1L

1.5 rewind - 重置文件指针到开头

1.5.1 函数原型

cpp 复制代码
#include <stdio.h>
void rewind(FILE *stream);

1.5.2 功能

将文件指针重置到文件开头 ,等价于 fseek(stream, 0, SEEK_SET),但无返回值,更简洁。

1.5.3 参数

参数名 数据类型 含义
stream FILE * 目标文件流指针

**特点:**无返回值,调用即生效

1.6 feof --- 判断是否读到文件末尾

1.6.1 函数原型

cpp 复制代码
#include <stdio.h>
int feof(FILE *stream);

1.6.2 功能

检测指定文件流的文件末尾标志(EOF),判断是否已经读取到文件的最后一个字节之后(即 "读完了")。

关键: feof 不是 "预判" 文件末尾,而是读取操作失败后,判断失败原因是否是 "到了末尾"。

示例:

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

int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) { perror("fopen"); exit(1); }

    // 错误写法:先判断feof,会多读取一次(最后一次fgetc返回EOF后,feof(fp)才为非0)
    // while (!feof(fp)) { printf("%c", fgetc(fp)); }

    // 正确写法:先读取,再判断是否失败,最后用feof找原因
    int ch;
    while ((ch = fgetc(fp)) != EOF) {  // 先读取
        printf("%c", ch);
    }
    // 读取失败后,判断原因
    if (feof(fp)) {
        printf("\n✅ 正常:已读取到文件末尾\n");
    } else if (ferror(fp)) {
        printf("\n❌ 错误:读取过程中发生错误\n");
    }

    fclose(fp);
    return 0;
}

1.6.3 参数

参数名 数据类型 含义
stream FILE * 目标文件流指针

1.6.4 返回值

返回值 含义
非 0 值 已到达文件末尾(EOF 标志被置位)
0 未到达文件末尾

1.6 ferror --- 判断文件流是否发生错误

1.6.1 函数原型

cpp 复制代码
#include <stdio.h>
int ferror(FILE *stream);

1.6.2 功能

检测指定文件流的错误标志,判断读写操作(如 fputc/fread)是否因硬件 / 权限等问题失败(如读只读文件、写无权限文件)。

1.6.3 参数

参数名 数据类型 含义
stream FILE * 目标文件流指针

1.6.4 返回值

返回值 含义
非 0 值 文件流发生错误(错误标志被置位)
0 文件流无错误

标志清除: 需调用 clearerr(stream) 手动清除错误标志,否则会一直返回非 0。

clearerr(fp) 只做两件事:

  1. 清除文件指针 fp结束标志位(EOF)
  2. 清除文件指针 fp错误标志位(error) ;它不会改变文件指针的位置,也不会影响文件本身,只是重置 fp 内部的两个状态标记。
cpp 复制代码
// 必须先清标志 + 重置文件指针,才能重新读
clearerr(fp);   // 清除EOF/error标志
fseek(fp, 0, SEEK_SET); // 回到文件开头

1.7 fflush --- 手动刷新文件流缓存

1.7.1 函数原型

cpp 复制代码
#include <stdio.h>
int fflush(FILE *stream);

1.7.2 功能

强制将文件流缓存区中未写入的内容刷新到目标设备(文件 / 终端),突破 "缓存满 / 换行 / 程序退出" 的默认刷新规则。

**核心场景:**实时写入(如日志、监控数据)

1.7.3 参数

参数名 数据类型 含义
stream FILE * 目标文件流指针;传入NULL时,刷新所有打开的文件流缓存

1.7.4 返回值

返回值 含义
0 刷新成功
EOF 刷新失败

适用场景:

  • 行缓存:未到\n但需要实时输出(如进度条);
  • 全缓存:文件写入后需要立即落盘(避免程序崩溃丢失数据);

fclose会自动调用fflush:关闭文件前会刷新缓存,无需手动调用。

1.8 setvbuf --- 自定义文件流缓存策略

1.8.1 函数原型

cpp 复制代码
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

1.8.2 功能

在打开文件后、读写操作前,自定义文件流的缓存类型、缓存区地址、缓存区大小,替代系统默认的缓存策略(行缓存 / 全缓存 / 无缓存)。

1.8.3 参数

参数名 数据类型 含义
stream FILE * 目标文件流指针(必须是已打开但未读写的流)
buf char * 自定义缓存区地址:・非 NULL:使用用户指定的缓存区(需提前分配)・NULL:由系统自动分配缓存区
mode int 缓存模式(系统宏定义): • _IOFBF:全缓存(默认文件流) • _IOLBF:行缓存(默认终端流) • _IONBF:无缓存(默认 stderr)
size size_t 缓存区大小(字节):buf非 NULL 时有效,建议设为 4096/8192 等块大小倍数

1.8.4 返回值

返回值 含义
0 设置成功
非 0 设置失败

1.8.5 核心特性

  1. 调用时机:必须在fopen之后、第一次读写操作之前调用,否则无效;
  2. 缓存区生命周期:用户自定义的buf需保证在文件流关闭前有效(避免栈溢出);
  3. 覆盖默认策略:比如将终端流(默认行缓存)改为全缓存,或文件流改为无缓存。
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

#define BUF_SIZE 8192  // 自定义缓存大小

int main() {
    FILE *fp = fopen("test.txt", "w");
    if (fp == NULL) { perror("fopen"); exit(1); }

    // 分配自定义缓存区(堆内存,避免栈溢出)
    char *my_buf = (char *)malloc(BUF_SIZE);
    if (my_buf == NULL) { perror("malloc"); fclose(fp); exit(1); }

    // 设置为全缓存,使用自定义缓存区
    int ret = setvbuf(fp, my_buf, _IOFBF, BUF_SIZE);
    if (ret != 0) {
        printf("setvbuf failed\n");
        free(my_buf);
        fclose(fp);
        exit(1);
    }

    // 后续读写使用自定义缓存
    fputs("自定义缓存测试\n", fp);
    fflush(fp);  // 手动刷新

    // 关闭后释放缓存区
    fclose(fp);
    free(my_buf);
    return 0;
}

fflush vs fclose 刷新的区别

操作 刷新效果 后续操作
fflush(fp) 仅刷新缓冲区,不关闭文件 可继续读写该文件流
fclose(fp) 刷新缓冲区 + 关闭文件 不可再操作该文件流(已失效)

1.9 clearerr --- 清除文件流标志

feof/ferror 的标志被置位后不会自动清除,需用clearerr重置:

cpp 复制代码
#include <stdio.h>
void clearerr(FILE *stream);

**功能:**清除文件流的 EOF 标志和错误标志,恢复初始状态。

错误判断

  • feof:读取失败后,判断是否是 "读到末尾";
  • ferror:读取失败后,判断是否是 "操作出错";
  • 核心逻辑:先判断读写是否失败(如fgetc==EOF),再用这两个函数找原因。

到此,标准IO的内容就到这里了,我们下次再见!

相关推荐
LYOBOYI1235 小时前
vscode界面美化
ide·vscode·编辑器
历程里程碑5 小时前
Linux20 : IO
linux·c语言·开发语言·数据结构·c++·算法
Doro再努力6 小时前
【Linux操作系统12】Git版本控制与GDB调试:从入门到实践
linux·运维·服务器·git·vim
智者知已应修善业7 小时前
【洛谷P9975奶牛被病毒传染最少数量推导,导出多样例】2025-2-26
c语言·c++·经验分享·笔记·算法·推荐算法
jllllyuz8 小时前
针对酒店KTV声控DMX512灯光系统的节目选择与实现
嵌入式
小龙报9 小时前
【51单片机】从 0 到 1 玩转 51 蜂鸣器:分清有源无源,轻松驱动它奏响新年旋律
c语言·数据结构·c++·stm32·单片机·嵌入式硬件·51单片机
小武编程9 小时前
基于JL700N可视化SDK的MAC地址应用
c语言·tws耳机·杰理jl700n
mailangduoduo9 小时前
零基础教学连接远程服务器部署项目——VScode版本
服务器·pytorch·vscode·深度学习·ssh·gpu算力
石去皿9 小时前
【嵌入式就业6】计算机组成原理与操作系统核心机制:夯实底层基础
c++·面试·嵌入式