当大家学习过动态内存开辟后,那么我们就已经可以把内存的每一个地方使用了。但是大家有没有想过,我们现在是在自己电脑上打代码。以后工作了,自己写代码在自己电脑上,老板要是想要一份代码看看,或者你成为大能了,自己有一个很厉害的代码。别人想买你想卖,但是你代码只在自己电脑上。是不是说发个文件就好了呀。所以我们除了将这个整个代码发出去,我们还可以再编译器里面创建一个文件,然后发送,这样的话,只会发出这个文件的代码,不会波及其他的源代码。其实鄙人的讲述其实是不好的,反正大家了解一下嘛,技多不压身。后面肯定是会用上的。我这里就简单的列举几个我们刚开始使用文件处理的库函数吧。
文件分类
我们这里是了解关于文件的知识。首先要知道什么是文件吧。我们觉得简单理解的话,就是放在硬盘/磁盘里面的数据就是文件。嘿嘿这是个人的狭义之见。反正大家大概知道什么是文件就行了吧。我们这一小节是将讲文件的分类。文件分为:程序文件、数据文件
那什么是程序文件嘞,包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。什么是数据文件嘞,文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。也就是说后缀是.c或者.obj,.exe的是程序文件。数据文件就是一堆数据。这个通俗易懂吧。
好了,当我们知道什么是文件后,那么我们文件内容其实也是区分了的。比如我们都知道在C语言中存储中不改变什么的话,就是存储的二进制,因为0/1嘛最简单。但是不能只有1二进制啊。肯定还要有其他的。那么这个时候我们以前也学过的ascll码值就跳出来了。我们知道二进制无聊如何存储都只能是数字无法存储字符或者其他的但ascll码值就不一样了。可以存储字符和其他的特殊符号。所以我们可以根据数据的组织形式,数据⽂件被称为⽂本⽂件或者⼆进制⽂件。以ASCII字符的形式存储的⽂件就是文本⽂件。二进制文件就是以二进制存储的。
我们以存储10000为例子,分别存储二进制形式与ascll形式。如果以ASCII码的形式输出到磁盘,则磁盘中占⽤5个字节(每个字符⼀个字节),⽽ ⼆进制形式输出,则在磁盘上只占4个字节。然后我们可以在vs编译器里面点击文件的打开方式来选着二进制,大家可以在自己电脑上看一看。
文件打开与关闭
大家想一想我们文件打开与关闭,是不是像看书。我们看书开头需要一个打开书本的先前动作。那么等我们看完书一般还有一个关书的动作吧。
1.文件指针
文件系统中,关键的概念是“文件类型指针”,简称**“文件指针”**。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE。其实大家不用太在意这个的,我觉得大家只需要知道我们在文件处理的时候是有一个文件指针的。然后开头写的是FILE。然后这个指针指向的是文件就可以了。
好了,我们知道了打开书要有一个动作然后关闭书还要有一个动作,那么我们打开和关闭是什么函数嘞。ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。当然大家一定要记得关闭不然一阵风吹过,不仅吹乱了我们的秀发还将我们以前“看书”的记录打乱了。
//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream );
文件的顺序读写
当我们知道文件的打开与关闭了后,那么我们就要了解如何做事情了是吧。而且在上面的图片中也出现过关于文件读写的库函数了。那么接下来我就将以这个为主要的讲述对象来给大家讲一讲。首先在这里,我们都是一页一页的看书的,就是如同我们打字都是一个一个打的,就算速度再快或者一次性打几个字,但是都是从第一个向后的。文件读写也是这样的,像我们鼠标有一个光标,打一个字向后一个。这就是标题的顺序读写。
然后就是我在开头与大家将够的文件的输入与输出大体是不一样的,这里我就画了一个简单示意图,让大家理解的更清楚一些。
那么接下来我们就来看我们本章节重要的知识,读与写的库函数。那么我们还是先看一下关于文件顺序读写有哪些库函数吧。
那么我们也还是讲两个比较特殊的fread与fwrite进行对比,然后其他的大家下来自己尝试一些,这样大家的印象更加深刻。
fwrite:不知道大家有没有看懂我上面的这个代码,我是将数组arr里面的5个元素给输入到了文件yh.txt里面,大家可以看到下面的记事本,那个就是以二进制的形式存储进去的样子。我看肯定是看不懂的。反正大家只需要知道我们存储进去成功了就可以了。并且大家要注意上面fopen使用的是wb是输出不是输入哦。这里我们再提醒一下不要搞混了。我们用的输入就是输入,是输出就是输出。如果一点迷糊就,大家可以看看我上面的那张红色图片。
int main()
{
int arr[] = { 1,2,3,4,5};
FILE* pf = fopen("yh.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int sz = sizeof(arr[0]) / sizeof(arr[0]);
fwrite(arr, sizeof(arr[0]), 5, pf);
fclose(pf);
pf = NULL;
return 0;
}
那么我们现在已经将arr数组里面的内容以二进制形式写到文件里面去了,那么我们是不是应该尝试将它拿出来让我们看看,是否正确啊。那么我们就看看上面的图片与fwrite相对应的是fread。一个输出那么后者就是输入了。好我们还是写一个代码大家来看看是怎么个事
fread ⼆进制输⼊ ⽂件输⼊流:就是二进制的输出。(写进的数组名,写进的元素大小,几个元素,开始打开文件的指针)
int main()
{
int arr[] = { 1,2,3,4,5};
int arr1[10] = { 0 };
//FILE* pf = fopen("yh.txt", "wb");
FILE* pf = fopen("yh.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
/*int sz = sizeof(arr[0]) / sizeof(arr[0]);
fwrite(arr, sizeof(arr[0]), 5, pf);*/
fread(arr1, sizeof(arr[0]), 5, pf);
for (int a = 0; a < 5; a++)
{
printf("%d ", arr1[a]);
}
fclose(pf);
pf = NULL;
return 0;
}
这里我就给大家讲了顺序读写中的两个比较特殊的库函数。其他的使用方法都大同小异,如果大家感兴趣的话,后面可以在编译器里面尝试一下。
fseek
大家看到这里说明已经将上面的文件顺序读写看完了吧。那么我们肯定不是只想一个一个顺序读写吧,那么我们现在来讲一个文件偏移的库函数。fseek。首先我们要知道fseek有什么作用。它是我们指向的文件然后写偏移多少,光标就偏移多少。(光标就是我们前面说的鼠标打字)。下面的这个库函数的基本格式。
int fseek(FILE *stream, long int offset, int whence)
- stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
- offset -- 这是相对 whence 的偏移量,以字节为单位。
- whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
int main()
{
FILE * pFile;
pFile = fopen("example.txt", "wb");
fputs("wxhn 0", pFile);
//fseek(pFile, 4, SEEK_SET);
fputs("1314", pFile);
fclose(pFile);
return 0;
}
ftell
好了,当我们知道可以指定光标位置做事情了后,那么假如我们以后写代码写了几百行几千行的话,可能就忘了光标在那个位置了。那么这个时候我们就应该在想有没有什么库函数可以告诉我现在光标在哪里了。好这个时候ftell来了,这个就是告诉我们现在光标在那个位置。这里我们都不深究,一是鄙人本来实力也不是很强,怕班门弄斧了。二是鄙人觉得大体时间我们知道如何使用就可以了,太深层的话,等我们慢慢的成为大拿后自然也懂了。
long int ftell ( FILE * stream );
rewind
不知道大家是否听过这样一句话,“你若回头,会发现我一直都在”那么我们在这里不能一直向前走啊,肯定要有回到梦最开始额地方啊。并且我们前面也只是说了一个fseek移动光标做事情啊。并没有一个直接回到开头的位置的库函数啊。那么我们的rewind就派上用场了。它的作用就是让光标回到文件的起始位置。
void rewind ( FILE * stream );
文件读取结束的判定
在我们使用读取文件的函数时,都讲解了其返回值,我们可以通过其返回值来判定对文件的读取是否成功(这里不再一一举例)。补充一个用来判断文件为什么结束读取的函数:
feof函数 (包含在头文件stdio.h中)对于文件读取结束之后,我们可以使用feof函数来文件是为何而读取结束的:
fgets 判断返回值是否为 NULL.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if (!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
}
正确使用feof函数的例子:
#include <stdio.h>
enum { SIZE = 5 };
int main(void) {
double a[SIZE] = { 1., 2., 3., 4., 5. };
FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式
fwrite(a, sizeof *a, SIZE, fp); // 写 double 的数组
fclose(fp);
double b[SIZE];
fp = fopen("test.bin", "rb");
size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读 double 的数组
if (ret_code == SIZE) {
puts("Array read successfully, contents: ");
for (int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
putchar('\n');
}
else { // error handling
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
}
fclose(fp);
}
补充
上面写的输入输出,与我们日常理解的输入输出不一样,这里的输入输入给我们(相当于我们平常的输出),那么这个里面的输出也是一样的了吧。所有输入流表示文件或者标注都是可以的
sprint:创建一个字符串数组,然后还有一个结构体赋初始值,使用sprintf的话会将这些内容装换位字符串并存储在写的那个字符串数组中。sprintf(接收数据的数组,传入数组中的内容),把格式化的数据装换为字符串
sscanf:把字符串装换为格式化的
好了以上就是鄙人想与大家分享的鄙见了。肯定还有很多错误和不足之处,希望大家可以不吝赐教,在评论区里面指出。