一、文件作用:
· 将数据进行持久化的保存;防止数据因为程序退出,内存的回收而丢失
二、文件分类:
1、磁盘文件:
顾名思义硬盘或者磁盘上的文件
2、在程序上:
1)、程序文件:
(我们写程序时,写下的文件以及编译链接的文件)
源文件: 后缀为 .c 文件
目标文件:后缀为 .obj 文件(Windows环境下) .o文件(Linux环境)
可执行文件:后缀为 .exe文件
2)、数据文件:
(程序运行时读写的数据)
1->文本文件:
内存中以二进制的形式存储,要求在外存上以ASCII字符的形式存储的文件
👉字符数 取决于✨数值位数 (这里不考虑大小端字节序)就是说3位数 就是占用 3字节
0011这个是固定的 理解为前缀,因为我们将多位数以ASCII码值存储,但是ASCII码值有限的,所以将多位数拆分成 个位数存储 ,这样就不会出现存不下的情况
2->二进制文件:
以二进制的形式存储,不加以转化 直接存储到外存的 文件
👉✨字符数(字节数)不取决于数值位数(不考虑大小端字节序)也就是64位环境下int 占4个字节
注意:
后面空的部分是不要的!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!😅画多了
三、流的引入:
为了方便程序员对各种设备进行操作,引入了流这一概念,我们可以将流想象成一条流淌着字符的 河流
介绍一个类型: FILE* —— 文件指针
1、标准流:
1、标准输入流:stdin
读取普通输入的流,大多环境是键盘输入,scanf与getchar等函数会在这个流读取 字符!
2、标准输出流:stdout
写入普通输出的流,大多环境是输出至显示器界面,printf、putchar、puts等函数 会向这个流写入字符
3、标准错误流:stderr
用于写出错误的流,大多环境是输出至显示器界面。
🧑🎓很好记就是stdio的头文件不要io 后面接 in out err🧑🎓
2、文件流:
我们创建的文件就是文件流;可以通过不同函数进行操作,向文件进行读取或者写入,根据不同模式下 文件的角色也是不同的,可以当输出流,可以当输入流
总之就是:文本既可以是输入流也可以是输出流!
四、文件操作:
1、文件的打开和关闭:
✨可以想象一下,我们喝水的时候👉顺序是怎么样的呢??
打开水瓶——>检查水是否能喝,是否是饮用水——>喝水(装水)——>拧上盖子
文件的打开也是如出一辙
(1)、打开文件:
fopen就是打开文件 传的本质其实是字符串
FILE* fopen(const char* filename, const char* mode);
以写的形式打开:
创建一个文件指针接收这个文件!!
(2)、检查是否正确:
✨进行检查,竟然是指针类型的函数,那么发生错误则就是返回空指针(NULL)
若是为空,return 1;表示🐸提前返回的意思!
perror检查是否发生错误,若是错误则会打印在屏幕上
(3)、关闭文件
fclose用来关闭文件
int fclose(FILE* stream);
关闭了文件,防止我们创建的文件指针变成野指针,要把他变成空指针!!
✨✨对于指针一定要记住不让 野狗 发疯
2、读取以及写入的模式介绍:
下面就是关于文件的打开方式:看表格即可
3、文件顺序读写:
读:将文本看成输入流(相当于键盘),从中读取!
写:将文本看成输出流(相当于屏幕),写入里面!
A、字符输入函数 fgetc(): 向文件读取
将文本看成输入流,从中读取字符,返回字符的ASCII码值!可以用int 型去接收
看下画红线部分,进去时有一个光标,你读取一个字符光标会右移,直到读完,,下面用的函数也是如此,不在赘述
✨该函数读取:
——>成功返回读取的字符
——>到达末尾时就会返回EOF
——>失败也是返回EOF,并且会标记这个错误;(perror可以检查)
注意:以下为例子注意,读取方式r 的含义,要文件存在才行!!!!!!
int main()
{
//从文件读取;
FILE* pf2 = fopen("test.txt", "r");
int ch = 0;
while ((ch = fgetc(pf2))!=EOF)
{
printf("%c ", ch);
}
fclose(pf2);
pf2 == NULL;
return 0;
}
B、字符输出函数fputc(): 向文件写入
✨文件可以看出输出流, 传你需要传的字符,写入到指定的文件里面去;
该函数:
——>成功返回写入的字符
——>失败则返回EOF,并且会标记这个错误;(perror可以检查)
实现:
int main()
{
FILE* pf1 = fopen("test.txt", "w");
if (pf1 == NULL)
{
perror("fopen");
return 1;
}
//写文件;写入;
char c = 0;
for (c = 'a'; c <= 'z'; c++)
{
fputc(c, pf1);
}
fclose(pf1);
pf1 == NULL;
return 0
}
C、文本行输入函数fgets():向文件读取
文本可以看成流
注: 一行一行进行读取,将读取到的放到空间里面,因为是ASCII码值的形式存入,所以一个字节一个字符;然后最后一个后间会给\0,相当于到第19个字符时会补\0,若是所在行读取字符不够了\n也会读取掉然后再补\0
该函数:
失败——>返回NULL
到达文件末尾——>返回NULL
实现一下:
我用while循环进行
int main()
{
//以读的形式打开文件
FILE *pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
//提前返回!
return 1;
}
//读取文件一行的字符
char arr[20] = { 0 };
//到19个字符会自动补\0;
while ((fgets(arr, 20, pf)) != NULL)
{
printf("%s", arr);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
D、文本行输出函数fputs():向文件输入
将想要写入的数据写入文本中,根据光标走,不会自动换行,需要自己带\n;
一直写入,碰到\0就停止写入
注意:不会将\0写入到里面!!
实现:
int main()
{
//以写入的模式打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[20] = { "hellow word\n" };
//以文本行输出(写入),要换行就需要写 \n !!!!
fputs(arr, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
E、格式化输入fscanf():写法与scanf相同,多了一个流
有点抽象,但是看例子很容易理解
这个写法一般用结构体来读取文本;并非只用来写结构体
若没有执行任何转换则是发生输入错误,返回EOF(-1);
若成功了返回成功输入的项数;
若发生错误返回比输入的项数少的实参个数,甚至是0:
实现:
int main()
{
struct st s = { 0 };
//以读取模式打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fscanf(pf, "%s%d%lf",s.name,&(s.ege),&(s.hight));
printf("%s %d %f", s.name, s.ege, s.hight);
//关闭
fclose(pf);
pf = NULL;
return 0;
}
F、格式化输出函数fprintf():写法与printf相同,多了一个流
写入,和上E的填写内容差不多
将内容写入到文件里面
成功——>返回发送的字符个数
发生错误——>返回负值
struct st {
char name[128];
int ege;
double hight;
};
//int fprintf(FILE* stream,const char* format,……);
int main()
{
struct st s = { "zhangsan",22,1.78 };
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//fprintf写入(文本形式写入)
fprintf(pf, "%s %d %.1f",s.name,s.ege,s.hight);
fclose(pf);
pf = NULL;
return 0;
}
总结下:
其实上面ABCDEF也适用于标准流使用,将文件指针的位置,写入标准流一样能达到效果,就像:fscanf 给他里面加入 stdin 与scanf的效果是一样的
G、二进制输入fread():
返回成功读取的元素个数
当发生错误或者读到文件末尾时,元素个数会少于nmemb个
实现如下:
int main()
{
int arr[10] = {0};
//打开文件
FILE* pf = fopen("sred.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读取
int cont = fread(arr, sizeof(arr[0]), 6, pf);
for (int i = 0; i < cont; i++)
{
printf("%d ", arr[i]);
}
//关闭文件
fclose(pf);
pf = 0;
return 0;
}
H、二进制输出fwrite():
返回写入成功的个数
若发生错误或者到达文件末尾 返回的个数少于nmemb
实现:
int main()
{
int arr[10] = { 1,2,3,4,5,6 };
//打开文件
FILE* pf = fopen("sred.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int sz = sizeof(arr) / sizeof(arr[0]);
//写入
fwrite(arr, sizeof(arr[0]), sz, pf);
//关闭文件
fclose(pf);
pf = 0;
return 0;
}
对上述总结:看下表
4、随机读取:
1》根据文件指标位置和偏移量来定位文件指针 fseek():
有如下三种选择:
int main()
{
FILE* pf1 = fopen("test.txt", "w");
if (pf1 == NULL)
{
perror("fopen");
return 1;
}
//写文件;写入;
char c = 0;
for(c = 'a'; c <= 'z'; c++)
{
fputc(c, pf1);
}
fclose(pf1);
pf1 == NULL;
//从文件读取;打开文件
FILE* pf2 = fopen("test.txt", "r");
int ch = 0;
while ((ch = fgetc(pf2))!=EOF)
{
printf("%c ", ch);
}
fseek(pf2, 3, SEEK_SET);
putchar('\n');
while ((ch = fgetc(pf2)) != EOF)
{
printf("%c ", ch);
}
fclose(pf2);
pf2 == NULL;
return 0;
}
起始位置在a的前面为初始位置
2》返回文件指针相当于起始位置的偏移量ftell():
实现如下:
int main()
{
FILE* pf1 = fopen("test.txt", "w");
if (pf1 == NULL)
{
perror("fopen");
return 1;
}
//写文件;写入;
char c = 0;
for(c = 'a'; c <= 'z'; c++)
{
fputc(c, pf1);
}
fclose(pf1);
pf1 == NULL;
//从文件读取;打开文件
FILE* pf2 = fopen("test.txt", "r");
int ch = 0;
while ((ch = fgetc(pf2))!=EOF)
{
printf("%c ", ch);
}
fseek(pf2, 3, SEEK_SET);
putchar('\n');
while ((ch = fgetc(pf2)) != EOF)
{
printf("%c ", ch);
}
putchar('\n');
long int s = ftell(pf2);
printf("%d", s);
fclose(pf2);
pf2 == NULL;
return 0;
}
3》让文件指针的位置回到文件起始位置rewiind():
int main()
{
FILE* pf1 = fopen("test.txt", "w");
if (pf1 == NULL)
{
perror("fopen");
return 1;
}
//写文件;写入;
char c = 0;
for(c = 'a'; c <= 'z'; c++)
{
fputc(c, pf1);
}
fclose(pf1);
pf1 == NULL;
//从文件读取;打开文件
FILE* pf2 = fopen("test.txt", "r");
int ch = 0;
while ((ch = fgetc(pf2))!=EOF)
{
printf("%c ", ch);
}
fseek(pf2, 3, SEEK_SET);
putchar('\n');
while ((ch = fgetc(pf2)) != EOF)
{
printf("%c ", ch);
}
putchar('\n');
long int s = ftell(pf2);
printf("%d", s);
//光标会到初始位置
rewind(pf2);
putchar('\n');
s = ftell(pf2);
printf("%d", s);
pf2 == NULL;
return 0;
}