一.文件的种类(从文件功能的角度来分类的)
1.1程序文件
包括源程序文件(后缀为.c), 目标文件(windows 环境后缀为.obj), 可执行程序(windows 环境后缀为.exe)。
1.2数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

1.3文件名
文件名包括三部分:文件路径+文件名主干+文件后缀
例如:c:\code\test.txt
二.文件的打开和关闭
2.1文件指针
每个被使用的文件都会在内存中开辟一个相应的文件信息区,用来存放文件的相关信息,这些信息保存在一个结构体变量中 ,该结构体类型有系统声明,取名为FILE,也就是说FILE是一个结构体 。
此为VS2013提供的stdio.h头文件中有的文件类型申明
当我们使用fopen打开文件时,会在内存里创建文件信息区,并且会将文件信息区的起始地址返回回来
一般都是通过FILE的指针来维护FILE结构中的变量,即:
FILE* pf //文件指针变量
可通过pf指向某个文件的文件信息区,再通过文件信息区访问文件,即:通过文件指针变量能够找到与它关联的文件。
2.2文件的打开和关闭
在打开文件时,都会返回一个FILE*的指针变量指向该文件
根据规定:使用fopen函数来打开文件,使用fclose来关闭文件。
fopen:
格式:FILE* fopen(const char* filename, const char* mode); //FILE*是fopen的返回类型
- 第一个参数是文件名(相对路径)。
- 第二个参数是打开模式,常用的有:
| 模式 | 含义 | 核心特性 |
| r | 只读 | 打开已存在的文件,文件不存在则打开失败 |
| w | 只写 | 文件不存在则创建,文件已存在则清空内容(覆盖)//在fopen打开文件时清空 |
| a | 追加 | 文件不存在则创建,写入内容追加到文件末尾 |
| r+ | 读写 | 打开已存在的文件,可读可写,不清空内容 |
| w+ | 读写 | 文件不存在则创建,已存在则清空内容,可读可写 |
| a+ | 读写 | 文件不存在则创建,写入追加到末尾,可读 |
如果在后面加上b,即rb、wb等,就表示是二进制模式
fclose:
格式:int fclose ( FILE * stream );
使用示例:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//打开文件
FILE*pf=fopen("text.c","r");//若打开失败,则pf会返回空指针
if(pf==NULL)
{
printf("%s\n",strerror(errno));
return 1;
}
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}
路径分为相对路径和绝对路径:
- 相对路径:以当前所在目录为起点,简写路径,依赖当前位置。 例:"text.c"
- 绝对路径:从根目录开始,完整、唯一的路径,不受当前所在位置影响。 例:"C:\Users\hpl13\Desktop\text.c"
注:当在代码中使用fopen打开绝对路径时应用\\ 例: FILE*pf=fopen("C:\\Users\\hpl13\\Desktop\\text.c","r");
三.文件的顺序读写
3.1关于文件读写的函数
| 功能 | 函数名 | 适用于 |
| 字符输入函数(读一个字符) | fgetc | 所有输入流 |
| 字符输出函数(写一个字符) | fputc | 所有输出流 |
| 文本行输入函数(读一行字符) | fgets | 所有输入流 |
| 文本行输出函数(写一行字符) | fputs | 所有输入流 |
| 格式化输入函数 | fscanf | 所有输入流 |
| 格式化输出函数 | fprintf | 所有输入流 |
| 二进制输入 | fread | 文件 |
| 二进制输出 | fwrite | 文件 |
输入=读,输出=写
- 键盘输入、文件读取、网络数据接收,本质上都是数据从外部流向程序,这叫输入流。
- 屏幕打印、文件写入、网络数据发送,本质上都是数据从程序流向外部,这叫输出流。
fgetc(读一个字符)
格式:int fgetc ( FILE * stream );
使用示例:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//打开文件
FILE*pf=fopen("text.c","r");
if(pf==NULL)
{
printf("%s\n",strerror(errno));
}
//读文件
int ch=fgetc(pf);
printf("%c\n"ch);//一次只读一个字符
//以下方法可将文件内的内容全部读出来:
int ch=0;
while((ch=fgetc(pf))!=EOF)
{
printf("%c ",ch);
}
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}
fputc(写一个字符)
格式:int fputc ( int character, FILE * stream );
- 第一个参数是写入的字符。
- 第二个参数是文件指针变量名。
使用示例:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//打开文件
FILE*pf=fopen("text.c","w");
if(pf==NULL)
{
printf("%s\n",strerror(errno));
}
//写文件
int i=0;
for(i='a';i<='z';i++);
{
fputc(i,pf);
}
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}
fgets(读一行字符)
格式:char * fgets ( char * str, int num, FILE * stream );
- 第一个参数是指向一个字符数组的指针,读取的字符串将被复制到该数组中。
- 第二个参数是读取的字节数(包括\0,且\0算一个字符)。
- 第三个参数是文件指针变量名。
使用示例:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//打开文件
FILE*pf=fopen("text.c","r");
if(pf==NULL)
{
printf("%s\n",strerror(errno));
}
//读一行字符
char arr[20];
fgets(arr,5,pf);
printf("%s",arr)
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}
fputs(写一行字符)
格式:int fputs ( const char * str, FILE * stream );
使用示例:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//打开文件
FILE*pf=fopen("text.c","w");
if(pf==NULL)
{
printf("%s\n",strerror(errno));
}
//写一行字符
fputs("hello world",pf);
//关闭文件
fclose(pf);
pf=NULL;
return 0;
}
补充:perror
头文件:#include<stdio.h>
当我们判断pf是否为空指针NULL时,可用perror来代替printf("%s",strerror(errno));
例:
FILE*pf=fopen("text.c","w");
if(pf==NULL)
{
perror(fopen:);
}
结果:
fprintf(格式化写入)
格式:int fprintf ( FILE * stream, const char * format, ... );//与printf相似,只比printf多了第一个参数
使用示例:
struct S
{
char name[10];
int age;
float weight;
};
int main()
{
struct S s={"zhangsan",13,50.2};
FILE*pf=fopen("text.c","w");
if(pf==NULL)
{
perror(fopen);
}
fprintf(pf,"%s %d %f",s.name,s.age,s.weight);
fclose(pf);
pf=NULL;
return 0;
}
fscanf(格式化读)
格式:int fscanf ( FILE * stream, const char * format, ... );//与scanf相似,只比scanf多了第一个参数
使用示例:
struct S
{
char name[10];
int age;
float weight;
};
int main()
{
struct S s={0};
FILE*pf=fopen("text.c","r");
if(pf==NULL)
{
perror(fopen);
}
fscanf(pf,"%s %d %f",s.name,&(s.age),&(s.weight));
printf("%s %d %f",s.name,s.age,s.weight);
fclose(pf);
pf=NULL;
return 0;
}
补充:流
任何一个C语言程序,只要运行起来就默认打开三个流:
FILE* stdin - 标准输入流(键盘)
FILE* stdout - 标准输出流(屏幕)
FILE* stderror - 标准错误流(屏幕)
如果将fprintf的第一个参数由pf换为stdout,那么就会写入到屏幕上,对于fputs和fputc也一样
fwrite(二进制方法写)
格式:size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
- 第一个参数是指向你要写入文件的数据的起始地址(数据从哪里来)
- 第二个参数是一个元素的大小
- 第三个参数是有几个这样的元素
使用实例:
-
FILE*pf={"test.c","wd"}; int arr[] = {10,20,30}; fwrite(arr, sizeof(int), 3, pf); -
FILE*pf={"test.c","wd"}; struct S s={"zhangsan",18,65.2f}; fwrite(&s,sizeof(struct S),1,pf);
通过二进制的方式写进去,这会让我们肉眼看不懂,这时就应该用fread以二进制的方式来读
fread(二进制方法读)
格式:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );//与fwrite相同
使用示例:
FILE*pf={"test.c","rd"};
struct S s={"zhangsan",18,65.2};
fread(&s,sizeof(struct S),1,pf);
printf("%s %d %f",s.arr,s.age,s.weight);
3.2关于一些函数的差异
scanf 是针对标准输入的格式化输入语句(键盘)
printf 是针对标准输出的格式化输出语句(键盘)
fscanf 是针对所有输入流的格式化输入语句(文件、键盘输入都可以)
fprintf 是针对所有输出流的格式化输出语句(文件、键盘输入都可以)
sscanf
作用:从一个字符串中转化出一个格式化的数据
#include<stdio.h>
struct S
{
char name[20];
int age;
float weight;
};
int main()
{
struct S s={"zhangsan",18,65.2f};
struct S tmp = { 0 };
char buf[100] = { 0 };
//把s中的格式化数据转化成字符串放到buf中
sprintf(buf, "%s %d %f", s.arr, s.age, s.score);
//"zhangsan 20 55.500000";
printf("字符串: %s\n", buf);
//从字符串buf中获取一个格式化的数据到tmp中
sscanf(buf, "%s %d %f", tmp.arr, &(tmp.age), &(tmp.score));
printf("格式化: %s %d %f\n", tmp.arr, tmp.age, tmp.score);
return 0
}
sprintf
作用:把一个格式化的数据写到字符串中,本质是把一个格式化的数据转换成字符串。
格式:int sprintf ( char * str, const char * format, ... );//与printf相似,只比其多了第一个参数
使用示例:
#include<stdio.h>
struct S
{
char name[20];
int age;
float weight;
};
int main()
{
struct S s={"zhangsan",18,65.2f};
char buf[100]={0};
sprintf(buf,"%s %d %f",s.name,s.age,s.weight);
printf("%s\n",buf);
return 0;
}
四.文件的随机读写
4.1fseek
作用:指定文件流的读写位置,想从文件哪里读 / 写,就把指针移到哪里。
格式:int fseek ( FILE * stream, long int offset, int origin );
- 第二个参数是偏移量
- 第三个参数是起始位置
| 常量 | 参考位置 |
| SEEK_SET | 文件开头 |
| SEEK_CUR | 文件指针的当前位置 |
| SEEK_END | 文件末尾 |
使用示例:
#include<stdio.h>
int main()
{
FILE*pf=fopen("test.c","r");//test.c文件里存放的内容:abcdef
if(pf==NULL)
{
perror(open:);
return 1;
}
fseek(pf,2,SEEK_SET);//与文件开头位置的偏移量为2(偏移量可以为负数)
int ch=fputc(pf);
printf("%c",ch);//c
fseek(pf,2,SEEK_CUR);
ch=fputc(pf);
printf("%c",ch);//f
return 0;
}
疑问:第二fseek为什么偏移量是 2 而不是 3?
解释:关键在于:fgetc 读取一个字符后,文件指针会自动向后移动 1 位。
4.2ftell
作用:返回文件指针当前的读写位置(距离文件开头的字节偏移量)。
格式:long int ftell ( FILE * stream );
使用示例:
fseek(pf,2,SEEK_SET);//与文件开头位置的偏移量为2(偏移量可以为负数)
int ch=fputc(pf);
printf("%c\n",ch);//c
printf("%d\n",ftell(pf));//3
fseek(pf,2,SEEK_CUR);
ch=fputc(pf);
printf("%c",ch);//f
printf("%d\n",ftell(pf));//6
4.3rewind
作用:让文件指针的位置回到文件的起始位置
格式:void rewind ( FILE * stream );
使用示例:
fseek(pf,2,SEEK_SET);
int ch=fputc(pf);
printf("%c\n",ch);//c
printf("%d\n",ftell(pf));//3
rewind(pf);
ch=fputc(pf);
printf("%c",ch);//a
printf("%d\n",ftell(pf));//1
五.文本文件和二进制文件
根据数据的组织形式,数据文件被称为文本文件或者二进制文件
二进制文件:数据在内存中以二进制的形式存储,不加转换的输出到外存(我们看不懂)
文本文件:在外存上以ASCLL字符的形式存储的文件(我们看得懂)
数据在内存中的存储方式:
- 字符一律以ASCLL形式存储
- 数值型数据既可以以ASCLL形式存储,也可以以二进制形式存储
以10000为例:

把10000转换为16进制,也就是 10000 = 0x00002710
当为大段存储时:00 00 27 10
当为小段存储时:10 27 00 00
六.文件读取结束的判定
6.1错误地使用feof
6.1.1feof
作用:检测上一次读操作是否遇到了文件末尾,千万不能用来预判文件有没有结束
应用于:判断是否读取失败结束,还是遇到文件尾结束
格式:int feof ( FILE * stream );
返回值:文件结束 → 返回非 0 值;没结束 → 返回 0
feof(pf)!=0:上一次读操作正常碰到文件末尾,正常结束
使用示例:
char buf[100];
while(fgets(buf, 100, fp) != NULL)
{
printf("%s", buf);
}
if(feof(fp))
{
printf("正常读到末尾\n");
}
6.1.2ferror
格式:int ferror ( FILE * stream );
作用:判断是否因【读取错误】结束
返回值:上一次文件操作发生错误->返回非 0;操作正常,无错误->返回 0
ferror(fp) != 0:上一次读操作发生 IO 错误,异常失败结束
总结:
- feof:只管是不是撞到文件尾、正常读完
- ferror:只管是不是读写错误、异常失败
6.1.3用来判断文件是否读取结束的方法
1.文本文件是否读取结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)
- fgetc判断是否为EOF
- fgets判断返回值是否为NULL
2.二进制文件是否读取结束,判断返回值是否小于实际要读的个数
- fread判断返回值是否小于实际要读的个数

count是我们实际要读的个数(我们要fread读的),而fread的返回值等于fread实际读到值的个数
- 文件读到末尾:已经读完文件里所有有效数据,此时缓冲区还有最后一个有效数据
-
文件读取结束:在读完所有数据后,又尝试再读一次,读操作失败
七.文件缓冲区
文件缓冲区:是C 语言标准库在内存中开辟的一块临时存储区域,用来暂存程序和磁盘文件之间的数据。缓冲区的大小根据编译系统决定

数据先写到输出缓冲区中,等到输出缓冲区满了后,再一次性写到硬盘中,但是也有库函数可以设置缓冲区的大小
因为有缓冲区的存在,C 语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件有问题
&spm=1001.2101.3001.5002&articleId=160382109&d=1&t=3&u=136991ea99154a1f836db95a84fd55f1)
1803

被折叠的 条评论
为什么被折叠?



