当前位置: 首页 > news >正文

C语言 ——— 文件操作的核心概念与函数使用

目录

文件指针

核心本质

文件的打开和关闭

关键:打开模式(mode)

使用步骤(必做)

注意事项

相对路径和绝对路径

一、相对路径:依赖当前程序的工作目录

1. 最简单的相对路径:直接写文件名

2. 带层级的相对路径:通过 . 和 .. 表示目录关系

二、绝对路径:从根目录开始的完整路径

补充:路径中的特殊符号说明

文件的顺序读写

fputc 函数

fgetc 函数

fputs 函数

fgets 函数

fprintf 函数

fscanf 函数

fwrite 函数

fread 函数

文件的随机读写

fseek 函数

ftell 函数

rewind 函数

文件读取结束的判定 - feof

1. 函数原型

2. 参数解析

3. 返回值

二、核心作用:区分 “文件结束” 与 “读写错误”

三、常见误区:不要用 feof 作为循环条件

四、正确用法示例


文件指针

核心本质

FILE是标准库定义的一个结构体(具体实现因编译器 / 系统而异),内部封装了文件操作所需的关键信息:

  • 文件描述符(底层操作系统标识文件的整数);
  • 缓冲区(用于减少磁盘 I/O 次数的内存区域);
  • 文件位置指针(记录当前读写位置);
  • 错误标志、文件打开模式(如 "r"、"wb")等元信息。

FILE*指针本身不直接指向物理文件,而是通过指向这个FILE结构体,间接关联到具体的文件,成为所有文件操作的 “桥梁”。

文件的打开和关闭

fopen 是 C 语言标准库(stdio.h)中用于打开文件的核心函数,作用是建立程序与文件之间的连接,返回一个 FILE* 指针(文件指针)供后续读写操作使用。

FILE* fopen(const char* filename, const char* mode);
  • 参数 1(filename):字符串,指定要打开的文件路径(可以是相对路径或绝对路径,如 "test.txt" 或 "/home/user/data.dat")。
  • 参数 2(mode):字符串,指定文件的打开模式(决定读写权限、文件不存在时的行为等)。
  • 返回值:成功打开时返回指向 FILE 结构体的指针(FILE*);失败时返回 NULL(如文件不存在且模式不允许创建、权限不足等)。

关键:打开模式(mode)

mode 决定了文件的操作权限,常用模式如下:

模式含义
"r"只读打开文本文件。文件必须存在,否则打开失败。
"w"只写打开文本文件。文件不存在则创建;文件存在则清空原有内容。
"a"追加打开文本文件。文件不存在则创建;写入时数据追加到文件末尾(不覆盖原有内容)。
"r+"读写打开文本文件。文件必须存在,可读写原有内容。
"w+"读写打开文本文件。文件不存在则创建;存在则清空,可读写。
"a+"读写打开文本文件。文件不存在则创建;读时可访问全部内容,写时只能追加。
  • 若操作二进制文件(如图片、音频),需在模式后加 "b"(如 "rb""wb+"),避免系统对换行符等文本特殊字符的自动转换。

使用步骤(必做)

  1. 调用 fopen 打开文件,传入文件名和模式;
  2. 检查返回的 FILE* 指针是否为 NULL(打开失败的处理,如打印错误信息);
  3. 通过返回的指针进行文件操作(如 fprintf 写入、fscanf 读取等);
  4. 操作完成后用 fclose 关闭文件(释放资源,确保数据刷新到磁盘)。
#include <stdio.h>int main() {// 打开文件(以只写模式打开文本文件,不存在则创建)FILE* fp = fopen("test.txt", "w");// 检查打开是否成功(关键!)if (fp == NULL) {perror("fopen failed");  // 打印错误原因(如权限不足)return 1;}// 写入内容fprintf(fp, "Hello, fopen!");// 关闭文件(必须!)fclose(fp);fp = NULL;return 0;
}

注意事项

  • 错误检查不可少fopen 失败时返回 NULL,必须判断,否则后续操作会导致程序崩溃。
  • 及时关闭文件fclose(fp) 不仅释放资源,还会将缓冲区数据强制刷新到磁盘(避免数据丢失)。
  • 模式匹配需求:根据实际读写需求选择模式(如不想覆盖原有内容则用 "a" 而非 "w")。
  • 路径格式:不同系统路径分隔符不同(Linux 用 /,Windows 用 \ 或 /),注意兼容性。

相对路径和绝对路径

一、相对路径:依赖当前程序的工作目录

相对路径是以程序运行时的 “当前工作目录” 为起点的路径,无需从根目录开始描述,仅通过层级关系(如子目录、当前目录、上一级目录)即可定位文件,特点是简洁但依赖当前位置。

1. 最简单的相对路径:直接写文件名
FILE* fp = fopen("test.txt", "w");
  • 它表示 “在程序当前的工作目录下” 查找或创建 test.txt 文件;
  • “当前工作目录” 通常是程序可执行文件(.exe)所在的目录,或运行程序时的命令行所在目录(具体取决于编译环境和运行方式)。
2. 带层级的相对路径:通过 . 和 .. 表示目录关系
FILE* fp = fopen(".\\Debug\\test.txt", "w");
  • ".\\" 表示 “当前目录”(与程序工作目录相同);
  • Debug 是当前目录下的子目录;
  • 整体路径表示 “当前目录 → Debug 子目录 → test.txt 文件”。
  • 若要定位 “上一级目录” 中的文件,可使用 ..\\(如 ..\\test.txt 表示 “当前目录的上一级目录下的 test.txt”)。

二、绝对路径:从根目录开始的完整路径

绝对路径是从文件系统的 “根目录” 开始的完整路径,完整描述了文件的精确位置,不依赖程序的当前工作目录,无论程序在哪里运行,都能准确定位文件,但路径较长且跨系统(如 Windows 和 Linux)格式有差异。

FILE* fp = fopen("C:\\Users\\32937\\Desktop\\test.txt", "w");
  • 从根目录 C: 开始,依次经过 Users 目录 → 32937 目录 → Desktop 目录,最终定位到 test.txt
  • 路径中的 \\ 是 C 语言中的特殊写法:单个 \ 在 C 中会被视为 “转义字符的开始”(如 \n 表示换行),因此需要用 \\ 表示一个实际的反斜杠,避免解析错误。

补充:路径中的特殊符号说明

  • \\(Windows)或 /(Linux/macOS):用于分隔目录层级(Windows 习惯用 \,但 C 语言中需写为 \\;Linux 统一用 /,无需转义);
  • .:表示当前目录;
  • ..:表示上一级目录。

文件的顺序读写

函数原型功能描述参数说明返回值备注
int fputc(int ch, FILE* stream)向文件写入一个字符ch:要写入的字符(ASCII 码)- stream:文件指针(如fp成功:返回写入的字符(ch)失败:返回EOF(-1)顺序写入,每次写一个字符,指针自动后移
int fgetc(FILE* stream)从文件读取一个字符stream:文件指针成功:返回读取的字符(ASCII 码)失败 / 文件尾:返回EOF(-1)顺序读取,每次读一个字符,指针自动后移;读取结束需用feof判断是否到文件尾
int fputs(const char* str, FILE* stream)向文件写入一个字符串str:要写入的字符串(以'\0'结尾)- stream:文件指针成功:返回非负数失败:返回EOF(-1)不自动写入换行符,需手动加'\n''\0'不写入文件
char* fgets(char* str, int n, FILE* stream)从文件读取一个字符串(最多 n-1 个字符)str:存储读取结果的缓冲区- n:最大读取字符数(含'\0')- stream:文件指针成功:返回str(缓冲区地址)失败 / 文件尾:返回NULL读取到换行符'\n'或达到 n-1 个字符时停止,自动在末尾加'\0';换行符会被读入
int fprintf(FILE* stream, const char* format, ...)按格式化字符串向文件写入数据stream:文件指针- format:格式化字符串(如"%d %s")- ...:可变参数(要写入的数据)成功:返回写入的字符总数失败:返回负数printf用法类似,只是输出目标是文件而非屏幕
int fscanf(FILE* stream, const char* format, ...)按格式化字符串从文件读取数据stream:文件指针- format:格式化字符串- ...:可变参数(存储读取结果的地址)成功:返回成功匹配并赋值的参数个数失败 / 文件尾:返回EOFscanf用法类似,只是输入来源是文件而非键盘;跳过空白字符(空格、换行等)
size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream)以二进制形式向文件写入数据ptr:要写入数据的起始地址- size:单个元素的大小(字节)- count:元素个数- stream:文件指针成功:返回实际写入的元素个数失败:返回值小于count(可能为 0)适合写入二进制数据(如结构体、数组);不处理格式,直接写内存二进制内容
size_t fread(void* ptr, size_t size, size_t count, FILE* stream)以二进制形式从文件读取数据ptr:存储读取结果的缓冲区地址- size:单个元素的大小(字节)- count:要读取的元素个数- stream:文件指针成功:返回实际读取的元素个数失败 / 文件尾:返回值小于count(可能为 0)适合读取二进制数据;读取结束需用feof判断是否到文件尾

fputc 函数

int main()
{// 创建FILE* fp = fopen("C:\\Users\\32937\\Desktop\\test.txt", "w");if (fp == NULL) {perror("fopen failed");  return 1;}// 写入char arr[] = "abcdefg";int len = strlen(arr);for (int i = 0; i < len; i++){if (fputc(arr[i], fp) == EOF)perror("fputc");}// 释放fclose(fp);fp = NULL;return 0;
}

fgetc 函数

int main()
{FILE* fp = fopen("C:\\Users\\32937\\Desktop\\test.txt", "r");if (fp == NULL){perror("fopen failed");return 1;}while(fgetc(fp) != EOF){printf("%c\n", fgetc(fp));}fclose(fp);fp = NULL;return 0;
}

fputs 函数

int main()
{FILE* fp = fopen("C:\\Users\\32937\\Desktop\\test.txt", "w");if (fp == NULL) {perror("fopen failed");  return 1;}if (fputs("abcdef", fp) == EOF){perror("fputs");return -1;}fclose(fp);fp = NULL;return 0;
}

fgets 函数

int main()
{FILE* fp = fopen("C:\\Users\\32937\\Desktop\\test.txt", "r");if (fp == NULL) {perror("fopen failed");  return 1;}char arr[10] = "";if (fgets(arr, 7, fp) == NULL){perror("fputs");return -1;}printf("%s\n", arr);fclose(fp);fp = NULL;return 0;
}

fprintf 函数

struct S
{int n;float f;
};int main()
{FILE* fp = fopen("C:\\Users\\32937\\Desktop\\test.txt", "w");if (fp == NULL){perror("fopen failed");return 1;}struct S s = { 5,3.14 };fprintf(fp, "%d %f", s.n, s.f);fclose(fp);fp = NULL;return 0;
}

fscanf 函数

struct S
{int n;float f;
};int main()
{FILE* fp = fopen("C:\\Users\\32937\\Desktop\\test.txt", "r");if (fp == NULL){perror("fopen failed");return 1;}struct S s = { 0 };fscanf(fp, "%d %f", &(s.n), &(s.f));printf("%d %f\n", s.n, s.f);fclose(fp);fp = NULL;return 0;
}

fwrite 函数

struct S
{int n;float f;
};int main()
{FILE* fp = fopen("C:\\Users\\32937\\Desktop\\test.txt", "wb");if (fp == NULL){perror("fopen");return -1;}struct S s = { 100,3.14f };fwrite(&s, sizeof(struct S), 1, fp);fclose(fp);fp = NULL;return 0;
}

fread 函数

struct S
{int n;float f;
};int main()
{FILE* fp = fopen("C:\\Users\\32937\\Desktop\\test.txt", "rb");if (fp == NULL){perror("fopen");return -1;}struct S s = { 0 };fread(&s, sizeof(struct S), 1, fp);printf("%d %f\n", s.n, s.f);fclose(fp);fp = NULL;return 0;
}

文件的随机读写

函数原型功能描述参数说明返回值备注
int fseek(FILE* stream, long offset, int origin)按指定 “起始位置” 和 “偏移量” 调整文件指针位置stream:文件指针(需先通过fopen打开)- offset:指针偏移量(可正可负,正数向后移,负数向前移)- origin:起始位置(宏定义),可选值:- SEEK_SET:文件开头(偏移量从 0 开始)- SEEK_CUR:当前文件指针位置- SEEK_END:文件末尾成功:返回 0失败:返回 非0(如偏移量超出文件范围、文件不可读 / 写等)随机读写的核心函数,调整指针后,后续fread/fwrite等会从新位置开始操作;二进制文件推荐使用,文本文件因换行符(\n)可能被转换(如 Windows 下\n存为\r\n),偏移量计算易出错
long ftell(FILE* stream)获取当前文件指针相对于 “文件开头” 的偏移量(字节数)stream:文件指针成功:返回当前偏移量(long类型,正数)失败:返回 -1LL表示长整型)常与fseek配合使用,例如:用fseek(stream, 0, SEEK_END)将指针移到文件尾,再用ftell获取偏移量,即可计算文件总大小
void rewind(FILE* stream)将文件指针重置到 “文件开头” 位置stream:文件指针无返回值功能等价于 fseek(stream, 0, SEEK_SET),但代码更简洁;重置指针的同时,会清除文件的错误标志(ferror状态)
int fsetpos(FILE* stream, const fpos_t* pos)fpos_t类型的位置值,设置文件指针位置stream:文件指针- pos:指向fpos_t类型变量的指针(该变量存储了通过fgetpos获取的位置信息)成功:返回 0失败:返回 非0C 标准推荐的函数,专为处理 “大文件” 设计(fpos_t是平台相关的类型,可存储比long更大的偏移量);需与fgetpos配合使用
int fgetpos(FILE* stream, fpos_t* pos)获取当前文件指针位置,存储到fpos_t类型变量中stream:文件指针- pos:指向fpos_t类型变量的指针(用于接收位置信息)成功:返回 0失败:返回 非0fsetpos配对,解决ftelllong类型偏移量不足的问题(如 64 位文件);fpos_t的具体实现由编译器决定,无需手动修改其值

fseek 函数

FILE* fp = fopen("data.bin", "rb");
if (fp == NULL) 
{// 打开失败处理
}// 从文件开头(SEEK_SET)向后移动100字节(offset=100)
// 100字节 = 25个int(每个4字节),即定位到第25个int的起始位置
int ret = fseek(fp, 100, SEEK_SET);
if (ret != 0) 
{// 定位失败处理(如文件不足100字节)
}int num;
// 从当前指针位置(100字节处)读取1个int
fread(&num, sizeof(int), 1, fp);  // 读取的是第25个int数据

ftell 函数

#include <stdio.h>int main() {FILE* fp = fopen("data.bin", "rb");  // 打开二进制文件(只读)if (fp == NULL) {printf("文件打开失败\n");return 1;}// 步骤1:将文件指针移到文件末尾(SEEK_END表示以文件尾为基准,偏移0字节)fseek(fp, 0, SEEK_END);// 步骤2:获取当前指针偏移量(即文件总字节数)long file_size = ftell(fp);if (file_size == -1L) {printf("获取文件大小失败\n");fclose(fp);return 1;}printf("文件总大小:%ld 字节\n", file_size);  // 例如:输出 "文件总大小:1024 字节"fclose(fp);return 0;
}

rewind 函数

#include <stdio.h>
#include <string.h>int main() {FILE* fp = fopen("test.txt", "r");  // 打开文本文件(只读)if (fp == NULL) {printf("文件打开失败\n");return 1;}// 第一次读取:读取前5个字符char buf1[6] = {0};  // 预留'\0'位置fread(buf1, 1, 5, fp);  // 读取后,指针移到第5字节处printf("第一次读取(前5字符):%s\n", buf1);  // 输出:Hello// 检查当前指针位置(验证已偏离开头)long pos = ftell(fp);printf("读取后指针位置:%ld 字节\n", pos);  // 输出:5(假设每个字符占1字节)// 使用rewind重置指针到文件开头rewind(fp);printf("rewind后,指针位置:%ld 字节\n", ftell(fp));  // 输出:0(已回到开头)// 第二次读取:从开头读取完整内容char buf2[100] = {0};fread(buf2, 1, sizeof(buf2)-1, fp);printf("第二次读取(完整内容):%s\n", buf2);  // 输出:Hello, rewind function!fclose(fp);return 0;
}

文件读取结束的判定 - feof

feof 是 C 语言中用于检测文件结束标志的函数,其核心作用是判断 “文件指针是否已经到达文件末尾”,帮助区分 “因文件结束导致的读写终止” 和 “因错误导致的读写终止”。它并非用于直接判断 “是否还能读写数据”,而是用于解释 “之前的读写操作失败的原因”,这是理解 feof 的关键。

1. 函数原型
int feof(FILE* stream);
2. 参数解析
  • FILE* stream:文件指针(需通过 fopen 打开文件获得,如 fp),指定要检测的文件。
3. 返回值
  • 若文件指针已到达文件末尾(且之前的读写操作因 “到尾” 而停止),返回非零值(通常为 1,代表 “真”);
  • 若文件指针未到达末尾,返回0(代表 “假”)

二、核心作用:区分 “文件结束” 与 “读写错误”

文件读写操作(如 freadfgetc)失败时,可能有两种原因:

  1. 正常原因:文件指针已到达末尾,没有更多数据可读写;
  2. 异常原因:发生 I/O 错误(如文件损坏、权限不足、磁盘错误等)。

feof 的核心价值就是判断失败是否因 “文件结束” 导致—— 若 feof 返回非零,说明是正常到尾;若返回 0,则需用 ferror 函数检测是否有错误(ferror 返回非零表示有错误)。

三、常见误区:不要用 feof 作为循环条件

很多初学者会错误地将 feof 作为循环条件(如 while(!feof(fp)) { ... }),这是因为对 feof 的触发时机理解有误:

  • feof 的 “文件结束标志”不会在指针刚到达末尾时立即设置,而是在 “指针到达末尾后,再尝试进行一次读写操作” 才会设置。
  • 例如:文件最后一个字符被读取后,feof 仍返回 0;当再次尝试读取(此时已无数据),feof 才会返回非零。

因此,直接用 feof 控制循环会导致 “多执行一次读写操作”(读取到无效数据)。

四、正确用法示例

feof 的正确使用方式是:先执行读写操作,检查其返回值是否表示失败,再用 feof 判断失败是否因 “文件结束” 导致

代码演示:

#include <stdio.h>int main() {FILE* fp = fopen("test.txt", "r");if (fp == NULL) {printf("文件打开失败\n");return 1;}int c;  // 用int接收fgetc返回值(需容纳EOF)// 正确逻辑:先读取,再判断是否为EOFwhile ((c = fgetc(fp)) != EOF) {putchar(c);  // 输出读取到的字符}// 读写结束后,用feof判断原因if (feof(fp)) {printf("\n读取结束:已到达文件末尾\n");  // 正常结束} else {printf("\n读取失败:发生I/O错误\n");      // 异常错误(可用ferror进一步确认)}fclose(fp);return 0;
}
http://www.dtcms.com/a/466699.html

相关文章:

  • 做网站维护的人叫啥电商网站维护费用
  • 高频面试题解析:算法到数据库全攻略
  • c# 使用yolov5模型
  • 表白网站制作代码公司邮箱如何申请
  • 厦门最早做网站的公司二字顺口名字公司
  • 【GIT】错误集锦及解决方案
  • C语言⽂件操作讲解(1~2)
  • 前端代码CR小知识点汇总
  • 企业建站免费模板wordpress访问统计
  • 乐清建站公司做网站哪里最便宜
  • 什么是 Spring AI?Spring AI 入门教程
  • 在线课程网站开发的研究意义客户管理系统免费版
  • 台州宇洋台州网站建设手机在线视频
  • uniapp开发 APP嵌入另一个APP打包的wgt文件,实现点击携带参数跳转到wgtAPP的某一个页面
  • VS2010做网站登录页面步骤可以免费浏览的网站
  • DeepSeek Sparse Attention(DSA)快速洞察(DeepSeek-V3.2)
  • 山西建设银行官方网站wordpress 文章投票插件
  • C++ 模拟 力扣1576. 替换所有的问号 题解 每日一题
  • 安全联盟网站认证网络营销的认识
  • 基于SpringBoot+Vue的少儿编程培训机构管理系(WebSocket及时通讯、协同过滤算法、Echarts图形化分析)
  • 时序数据库promQL
  • 网站安全检测可以监测哪些内容风险信息宜春网站开发
  • 网站建设中企动力强成都那家网站建设好
  • RK3588 linux在uboot关机模式下待熄屏休眠后拔插适配器无反应屏幕也不会亮
  • 建设厅网站关于建筑资质合并wordpress速度很慢
  • 做网站的叫什么软件上海阿里巴巴做网站
  • Redis的Hash解析
  • 旅游业网站开发建设毕设做微课资源网站设计可以吗
  • 设计公司网站什么重要杭州工业设计
  • 【北京迅为】iTOP-4412精英版使用手册-第三十五章 WEB控制LED