news 2026/4/21 4:14:52

C语言笔记归纳20:文件操作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言笔记归纳20:文件操作

文件操作

目录

文件操作

1. 🌟 为什么需要文件操作?(内存数据的 “永久存储” 方案)

2. 📌 什么是文件?(程序文件 vs 数据文件)

2.1 程序文件

2.2 数据文件

文件名(文件的 “唯一标识”)

3. 📊 文本文件 vs 二进制文件(数据存储的两种形式)

3.1 核心定义

3.2 直观示例:整数10000的存储对比

3.3 代码验证(文本 vs 二进制存储)

4. 🚪 文件的打开与关闭(核心操作:fopen/fclose)

4.1 流与标准流(默认打开的 3 个流)

4.2 文件指针(FILE*:文件的 “身份证”)

4.3 文件打开方式详解(表格汇总)

4.4 打开与关闭文件的正确示例

易错点标注:

5. 📝 文件的顺序读写(8 个核心函数 + 对比)

5.1 字符读写:fputc/fgetc(单个字符操作)

fputc:字符输出函数(写字符)

fgetc:字符输入函数(读字符)

5.2 行读写:fputs/fgets(整行数据操作)

fputs:文本行输出函数(写整行)

fgets:文本行输入函数(读整行)

易错点:

5.3 格式化读写:fprintf/fscanf(结构体 / 多类型数据)

fprintf:格式化输出函数(写多类型数据)

fscanf:格式化输入函数(读多类型数据)

易错点:

5.4 二进制读写:fwrite/fread(高效存储)

fwrite:二进制输出函数(写二进制数据)

fread:二进制输入函数(读二进制数据)

5.5 拓展:三组格式化函数对比

实战示例:sscanf/sprintf

6. 🔀 文件的随机读写(灵活定位:fseek/ftell/rewind)

6.1 fseek:定位文件指针

实战示例:定位读取文件内容

6.2 ftell:获取文件指针偏移量

6.3 rewind:重置文件指针到开头

7. ❗ 文件读取结果判定(feof/ferror:区分 “文件尾” 和 “读错误”)

7.1 feof:判断是否读到文件尾

7.2 ferror:判断是否发生读错误

实战示例:正确判定读取结束原因

异常情况测试(故意出错):

8. 📦 文件缓冲区(为什么数据没立刻写到硬盘?)

缓冲区的作用:

实战验证缓冲区存在:

关键结论:

9. 🚀 实战:文件拷贝程序(文本 / 二进制通用)

核心思路:

代码实现:

优化:增大缓冲区提升效率

10. ⚠️ 常见文件操作易错点(避坑指南)

11. ✅ 文件操作最佳实践(总结)


✨引言:

在 C 语言中,程序运行时的数据默认存储在内存中 —— 一旦程序退出,内存被系统回收,数据就会永久丢失。比如你输入一个数字10,程序退出后再次运行,上次的10就不见了。

如果想让数据持久化保存(比如存到硬盘),就需要用到「文件操作」。本文将从 “为什么用文件” 到 “缓冲区原理”,用通俗比喻 + 实战代码,拆解文件操作的核心知识点,包括文件类型、打开关闭、顺序 / 随机读写、错误判定,还会补充大量细节和实战案例,帮你彻底掌握文件操作!

1. 🌟 为什么需要文件操作?(内存数据的 “永久存储” 方案)

先看一个简单的例子:

#include <stdio.h> int main() { int n = 0; scanf("%d", &n); // 输入10 printf("%d", n); // 打印10 return 0; }
  • 程序运行时,n=10存储在内存栈区
  • 程序退出后,栈区内存被系统回收,10消失;
  • 再次运行程序,无法获取上次的10

文件操作的核心价值

将数据从内存写入硬盘(或从硬盘读入内存),实现数据的 “持久化存储”。就像把临时放在桌上的文件(内存),放进抽屉(硬盘)永久保存。

2. 📌 什么是文件?(程序文件 vs 数据文件)

放在硬盘上的数据集统称为 “文件”,在 C 语言中分为两类:

2.1 程序文件

  • 用于存放程序代码和编译相关的文件;
  • 例如:源文件(.c)、目标文件(.obj)、可执行文件(.exe)。

2.2 数据文件

  • 用于存放程序运行时读写的数据(不是程序本身);
  • 例如:程序读取的配置文件(config.txt)、程序输出的日志文件(log.txt)。

文件名(文件的 “唯一标识”)

一个完整的文件名包含三部分:文件路径 + 文件名主干 + 文件后缀示例:C:\code\test.txt

  • 路径:C:\code\(文件所在位置);
  • 主干:test(文件名);
  • 后缀:.txt(文件类型,文本文件)。

3. 📊 文本文件 vs 二进制文件(数据存储的两种形式)

根据数据的存储方式,数据文件分为「文本文件」和「二进制文件」,核心区别是是否将内存中的二进制数据转换为 ASCII 码。

3.1 核心定义

类型存储规则特点
文本文件内存中的二进制数据→转换为 ASCII 码→存储到硬盘可被记事本打开,人类可读
二进制文件内存中的二进制数据→直接存储到硬盘(不转换)不可被记事本直接读取(乱码),存储高效

3.2 直观示例:整数10000的存储对比

  • 内存中10000的二进制:00000000 00000000 00100111 00010000(4 字节);
  • 文本文件存储:转换为 5 个 ASCII 码字符('1'→49、'0'→48、'0'→48、'0'→48、'0'→48),占用 5 字节;
  • 二进制文件存储:直接存储 4 字节二进制数据,占用 4 字节。

3.3 代码验证(文本 vs 二进制存储)

#include <stdio.h> int main() { int a = 10000; // 1. 二进制存储(wb模式) FILE* pf1 = fopen("binary.txt", "wb"); if (pf1 != NULL) { fwrite(&a, 4, 1, pf1); // 直接写二进制数据(4字节) fclose(pf1); pf1 = NULL; } // 2. 文本存储(w模式) FILE* pf2 = fopen("text.txt", "w"); if (pf2 != NULL) { fprintf(pf2, "%d", a); // 转换为ASCII码存储(5字节) fclose(pf2); pf2 = NULL; } return 0; }
  • 用记事本打开binary.txt:显示乱码(二进制数据);
  • 用记事本打开text.txt:显示10000(ASCII 码,人类可读)。

4. 🚪 文件的打开与关闭(核心操作:fopen/fclose)

文件操作遵循 “三段论”:打开文件→读写文件→关闭文件

就像 “喝水”:打开水瓶→喝水→关闭水瓶。

4.1 流与标准流(默认打开的 3 个流)

程序要和外部设备(键盘、屏幕、硬盘文件)交互,需要通过 “流”(数据传输的通道)。C 语言启动时,会默认打开 3 个标准流,无需手动打开:

标准流作用对应设备常用函数
stdin标准输入流(读取数据)键盘scanf、fgetc
stdout标准输出流(输出数据)屏幕printf、fputc
stderr标准错误流(输出错误信息)屏幕perror、fprintf

💡 比喻:流就像 “数据线”,连接程序和外部设备,数据通过 “数据线” 传输。

4.2 文件指针(FILE*:文件的 “身份证”)

每个被打开的文件,系统都会在内存中开辟一块 “文件信息区”,存储文件名、状态、当前读写位置等信息。这个信息区被封装在FILE结构体中,通过FILE*类型的指针(文件指针)来访问。

// FILE* 指针指向文件信息区,间接操作文件 FILE* pf1; // 指向文件1的信息区 FILE* pf2; // 指向文件2的信息区
  • 打开文件时,fopen返回文件指针,指向该文件的信息区;
  • 关闭文件时,fclose释放文件信息区,指针需置空(避免野指针)。

4.3 文件打开方式详解(表格汇总)

打开文件的核心函数是fopen,第二个参数mode指定打开方式,不同方式决定了文件的读写权限和创建规则:

打开方式类型核心权限文件不存在时适用场景
"r"文本只读(输入)出错(返回 NULL)读取已存在的文本文件
"w"文本只写(输出)创建新文件覆盖 / 创建文本文件并写入
"a"文本追加(在文件尾写入)创建新文件向文本文件尾添加数据
"r+"文本读写(先读)出错读写已存在的文本文件
"w+"文本读写(先写)创建新文件覆盖 / 创建文本文件并读写
"a+"文本读写(追加 + 读)创建新文件读写文本文件,写在尾部
"rb"二进制只读(输入)出错读取已存在的二进制文件
"wb"二进制只写(输出)创建新文件覆盖 / 创建二进制文件并写入
"ab"二进制追加(在文件尾写入)创建新文件向二进制文件尾添加数据
"rb+"二进制读写(先读)出错读写已存在的二进制文件
"wb+"二进制读写(先写)创建新文件覆盖 / 创建二进制文件并读写
"ab+"二进制读写(追加 + 读)创建新文件读写二进制文件,写在尾部

4.4 打开与关闭文件的正确示例

#include <stdio.h> int main() { // 1. 打开文件:读文本文件("r"模式) FILE* pf = fopen("test.txt", "r"); // 关键:判断文件是否打开成功(避免NULL指针) if (pf == NULL) { perror("fopen failed"); // 打印错误信息(如:fopen failed: No such file or directory) return 1; // 打开失败,退出程序 } // 2. 读写文件(后续讲解) // ... // 3. 关闭文件(必须!否则内存泄漏、数据丢失) fclose(pf); pf = NULL; // 指针置空,避免野指针 return 0; }
易错点标注:
// ❌ 错误1:未判断打开失败 FILE* pf = fopen("test.txt", "r"); fgetc(pf); // 若pf为NULL,崩溃 // ❌ 错误2:关闭后未置空 fclose(pf); fputc('a', pf); // 野指针,非法访问 // ❌ 错误3:打开方式与操作不匹配 FILE* pf = fopen("test.txt", "r"); // 只读模式 fputc('a', pf); // 错误:只读模式不能写

5. 📝 文件的顺序读写(8 个核心函数 + 对比)

顺序读写是指从文件开头到结尾,按顺序读写数据(不能跳着来),核心是 8 个函数,分为 4 组:

5.1 字符读写:fputc/fgetc(单个字符操作)

fputc:字符输出函数(写字符)
  • 原型:int fputc(int character, FILE* stream);
  • 功能:将字符character写入stream流(文件 / 屏幕);
  • 返回值:成功返回写入的字符(ASCII 码),失败返回EOF(-1)。
int main() { // 打开文件:写文本文件("w"模式) FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { perror("fopen"); return 1; } // 写字符:单个写入 fputc('a', pf); fputc('b', pf); fputc('c', pf); // 写字符:循环写入a~z for (char ch = 'a'; ch <= 'z'; ch++) { fputc(ch, pf); // 写入abcdefghijklmnopqrstuvwxyz } fclose(pf); pf = NULL; return 0; }
  • 结果:test.txt中存储abcabcdefghijklmnopqrstuvwxyz(前 3 个是单个写入,后 26 个是循环写入)。
fgetc:字符输入函数(读字符)
  • 原型:int fgetc(FILE* stream);
  • 功能:从stream流(文件 / 键盘)读取单个字符;
  • 返回值:成功返回字符的 ASCII 码,失败或读到文件尾返回EOF(-1)。
int main() { // 打开文件:读文本文件("r"模式) FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } // 读字符:循环读取所有字符(直到EOF) int ch = 0; // 用int接收,因为EOF是-1(char无法存储) while ((ch = fgetc(pf)) != EOF) { printf("%c", ch); // 输出:abcabcdefghijklmnopqrstuvwxyz } fclose(pf); pf = NULL; return 0; }

💡 关键:用int接收fgetc的返回值,因为char的范围是-128~127,而EOF-1,若字符是0xFF(ASCII 码 255),会和EOF冲突。

5.2 行读写:fputs/fgets(整行数据操作)

fputs:文本行输出函数(写整行)
  • 原型:int fputs(const char* str, FILE* stream);
  • 功能:将字符串str写入stream流,不自动添加换行符
  • 返回值:成功返回非负数,失败返回EOF
int main() { FILE* pf = fopen("test.txt", "w"); if (pf == NULL) return 1; // 写整行:不自动加换行 fputs("hello world", pf); fputs("hello bit", pf); // 结果:hello worldhello bit(同一行) // 手动加换行符 fputs("hello world\n", pf); fputs("hello bit\n", pf); // 结果:hello world(行1),hello bit(行2) fclose(pf); pf = NULL; return 0; }
fgets:文本行输入函数(读整行)
  • 原型:char* fgets(char* str, int num, FILE* stream);
  • 功能:从stream流读取最多num-1个字符(留 1 个存'\0'),遇到'\n'或文件尾停止;
  • 返回值:成功返回str(存储字符串的地址),失败或文件尾返回NULL
int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) return 1; char arr[20] = {0}; // 读第一行:最多读19个字符(num=20) fgets(arr, 20, pf); printf("%s", arr); // 输出:hello worldhello bit(无换行) // 读第二行 fgets(arr, 20, pf); printf("%s", arr); // 输出:hello world(带换行) // 循环读取所有行 while (fgets(arr, 20, pf) != NULL) { printf("%s", arr); } fclose(pf); pf = NULL; return 0; }
易错点:
  • fgetsnum必须大于字符串长度 + 1(留'\0'),否则会截断字符串;
  • fgets会读取'\n'并存储在字符串中,打印时无需额外加'\n'

5.3 格式化读写:fprintf/fscanf(结构体 / 多类型数据)

fprintf:格式化输出函数(写多类型数据)
  • 原型:int fprintf(FILE* stream, const char* format, ...);
  • 功能:将多类型数据(int/char/struct 等)按格式写入stream流;
  • 对比printfprintf默认写入stdout(屏幕),fprintf可指定流(文件 / 屏幕)。
// 定义结构体 struct Student { char name[20]; int age; float score; }; int main() { struct Student s = {"张三", 20, 65.5f}; // 打开文件:写文本文件 FILE* pf = fopen("student.txt", "w"); if (pf == NULL) return 1; // 写结构体数据到文件 fprintf(pf, "%s %d %.1f", s.name, s.age, s.score); // 写屏幕(等价于printf) fprintf(stdout, "%s %d %.1f\n", s.name, s.age, s.score); fclose(pf); pf = NULL; return 0; }
  • 结果:student.txt中存储张三 20 65.5,屏幕输出相同内容。
fscanf:格式化输入函数(读多类型数据)
  • 原型:int fscanf(FILE* stream, const char* format, ...);
  • 功能:从stream流按格式读取多类型数据;
  • 对比scanfscanf默认读取stdin(键盘),fscanf可指定流(文件 / 键盘)。
struct Student { char name[20]; int age; float score; }; int main() { struct Student s = {0}; // 打开文件:读文本文件(注意:必须用"r"模式,不能用"w") FILE* pf = fopen("student.txt", "r"); if (pf == NULL) return 1; // 从文件读取数据到结构体 fscanf(pf, "%s %d %f", s.name, &s.age, &s.score); // 从键盘读取(等价于scanf) fscanf(stdin, "%s %d %f", s.name, &s.age, &s.score); // 打印验证 printf("%s %d %.1f\n", s.name, s.age, s.score); fclose(pf); pf = NULL; return 0; }
易错点:
  • fscanf读取字符串时,name是数组名(本身是地址),无需加&
  • 打开文件时,读操作必须用"r"/"r+"模式,写操作必须用"w"/"w+"/"a"/"a+"模式,否则操作失败。

5.4 二进制读写:fwrite/fread(高效存储)

二进制读写直接操作内存中的二进制数据,不转换为 ASCII 码,存储效率高(占用空间小、速度快),适用于结构体、数组等复杂数据。

fwrite:二进制输出函数(写二进制数据)
  • 原型:size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
  • 参数:
    • ptr:指向要写入的数据(数组 / 结构体地址);
    • size:每个数据的字节数;
    • count:数据的个数;
    • stream:目标流(只能是文件流,不能是 stdin/stdout);
  • 返回值:成功写入的数据个数。
int main() { int arr[] = {1, 2, 3, 4, 5}; // 打开文件:写二进制文件("wb"模式) FILE* pf = fopen("binary_arr.txt", "wb"); if (pf == NULL) return 1; // 写数组到文件:5个int,每个4字节 size_t ret = fwrite(arr, sizeof(int), 5, pf); printf("成功写入%d个数据\n", ret); // 输出:成功写入5个数据 fclose(pf); pf = NULL; return 0; }
fread:二进制输入函数(读二进制数据)
  • 原型:size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
  • 功能:从文件流读取二进制数据到ptr指向的空间;
  • 返回值:成功读取的数据个数(小于count表示读取结束或失败)。
int main() { int arr[5] = {0}; // 打开文件:读二进制文件("rb"模式) FILE* pf = fopen("binary_arr.txt", "rb"); if (pf == NULL) return 1; // 读文件到数组:最多读5个int size_t ret = fread(arr, sizeof(int), 5, pf); printf("成功读取%d个数据:", ret); for (int i = 0; i < ret; i++) { printf("%d ", arr[i]); // 输出:1 2 3 4 5 } fclose(pf); pf = NULL; return 0; }

5.5 拓展:三组格式化函数对比

函数功能适用场景
scanf从标准输入流(stdin)读取格式化数据键盘输入→程序
fscanf从指定输入流(文件 / 键盘)读取格式化数据文件 / 键盘输入→程序
sscanf从字符串中读取格式化数据字符串→程序
printf向标准输出流(stdout)输出格式化数据程序→屏幕输出
fprintf向指定输出流(文件 / 屏幕)输出格式化数据程序→文件 / 屏幕输出
sprintf将格式化数据转换为字符串程序→字符串
实战示例:sscanf/sprintf
#include <stdio.h> struct Student { char name[20]; int age; float score; }; int main() { char buf[100] = {0}; struct Student s = {"张三", 20, 65.5f}; struct Student t = {0}; // 1. sprintf:将结构体数据转为字符串 sprintf(buf, "%s %d %.1f", s.name, s.age, s.score); printf("字符串:%s\n", buf); // 输出:字符串:张三 20 65.5 // 2. sscanf:从字符串读取数据到结构体 sscanf(buf, "%s %d %f", t.name, &t.age, &t.score); printf("结构体:%s %d %.1f\n", t.name, t.age, t.score); // 输出:张三 20 65.5 return 0; }

6. 🔀 文件的随机读写(灵活定位:fseek/ftell/rewind)

顺序读写只能从开头到结尾按顺序操作,随机读写可以通过 “定位文件指针”,跳转到文件任意位置读写(比如直接读第 5 个字符、修改中间数据)。

6.1 fseek:定位文件指针

  • 原型:int fseek(FILE* stream, long int offset, int origin);
  • 功能:根据origin(起始位置)和offset(偏移量),定位文件指针;
  • 参数说明:
    • origin:起始位置(3 种选择):
      • SEEK_SET:文件开头(0);
      • SEEK_CUR:文件指针当前位置;
      • SEEK_END:文件末尾;
    • offset:偏移量(正数→向后移,负数→向前移)。
实战示例:定位读取文件内容

假设test.txt中存储abcdefghi(9 个字符,索引 0~8):

#include <stdio.h> int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) return 1; int ch = 0; // 1. 读第一个字符(a) ch = fgetc(pf); printf("%c\n", ch); // 输出:a // 2. 从当前位置(a后)向后移4个字符→f fseek(pf, 4, SEEK_CUR); ch = fgetc(pf); printf("%c\n", ch); // 输出:f // 3. 从文件开头向后移5个字符→f fseek(pf, 5, SEEK_SET); ch = fgetc(pf); printf("%c\n", ch); // 输出:f // 4. 从文件末尾向前移4个字符→f fseek(pf, -4, SEEK_END); ch = fgetc(pf); printf("%c\n", ch); // 输出:f fclose(pf); pf = NULL; return 0; }

6.2 ftell:获取文件指针偏移量

  • 原型:long int ftell(FILE* stream);
  • 功能:返回文件指针相对于文件开头的偏移量(字节数)。
int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) return 1; fseek(pf, 0, SEEK_END); // 定位到文件末尾 long int len = ftell(pf); // 获取偏移量(文件长度) printf("文件长度:%ld字节\n", len); // 输出:9字节 fclose(pf); pf = NULL; return 0; }

6.3 rewind:重置文件指针到开头

  • 原型:void rewind(FILE* stream);
  • 功能:将文件指针重置到文件开头。
int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) return 1; int ch = fgetc(pf); printf("%c\n", ch); // 输出:a fseek(pf, -4, SEEK_END); ch = fgetc(pf); printf("%c\n", ch); // 输出:f rewind(pf); // 重置到开头 ch = fgetc(pf); printf("%c\n", ch); // 输出:a fclose(pf); pf = NULL; return 0; }

7. ❗ 文件读取结果判定(feof/ferror:区分 “文件尾” 和 “读错误”)

文件读取结束有两种原因:

  1. 正常结束:读到文件末尾(EOF);
  2. 异常结束:读取过程中发生错误(如文件损坏、权限不足)。

仅通过fgetc返回EOF无法区分这两种情况,需要用feofferror函数判定。

7.1 feof:判断是否读到文件尾

  • 原型:int feof(FILE* stream);
  • 功能:检测文件是否因读到末尾而结束;
  • 返回值:是→非 0 值,否→0。

7.2 ferror:判断是否发生读错误

  • 原型:int ferror(FILE* stream);
  • 功能:检测文件是否因错误而结束;
  • 返回值:是→非 0 值,否→0。

实战示例:正确判定读取结束原因

#include <stdio.h> int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) return 1; int ch = 0; // 循环读取 while ((ch = fgetc(pf)) != EOF) { printf("%c", ch); } printf("\n"); // 判定结束原因 if (feof(pf)) { printf("读取正常结束:已到文件末尾\n"); } else if (ferror(pf)) { perror("读取异常结束"); } fclose(pf); pf = NULL; return 0; }
异常情况测试(故意出错):
int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) return 1; // 错误操作:只读模式下写数据 char ch = 'x'; for (ch = 'a'; ch <= 'z'; ch++) { fputc(ch, pf); // 只读模式不允许写,会触发错误 } // 判定错误 if (ferror(pf)) { perror("操作失败"); // 输出:操作失败: Bad file descriptor } fclose(pf); pf = NULL; return 0; }

8. 📦 文件缓冲区(为什么数据没立刻写到硬盘?)

C 语言文件操作存在 “缓冲区” 机制 —— 数据不会直接写入硬盘,而是先存入内存中的缓冲区,当缓冲区满、调用fflushfclose时,才会将数据同步到硬盘。

缓冲区的作用:

  • 减少硬盘 I/O 次数(硬盘读写速度慢,缓冲区批量写入更高效);
  • 比喻:缓冲区就像 “快递驿站”,快递员不会收到一个快递就送一次,而是攒一批再送,提高效率。

实战验证缓冲区存在:

#include <stdio.h> #include <windows.h> // Sleep函数头文件(Windows) // #include <unistd.h> // sleep函数头文件(Linux) int main() { FILE* pf = fopen("buffer.txt", "w"); if (pf == NULL) return 1; fputs("abcdef", pf); // 数据写入缓冲区(未同步到硬盘) printf("睡眠10秒:此时打开buffer.txt,无内容\n"); Sleep(10000); // 睡眠10秒(Windows),Linux用sleep(10) fflush(pf); // 手动刷新缓冲区:数据同步到硬盘 printf("刷新缓冲区后,睡眠10秒:此时打开buffer.txt,有内容\n"); Sleep(10000); fclose(pf); // 关闭文件时,自动刷新缓冲区 pf = NULL; return 0; }

关键结论:

  • 缓冲区由 C 标准库管理,默认大小由编译器决定(通常 4KB/8KB);
  • fflush(stream):手动刷新缓冲区(仅对输出流有效);
  • fclose:关闭文件时自动刷新缓冲区,因此必须关闭文件,否则缓冲区数据可能丢失。

9. 🚀 实战:文件拷贝程序(文本 / 二进制通用)

需求:将source.txt(源文件)的内容拷贝到dest.txt(目标文件),支持文本和二进制文件。

核心思路:

  1. 打开源文件(读模式:"r""rb")和目标文件(写模式:"w""wb");
  2. 循环读取源文件数据,写入目标文件;
  3. 关闭两个文件。

代码实现:

#include <stdio.h> #include <stdlib.h> // 拷贝函数:src_path(源文件路径),dest_path(目标文件路径) void file_copy(const char* src_path, const char* dest_path) { // 打开源文件(二进制读模式,兼容文本和二进制文件) FILE* pfin = fopen(src_path, "rb"); if (pfin == NULL) { perror("打开源文件失败"); exit(1); // 退出程序 } // 打开目标文件(二进制写模式) FILE* pfout = fopen(dest_path, "wb"); if (pfout == NULL) { perror("打开目标文件失败"); fclose(pfin); // 先关闭源文件,避免内存泄漏 exit(1); } // 循环读写:每次读1字节,写1字节(也可增大缓冲区提升效率) int ch = 0; while ((ch = fgetc(pfin)) != EOF) { fputc(ch, pfout); } // 判定拷贝是否成功 if (feof(pfin)) { printf("拷贝成功!\n"); } else if (ferror(pfin)) { perror("拷贝失败"); } // 关闭文件 fclose(pfin); fclose(pfout); pfin = NULL; pfout = NULL; } int main() { // 拷贝文本文件 file_copy("source.txt", "dest.txt"); // 拷贝二进制文件(如图片、exe) // file_copy("image.jpg", "image_copy.jpg"); return 0; }

优化:增大缓冲区提升效率

每次读写 1 字节效率低,可使用数组作为缓冲区,每次读写 1024 字节:

// 优化版:缓冲区大小1024字节 void file_copy_opt(const char* src_path, const char* dest_path) { FILE* pfin = fopen(src_path, "rb"); FILE* pfout = fopen(dest_path, "wb"); if (pfin == NULL || pfout == NULL) { perror("文件打开失败"); exit(1); } char buf[1024] = {0}; // 缓冲区 size_t ret = 0; // 每次读1024字节,返回实际读取的字节数 while ((ret = fread(buf, 1, 1024, pfin)) != 0) { fwrite(buf, 1, ret, pfout); // 写实际读取的字节数 } // 判定结果... fclose(pfin); fclose(pfout); }

10. ⚠️ 常见文件操作易错点(避坑指南)

  1. 未判断文件打开成功fopen返回NULL时直接操作,导致崩溃;
  2. 打开方式错误:只读模式("r")下写数据,或只写模式("w")下读数据;
  3. 忘记关闭文件:导致内存泄漏、缓冲区数据丢失;
  4. 关闭后未置空指针:文件指针成为野指针,后续误操作崩溃;
  5. char接收fgetc返回值:无法区分EOF(-1)和字符0xFF(255);
  6. fgetsnum参数过小:导致字符串被截断,未存储'\0'
  7. 二进制文件用记事本打开:看到乱码误以为写入失败(正常现象);
  8. 忽略缓冲区:未调用fflushfclose,数据未同步到硬盘。

11. ✅ 文件操作最佳实践(总结)

  1. 打开必判断fopen后必须检查返回值是否为NULL,用perror打印错误;
  2. 关闭必执行:读写完成后必须调用fclose,且关闭后将指针置空;
  3. 模式要匹配:读操作对应"r"/"rb",写操作对应"w"/"wb",追加对应"a"/"ab"
  4. 二进制优先:存储结构体、数组等复杂数据时,优先用二进制模式(高效、无转换);
  5. 缓冲区注意:需要立即同步数据时,调用fflush(如日志输出);
  6. 结束必判定:用feofferror区分 “文件尾” 和 “读错误”;
  7. 路径要正确:文件路径若包含空格或特殊字符,需用引号括起来(如"C:\my file.txt")。

文件操作是 C 语言的核心实用技能,掌握它能让你的程序具备数据持久化能力,无论是开发工具、日志系统还是数据处理程序,都离不开文件操作。如果这篇博客帮到了你,欢迎点赞收藏🌟~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 20:36:56

Langchain-Chatchat在ESG报告编制中的辅助

Langchain-Chatchat在ESG报告编制中的辅助 在企业可持续发展日益受到监管机构、投资者与公众关注的今天&#xff0c;一份准确、完整且合规的ESG&#xff08;环境、社会与治理&#xff09;报告已成为企业非财务信息披露的核心载体。然而&#xff0c;现实中的ESG数据往往散落在年…

作者头像 李华
网站建设 2026/4/17 22:44:50

Langchain-Chatchat问答延迟优化:GPU推理加速实测

Langchain-Chatchat问答延迟优化&#xff1a;GPU推理加速实测 在企业智能客服、内部知识助手等应用场景中&#xff0c;用户早已习惯了“秒回”的交互体验。然而&#xff0c;当我们将大语言模型&#xff08;LLM&#xff09;引入私有知识库问答系统时&#xff0c;动辄数秒甚至十几…

作者头像 李华
网站建设 2026/4/17 19:10:51

XML 注意事项

XML 注意事项 引言 XML(eXtensible Markup Language,可扩展标记语言)作为一种用于存储和传输数据的标记语言,广泛应用于互联网数据交换、Web服务和数据存储等领域。正确使用XML可以提高数据处理的效率和质量。本文将详细阐述在使用XML过程中需要注意的几个关键事项。 1.…

作者头像 李华
网站建设 2026/4/19 23:21:00

FaceFusion能否用于在线教育中的个性化讲师替换?

FaceFusion能否用于在线教育中的个性化讲师替换&#xff1f;在远程学习逐渐成为主流的今天&#xff0c;一个尴尬的事实是&#xff1a;很多学生看不完一门课程&#xff0c;并不是因为内容太难&#xff0c;而是“讲师我不喜欢”。可能是口音听不惯、形象有距离感&#xff0c;甚至…

作者头像 李华
网站建设 2026/4/18 14:48:15

FaceFusion在城市规划公众参与中的居民形象模拟展示

FaceFusion在城市规划公众参与中的居民形象模拟展示 在一座老城区即将启动改造的社区议事会上&#xff0c;一位年过七旬的居民盯着投影屏上的效果图皱眉&#xff1a;“这楼是挺漂亮&#xff0c;可我怎么觉得这不是我们的家&#xff1f;”——这样的场景&#xff0c;在全国许多…

作者头像 李华
网站建设 2026/4/18 14:28:42

Langchain-Chatchat打造个性化学习辅导机器人

Langchain-Chatchat打造个性化学习辅导机器人 在今天的教育场景中&#xff0c;一个常见的困境是&#xff1a;学生反复询问“这个公式怎么用&#xff1f;”、“这道题的解法是什么&#xff1f;”&#xff0c;而老师却难以做到一对一即时响应。与此同时&#xff0c;教学资料散落在…

作者头像 李华