标准I/O文件
文件基础概念
文件定义
存储在外存储器(磁盘、U盘、移动硬盘等)上的数据集合,是操作系统管理数据的基本单位。
文件操作核心
- 文件内容的读取(输入操作 Input)
- 文件内容的写入(输出操作 Output)
文件缓冲机制
- 系统为文件在内存中创建缓冲区(通常4KB大小)
- 程序操作实际上是在缓冲区进行
- 数据像水流一样流动,称为“文件流”
- 缓冲机制减少了直接访问外存的次数,提高效率
文件分类
- 文本文件(ASCII文件)
- 以ASCII码形式存储
- 可直接用文本编辑器查看
- 示例:.txt、.c、.h文件
- 二进制文件
- 以二进制形式存储
- 需要特定程序才能解析
- 示例:.exe、.jpg、.dat文件
文件标识
-
文件系统中:路径 + 文件名
- Windows示例:
D:\edu\test.txt
- Linux示例:
/home/user/data.bin
- Windows示例:
-
C程序中:文件指针(FILE *类型)
FILE *fp; // 声明文件指针
文件操作的基本步骤
- 打开文件:建立程序与文件的连接
- 文件处理/读写文件:读写操作
- 关闭文件:释放资源
文件打开与关闭
fopen()
说明:打开文件
函数原型:
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
参数详解:
-
path
:文件路径(绝对路径或相对路径) -
mode
:打开模式-
基本模式(ASCII模式):
r
以只读方式打开文件,文件指针指向文件起始位置。
r+
以读写方式打开文件,文件指针指向文件起始位置。w
以写的方式打开文件,如果文件存在则清空,不存在就创建,文件指针指向文件起始位置。w+
以读写方式打开文件,如果文件不存在则创建,否则清空,文件指针指向文件起始位置。a
以追加方式打开文件,如果文件不存在则创建,文件指针指向文件末尾位置。a+
以读和追加方式打开文件,如果文件不存在则创建,如果读文件则文件指针指向文件起始位置,如果追加(写)则文件指针指向文件末尾位置。
-
二进制模式:
rb
、wb
、ab
等,其实就是基本模式的前提下,加b
-
特殊模式:
x
:独占模式(与w
组合,文件必须不存在)
-
返回值:
- 成功:返回文件指针(指向FILE结构体)
- 失败:返回NULL(需要检查errno)
错误处理示例:
FILE *fp = fopen("data.txt","r");
if(fp == NULL)
{
perror("文件打开失败!");
// 退出进程
exit(EXIT_FAILURE); // exit(1) 程序失败退出
}
fclose()
**说明:**关闭文件
函数原型:
#include <stdio.h>
int fclose(FILE *fp);
参数
fp
(文件指针):指向要关闭的FILE
对象的指针,通常由fopen()
或类似函数返回。
返回值
- 成功:返回
0
(即EXIT_SUCCESS
)。 - 失败:返回
EOF
(通常是-1
),并设置errno
指示错误原因(如磁盘已满、文件被占用等)。
注意事项:
- 必须关闭所有打开的文件
- 程序退出时未关闭的文件可能丢失数据
- 多次关闭同一文件指针会导致未定义行为
完整示例:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
// 打开文件
FILE *fp = fopen("texst.txt","w");
if (!fp)
{
perror("打开文件失败!");
return -1; // exit(1);
}
// 读写文件
// 关闭文件
if(fclose(fp) != 0)
{
perror("关闭文件失败!");
return -1;
}
return 0;
}
文件顺序读写
单字符读写
fgetc()
**说明:**单字符的读取
函数原型:
int fgetc(FILE *fp);
参数说明:
fp
:待操作的文件指针
返回值:
- 成功:返回字符的ASCII码
- 失败:或者文件末尾返回EOF(-1)
fputc()
**说明:**单字符的写出
函数原型:
int fputc(int c, FILE *fp);
参数说明:
c
:待写出的字符的ASCII码fp
:待操作的文件指针
返回值:
- 成功:返回写入的字符的ASCII码
- 失败:返回EOF(-1)
案例:文件拷贝(字符版)
#include <stdio.h>
int main(int argc,char *argv[])
{
if (argc != 3)
{
printf("用法:%s 源文件 目标文件\n", argv[0]);
return -1;
}
// 打开代操做的文件
FILE *src = fopen(argv[1],"r");
FILE *dest = fopen(argv[2],"w");
// 非空校验
if (!src || !dest)
{
perror("文件打开失败!");
return -1;
}
// 读写文件
int ch;
while((ch = fgetc(src)) != EOF) // 循环读取 EOF:-1 返回-1,表示读取结束
{
// 循环的写入
fputc(ch, dest);
}
// 给文件末尾添加换行
fputc('\n', dest);
// 关闭文件
fclose(src);
fclose(dest);
return 0;
}
行读写(多字符读写)
fgets()
**说明:**读取字符串(字符数组)
函数原型:
#include <stdio.h>
char* fgets(char *buf, int size, FILE *fp);
功能描述:
从指定的文件流(fp
)中读取一行字符串(以 \n
结尾),并将其存储到缓冲区 buf
中。
- 读取的字符数最多为
size - 1
(保留一个位置给\0
)。 - 如果遇到 换行符
\n
或 文件结束符EOF
,则停止读取。 - 读取的字符串末尾会自动添加
\0
(空字符),使其成为合法的 C 字符串。
参数:
buf
(缓冲区):用于存储读取数据的字符数组(必须足够大)。size
(最大读取长度):最多读取size - 1
个字符(防止缓冲区溢出)。fp
(文件指针):要读取的文件流(如stdin
、fopen()
返回的指针等)。
返回值
- 成功:返回
buf
的指针(即读取的字符串)。 - 失败或到达文件末尾(EOF):返回
NULL
。
fputs()
**说明:**写入字符串
函数原型:
int fputs(const char *str, FILE *fp);
功能描述:
将字符串 str
写入到指定的文件流 fp
中。
- 不自动添加换行符:与
puts()
不同,fputs
不会在末尾自动添加\n
。 - 遇到
\0
停止:写入过程遇到字符串结束符\0
时终止(\0
本身不会被写入)。
参数:
str
:要写入的字符串(必须以\0
结尾)。fp
:目标文件流(如stdout
、文件指针等)。
返回值:
- 成功:返回一个非负整数(通常是
0
或写入的字符数)。 - 失败:返回
EOF
(-1),并设置errno
指示错误原因(如磁盘已满、文件不可写等)。
案例:文件拷贝(行处理)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_LEN 256
int main(int argc,char *argv[])
{
if (argc != 3)
{
printf("用法:%s 源文件 目标文件\n", argv[0]);
return -1;
}
// 打开文件
FILE *src = fopen(argv[1],"r");
FILE *dest = fopen(argv[2],"w");
// 非空校验
if (!src || !dest)
{
perror("文件打开失败!");
return -1;
}
// 创建一个数组,用来存储每次读写的文本数据、
char line[MAX_LEN];
// 循环读取数据
while(fgets(line, MAX_LEN - 1, src)) // NULL:读取失败,或者读取完毕
{
// 去除换行符
// line[strcspn(line,"\n")] = '\0'; // 将\n替换\0
printf("读取行:%s\n", line);
// 写入数据到文件
if (fputs(line, dest) < 0) // -1:写入失败,或者写入完毕
{
printf("文件写入完毕!\n");
return 1;
}
}
// 关闭文件
fclose(src);
fclose(dest);
return 0;
}
数据块读写(二进制文件访问)
fread()
函数原型:
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t count, FILE *fp);
功能描述:
从文件流中读取数据块到内存缓冲区。主要用于二进制文件的读取操作。
参数说明:
参数 | 类型 | 说明 |
---|---|---|
ptr | void* | 指向存储读取数据的内存缓冲区指针 |
size | size_t | 每个数据项的字节大小(例如:sizeof(int)) |
count | size_t | 要读取的数据项个数 |
fp | FILE* | 指向要读取的文件流指针 |
返回值:
- 成功返回实际读取的数据项个数(不是字节数)
- 如果返回值小于count,可能表示:
- 到达文件末尾(可用
feof()
检查) - 发生读取错误(可用
ferror()
检查)
- 到达文件末尾(可用
fwrite()
函数原型:
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *fp);
功能描述:
将内存中的数据块写入到文件流中。主要用于二进制文件的写入操作,是fread()的配套函数。
参数说明:
参数 | 类型 | 说明 |
---|---|---|
ptr | const void* | 指向要写入数据的内存缓冲区指针 |
size | size_t | 每个数据项的字节大小(例如:sizeof(int)) |
count | size_t | 要写入的数据项个数 |
fp | FILE* | 指向要写入的文件流指针 |
返回值:
- 成功返回实际写入的数据项个数(不是字节数)
- 如果返回值小于count,表示写入过程发生错误
案例:结构体读写
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 创建学生结构体
typedef struct Student
{
int id; // 学号
char name[20]; // 姓名
float scores[3]; // 三门成绩
} Stu;
/**
* @brief 从文件读取二进制数据
*
* @return
*/
void read_data(int len)
{
// 打开文件
FILE *fp = fopen("stu_data.dat", "rb");
if (!fp)
{
perror("文件打开失败!");
return;
}
Stu n_stus[len];
// 从二进制文件中读取数据
size_t read_count = fread(n_stus, sizeof(Stu), len, fp);
// 读取失败校验
if (read_count < len)
{
if (feof(fp)) printf("只读取到%lu条记录!\n", read_count);
else /* else if(ferror(fp)) */
{
perror("读取文件时发生错误!");
// 关闭文件
fclose(fp);
return;
}
}
// 关闭文件
fclose(fp);
for (size_t i = 0; i < len; i++)
{
printf("学生%d:%s,语文-%.2f,数学-%.2f,英语-%.2f\n", n_stus[i].id, n_stus[i].name,n_stus[i].scores[0],n_stus[i].scores[1],n_stus[i].scores[2]);
}
}
/**
* @brief 向文件写入二进制数据
*
* @param stus
* @param len
*
* @return
*/
int write_data(Stu *stus, int len)
{
// 打开文件(文件不存在就创建)
FILE *fp = fopen("stu_data.dat","wb");
if (!fp)
{
perror("文件打开失败!");
return EXIT_FAILURE;
}
// 将stus以二进制形式写入到stu_data.dat文件
size_t written = fwrite(stus, sizeof(Stu), len, fp);
// 写入结果校验
if(written < len)
{
if (feof(fp)) printf("只写入了%lu条记录!\n", written);
else perror("文件写入失败!");
// 关闭文件
fclose(fp);
return EXIT_FAILURE;
}
printf("成功写入%lu条记录!\n", written);
// 关闭文件
return EXIT_SUCCESS;
}
int main(int argc, char *argv[])
{
// 准备一个学生数组
Stu stus[] = {
{1, "张三", {89,88,97}},
{2, "李四", {87,86,67}},
{3, "王五", {81,83,87}}
};
int len = sizeof(stus)/sizeof(stus[0]);
// 本地持久化(写入)
write_data(stus,len);
// 本地持久化(读取)
read_data(len);
printf("\n");
return 0;
}
文件的随机访问
核心函数
rewind(fp)
- 重置到文件开头fseek(fp, offset, whence)
- 定位文件指针whence
取值:SEEK_SET
:文件开头SEEK_CUR
:当前位置SEEK_END
:文件末尾
ftell(fp)
- 获取当前位置feof(fp)
- 检测文件结束
案例
随机访问案例
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp = fopen("demo01.c", "r+");
if (!fp)
{
perror("文件打开失败!");
return -1;
}
// 移动到文件末尾获取大小
fseek(fp,0,SEEK_END);
long size = ftell(fp);// 获取文件大小
printf("文件大小:%ld字节\n",size);
// 移动中间内容
fseek(fp,size/2,SEEK_SET);
// 读取文件内容
char buf[256];
while(!feof(fp))// 如果文件未读完,就循环读取
{
fgets(buf, sizeof(buf), fp);
// 打印读取的内容
printf("中间内容:%s\n",buf);
}
fclose(fp);
return 0;
}
结构体读写
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 创建学生类型(结构体)
typedef struct Student
{
int id; // 学号
char name[20]; // 姓名
float scores[3]; // 三门成绩
} Stu;
/**
* 向文件写入二进制数据
*/
int write_data(FILE **fp)
{
// 将stus写入到stu_data.dat
// 准备要写入的学生数据
Stu stus[3] = {
{1, "张三", {89, 88, 97}},
{2, "李四", {87, 86, 67}},
{3, "王五", {81, 83, 87}}};
// 向文件写入二进制数据
size_t written = fwrite(stus, sizeof(Stu), sizeof(stus) / sizeof(stus[0]), *fp);
if (written < 3)
{
perror("数据写入失败!");
fclose(*fp);
return EXIT_FAILURE;
}
printf("成功写入%lu条学生记录\n", written);
return EXIT_SUCCESS;
}
/**
* 读取二进制文件数据
*/
int read_data(FILE **fp)
{
// 重置文件指针都开头位置
// rewind(*fp);
fseek(*fp, 0, SEEK_SET);
Stu stus[3];
// 从二进制文件中读取信息
size_t read_count = fread(stus, sizeof(Stu), sizeof(stus) / sizeof(stus[0]), *fp);
// 读取失败
if (read_count < 3)
{
if (feof(*fp)) // 查看是否读取到文件末尾
{
printf("只读取到%lu条记录!\n", read_count);
}
else
{
perror("读取文件时发生错误!");
}
}
// 读取成功,遍历
for (size_t i = 0; i < read_count; i++)
{
printf("学生%d:%s,语文-%.2f,数学-%.2f,英语-%.2f\n", stus[i].id, stus[i].name, stus[i].scores[0], stus[i].scores[1], stus[i].scores[2]);
}
return EXIT_SUCCESS;
}
int main(int argc, char const *argv[])
{
// 打开文件(文件不存在就创建)
FILE *fp = fopen("stu_data.dat", "rb+");// 对二进制文件读写
if (!fp)
{
perror("文件打开失败!");
return EXIT_FAILURE;
}
// 本地化保存数据(持久化保存数据)
write_data(&fp);
// 本地化数据读取(持久化数据读取)
read_data(&fp);
// 本地化数据读取(持久化数据读取)
read_data(&fp);
// 关闭文件
fclose(fp);
return 0;
}
运行结果: