文件操作的介绍
相关内容
1.什么是文件
文件的定义
文件,是存储在外部存储设备(磁盘、U 盘、SSD)上、有名字的一组有序数据的集合,操作系统把它抽象成一个统一的访问对象,程序通过文件操作来读写这堆数据 也就是说文件就是一个存放着数据的对象 我们可以通过文件操作 借助操作系统 对其操作 我们写的程序可以叫做程序文件 带有数据的文件可以叫做数据文件
在c语言中系统给我们了一个结构体 FILE 每个编译器里对这个结构体的定义大同小异 所以我们不用再自己定义了 直接使用即可 通过这个结构体 当我们操作文件时 我们可以创建文件信息区 用来存放文件的状态信息等 平时使用还需要用到文件指针 即FILE* 来指向这个区域
文件名
路径 主文件名 . 后缀 例如 D:\\new\\test.txt这样一个文件名 路径是我们查找文件的方式 后缀是文件的格式 常见的形式有:.txt .c等
D:\\new\\ test . txt
在c语言中我们通过文件名来指定文件 还有一个需要注意的点:路径分为绝对路径和相对路径 绝对路径就是将文件的路径全部描述出来这样来找文件 相对路径就是在 当前工作目录去找文件
文件的一种分类(按数据存储形式)
在c中我们把文件分为二进制文件 (内容由原始的二进制数据组成) 和 文本文件(内容由字符编码组成)
2. 为什么要使用文件
像我们平时在编译器中输入输出的各种数据在程序关闭后 就被清除了 不能保存下来 但是文件不会自动清除 因此我们可以利用文件 将程序产生的数据或所需要的数据存储在文件中 这样就保存了数据
3. 文件的缓冲区

我们从文件中输入输出数据并不是一个一个依次进行的 而是有一个缓冲区 先把数据放在缓冲区里 当数据达到一定大小 再一起输入输出
因为我们每次输入输出时都是操作系统在背后出力 如果我们每输入一个数据就让操作系统处理一次 那就太过繁琐了 所以使用了缓冲区
4.硬盘文件,流,程序内存三者的关系

首先什么是流呢
流是一个十分抽象的概念 他就像是充满了数据的一条小河 我们打开文件时就会创建一个流 然后将数据丢进去 再捞出来放到缓存中 最后再输入文件中 还是很有意思的
三个标准流:stdin stdout stderr
5.文件的打开与关闭
现在就正式进入文件操作了!
这里涉及了两个函数 fopen() 和 fclose() 二者都是在 <stdio.h>这个头文件中 后面的所有函数也是在这个头文件中
关于fopen
函数原型是 FILE *fopen(const char *filename, const char *mode);
- 参数 1 filename:文件名 / 路径(相对路径、绝对路径)
- 参数 2 mode:打开模式(文本 / 二进制,读、写、追加)
返回值:打开成功返回 文件信息区的地址 打开失败返回 NULL 空指针 所以使用时一定要加判断看是否打开成功 否则程序可能会崩溃
这里给出一个打开模式的表格 请读者阅读
| "r" | 只读(文本流) | 打开失败,返回 NULL | 从头读 |
| "w" | 只写(文本流) | 创建新文件 | 清空原有内容,从头写 |
| "a" | 追加(文本流) | 创建新文件 | 在文件末尾追加,原有内容保留 |
| "rb" | 只读(二进制流) | 失败 | 从头读 |
| "wb" | 只写(二进制流) | 创建 | 清空重写 |
| "ab" | 追加(二进制流) | 创建 | 末尾追加 |
| "r+" | 读写(文本) | 失败 | 可读可写 |
| "rb+" | 读写(二进制) | 失败 | 可读可写 |
关于fclose
函数原型:int fclose(FILE *stream);
- 参数:FILE* 文件指针(要关闭的流)
- 返回值:成功返回 0;失败返回 EOF(-1)
fclose 可以关闭文件信息区 同时将缓存区的数据输入输出文件 防止数据丢失
完整的过程如下
代码
#include <stdio.h>
int main()
{
FILE *fp = fopen("test.txt", "r");
if(fp == NULL) // 必写判断!
{
perror("fopen"); // 打印打开失败原因
return 1;
}
// 读写操作 …
fclose(fp);
fp = NULL;
return 0;
}
我们创建一个文件后可以在资源管理器里打开查看
6.文件的顺序读写
这里涉及到四组函数依次是: fputc()和fgetc() fputs()和fgets() fprintf()和fscanf() fwrite()和fread() 其中前面三个是常用的函数
后面一组由于控制的是二进制文本所以 用的会相对较少一些
函数的内容有点多 但可以类比 printf 和 scanf 来记忆
| fgetc | int fgetc(FILE *stream); | 从流中读取一个字符 | 文本 / 二进制流 | 读到的字符以unsigned char转 int 返回;读到末尾 / 出错返回 EOF | 成功:读到字符(int)失败 / 到文件尾:EOF(-1) | char ch = fgetc(fp); |
| fputc | int fputc(int ch, FILE *stream); | 向流写入一个字符 | 文本 / 二进制流 | ch 虽然是 int,只会低 8 位有效 | 成功:写入的字符失败:EOF | fputc('A', fp); |
| fgets | char *fgets(char *str, int n, FILE *stream); | 从流读取一行字符串 | 文本流 | 最多读n-1个字符;会把换行符\\n一起读进来,末尾自动加\\0 | 成功:str 地址失败 / 到末尾:NULL | fgets(buf, 100, fp); |
| fputs | int fputs(const char *str, FILE *stream); | 向流写入字符串 | 文本流 | 不会自动加换行符\\n,需要自己写\\n | 成功:非负数失败:EOF | fputs("hello", fp); |
| fscanf | int fscanf(FILE *stream, const char *format, …); | 从文件按格式读取数据 | 文本流 | 用法和scanf一致,来源从文件流代替 stdin;空白符(空格、换行、Tab)自动分隔 | 成功:成功读到的数据个数失败 / 末尾:EOF | fscanf(fp,"%d",&num); |
| fprintf | int fprintf(FILE *stream, const char *format, …); | 按格式写入文件 | 文本流 | 用法和printf一致,输出到文件流代替 stdout | 成功:输出字符总数失败:负数 | fprintf(fp,"%d",num); |
| fread | size_t fread(void *ptr, size_t size, size_t count, FILE *stream); | 从文件读取整块数据 | 二进制流(rb) | 原样读取字节,不做换行转换;适合数组、结构体 | 成功:读到的元素个数小于 count:读到末尾或者出错 | fread(&stu, sizeof(stu),1,fp); |
| fwrite | size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream); | 向文件写入整块数据 | 二进制流(wb) | 原样写入内存原始二进制,适合结构体批量保存 | 成功:写入的元素个数失败:小于 count | fwrite(&stu, sizeof(stu),1,fp); |
这里给出其中2个示例
我们向指文件中 输入1234 要我们能看懂肯定就是文本文件了
代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
FILE* pf = fopen("text.txt", "w");
char op[10]="1234";
if (pf == NULL) {
perror("fopen");
}
fprintf(pf,"%s", op);
fclose(pf);
pf = NULL;
}
执行后从资源管理器打开文件就是这样的

而如果改成fopen("text.txt", "r");内存中 并没有这个文件 就会报错 就是这样的

如果我们要从文件中输出数据就是这样的 这里使用fgets函数 接着刚刚的操作进行 也就是说此时我们已经有一个文件text.txt了
代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
FILE* pf = fopen("text.txt", "r");
char op[10] = { 0 };
if (pf == NULL) {
perror("fopen");
}
fgets(op, 4, pf);
printf("%s", op);
fclose(pf);
pf = NULL;
}
打印出来的结果

如果在原来的1234中加一个换行12\\n34 结果就是这样的

)
其余的大家自己可以下去试试 多注意 特点和注意事项 这里就不多展示了
7. 文件的随机读写
涉及的函数有 fseek() ftell() rewind()
fseek
函数原型 int fseek(FILE *stream, long int offset, int whence);
| stream | FILE* 文件指针 |
| offset | 偏移字节数,long 类型;正数向后(朝文件尾),负数向前(朝文件开头) |
| whence | 基准起点(三个宏) |
| SEEK_SET | 0 | 文件开头 |
| SEEK_CUR | 1 | 文件位置指针当前位置 |
| SEEK_END | 2 | 文件末尾 |
作用:移动流里面的文件位置指针(读写光标),实现随机读写,不再只能从头顺序读。
注意:这个是文件内的位置光标,不是 FILE* 文件指针!
继续前面的例子
代码
#include <stdio.h>
int main()
{
FILE* pf = fopen("text.txt", "rb");
if(pf == NULL)
{
perror("fopen");
return 1;
}
// 光标跳到开头往后偏移2字节,也就是 \\n 的位置
fseek(pf, 2L, SEEK_SET);
int ch = fgetc(pf);
printf("%c", ch); // 读到 '\\n'
fclose(pf);
pf = NULL;
return 0;
}
这里打印出的就是换行

注意:
ftell
函数原型: long ftell(FILE *stream);
作用:获取文件位置指针当前距离文件起始位置的字节偏移量
返回值:
成功:返回 long 类型,当前位置相对于文件开头的字节数
失败:返回 -1L
rewind
函数原型:void rewind(FILE *stream);
作用:把文件流的读写位置指针,直接重置到文件开头
搭配 fseek 经典用法:求文件大小
FILE* pf = fopen("test.txt", "rb");
if(pf == NULL)
{
perror("fopen");
return 1;
}
fseek(pf, 0L, SEEK_END); // 光标移到文件末尾
long file_size = ftell(pf); // 获取末尾的偏移量 = 文件总字节数
printf("文件大小:%ld 字节\\n", file_size);
rewind(pf); // 光标回到开头,方便后续读取
fclose(pf);
8.文件的更新

关键要点
fflush(FILE *stream) 可以刷新文件缓存区
这里给出一个例子
代码
#include <stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "w+");
if (fp == NULL)
{
perror("fopen for w+");
return 1;
}
//写abcdefghi到文件中
fputs("abcdefghi", fp);
//刷新缓冲区,保证数据写入文件
fflush(fp);
//要读取数据b字符,先定位文件指针
fseek(fp, 1, SEEK_SET);
int ch = fgetc(fp);//读取字符
printf("%c\\n", ch);
//在b的位置开始写入hello
fseek(fp, –1, SEEK_CUR);
//解释:因为前面读取一个字符后,文件指示器现在指向了c,需要从当前位置退回一个字符
fputs("hello", fp);
//关闭文件
fclose(fp);
fp = NULL;
return 0;
}
最终文件

打印结果

以上就是本次文件操作的介绍了 内容会有点多 很多函数没有举例 还是要自己把函数的功能都实现一下才能理解本次的大致内容


