C生万物之文件操作

文章目录

    • 一、为什么使用文件?
    • 二、什么是文件?
      • 1、程序文件
      • 2、数据文件
      • 3、文件名
    • 三、文件的打开和关闭
      • 1、文件指针
      • 2、文件的打开和关闭
    • 四、文件的顺序读写
      • 1. 8个重要的库函数
        • 1.1 单字符输入输出【fputc和fgetc】
        • 1.2 文本行输入输出【fputs和fgets】
        • 1.3 格式化输入输出【fprintf和fscanf】
        • 1.4 二进制输入输出【fwrite和fread】
      • 2、拓展:默认打开的三个流
      • 3、对比一组函数
    • 五、文件的随机读写
      • 1、fseek
      • 2、ftell
      • 3、rewind
    • 六、文本文件和二进制文件
    • 七、文件读取结束的判定
      • 1、被错误使用的feof
      • 2、fgetc、fgets、fscanf、fread结束判断解读
      • 3、实例代码走读
    • 八、文件缓冲区
    • 拓展:文件外排序
      • 1、前言
      • 2、思路解析
      • 3、代码详解

一、为什么使用文件?

  • 我们前面学习结构体时,写了通讯录的程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受

  • 所以就想到了通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在【磁盘文件】、存放到【数据库】等方式

二、什么是文件?

  • 磁盘上的文件是文件

  • 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)

1、程序文件

  • 包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。

  • 【程序文件】一般指的是我们创建工程时所编写的代码,也就想下面这个【test.c】一样
    在这里插入图片描述

2、数据文件

  • 文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

  • 【数据文件】一般指通过程序去操纵的那个文件

  • 就想上面的这个【test.txt】就是一个数据文件,通过【test.exe】运行起来时,内存中有有了数据,此时我们可以将数据写到这个【test.txt】中,自然也可以从这个文件中读取数据到内存中

3、文件名

  • 一个文件要有一个唯一的文件标识,以便用户识别和引用

  • 文件名包含3部分:文件路径+文件名主干+文件后缀

  • 例如:c:\code\test.txt

  • 为了方便起见,文件标识常被称为文件名。

三、文件的打开和关闭

1、文件指针

  • 缓冲文件系统中,关键的概念是文件类型指针,简称文件指针

  • 每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE

例如,VS2019编译环境提供的 stdio.h 头文件中有以下的文件类型申明:

struct _iobuf {char *_ptr;int   _cnt;char *_base;int   _flag;int   _file;int   _charbuf;int   _bufsiz;char *_tmpfname;};
typedef struct _iobuf FILE;
FILE* pf;//文件指针变量
  • 不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节
  • 一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。我们来看看如何创建一个FILE*的指针变量
FILE* pf;	//文件指针变量
  • 定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件

在这里插入图片描述

2、文件的打开和关闭

注:文件在读写之前应该先打开文件,在使用结束之后应该关闭文件

  • ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。显示如何打开和关闭文件的个格式
//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream );

  • 下面是文件的一些打开方式,有很多的操作,大家挑重点记就行

注:a即append(追加);b即binary(二进制)

文件使用方式含义如果指定文件不存在
【重点】“r”(只读)为了输入数据,打开一个已经存在的文本文件出错
【重点】“w”(只写)为了输出数据,打开一个文本文件建立一个新的文件
【重点】“a”(追加)向文本文件尾添加数据建立一个新的文件
rb”(只读)为了输入数据,打开一个二进制文件出错
“wb”(只写)为了输出数据,打开一个二进制文件建立一个新的文件
“ab”(追加)向一个二进制文件尾添加数据出错
“r+”(读写)为了读和写,打开一个文本文件出错
“w+”(读写)为了读和写,建议一个新的文件建立一个新的文件
“a+”(读写)打开一个文件,在文件尾进行读写建立一个新的文件
“rb+”(读写)为了读和写打开一个二进制文件出错
“wb+”(读写)为了读和写,新建一个新的二进制文件建立一个新的文件
“a+”(读写)打开一个二进制文件,在文件尾进行读写建立一个新的文件

例:

int main()
{//打开文件FILE* pf = fopen("test.txt", "w");if (NULL == pf){perror("fail fopen");return 1;}//写文件// ...//关闭文件fclose(pf);pf = NULL;		//防止野指针return 0;
}

四、文件的顺序读写

1. 8个重要的库函数

  • 下面的8个库函数都很重要,大家最好都要记住,而且对于它们的用法也要熟知
功能函数名适用于
字符输入函数【读】fgetc所有输入流
字符输出函数【写】fputc所有输出流
文本行输入函数【读】fgets所有输入流
文本行输出函数【写】fgets所有输入流
格式化输入函数【读】fscanf所有输入流
格式化输出函数【写】fprintf所有输入流
二进制输入【读】fread文件
二进制输入【写】fwrite文件

对于上面的这些函数的使用最关键的一点就是:【读】对应的输入流,【写】对应的输出流

  • 在初识C语言时,我们学习了【scanf】和【printf】,只要了如何从键盘读取数据,然后将数据显示在屏幕上

在这里插入图片描述

  • 现在我们可以从键盘、屏幕过渡到文件,也可以从文件读、写数据

1.1 单字符输入输出【fputc和fgetc】
  • 首先我们去cplusplus里面找到这两个函数的描述
int fputc ( int character, FILE * stream );
int fgetc ( FILE * stream );

在这里插入图片描述

在这里插入图片描述

好,有了一个基本的了解后,我们就到VS2019中去实操一下

  • 首先是写文件,我们往【test.txt】中写一个字符a进去
#include<stdio.h>
int main()
{//打开文件FILE* pf = fopen("data.txt", "w");if (NULL == pf){perror("fopen");return 1;}//写文件fputc('a', pf);//关闭文件fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述

  • 既然能写一个,那也就可以写多个

在这里插入图片描述

  • 那我们能不能将26个字母都写进去呢?当然是可以的,不过不是这么一句一句写,要用循环来写
for (char ch = 'a'; ch <= 'z'; ch++)
{fputc(ch, pf);
}

在这里插入图片描述


  • 可以写数据了,那能不能将我们写进去的内容再读出来呢,这就要用到 fgetc() 了,而且在打开文件的时候要以【读】也就是【r】的形式打开
  • 既然是读取数据,那我们就要去接收读到的这个数据,刚才看到这个库函数的返回值是【int】,是一个ASCLL码值,所以我们就这么去接收
int ch = fgetc(pf);
printf("%c", ch);

在这里插入图片描述

  • 能读一个,那也能读多个,我们多读几个试试
int ch = fgetc(pf);
printf("%c", ch);ch = fgetc(pf);
printf("%c", ch);ch = fgetc(pf);
printf("%c", ch);ch = fgetc(pf);
printf("%c", ch);

在这里插入图片描述

  • 然后我们再把这26个字母都读出来试试
for (int i = 0; i < 26; ++i)
{printf("%c", fgetc(pf));
}

在这里插入图片描述

  • 但是呢,我们平常在读取文件中内容的时候,并不知道里面有什么东西,有多少东西,因此应该写一个通过的程序,才能适应更多的情况
  • 我们再仔细看看fgetc的简述。可以看到当它读到文件末尾的时候便会返回EOF,即End Of File(文件结束)

在这里插入图片描述

  • 此时我们就可以将代码写成这样。将for循环改为while循环
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{printf("%c", ch);
}

可以看到,一样是可以显示出来的

在这里插入图片描述

1.2 文本行输入输出【fputs和fgets】
  • 首先来了解一下这个两个函数

fputs
fgets

int fputs ( const char * str, FILE * stream );
char * fgets ( char * str, int num, FILE * stream );

在这里插入图片描述
在这里插入图片描述

  • 然后我们像向文件中写入一个字符串试试

在这里插入图片描述

  • 接下去多写几行试试

在这里插入图片描述


  • 可以写东西进去了,接下去一样,将我们写的东西读出来试试
  • 可以看到在读取结束之后DeBug和显示窗口都可以看到只有四个字符,并没有5个,这是为什么呢?似乎是读到了一个换行符

在这里插入图片描述

  • 我们再仔细地观察一个这个函数

在这里插入图片描述

  • 看了一些官方文档的描述,应该清楚为什么会只有四个了吧,
  • 若是最大字符数num < 本行的字符数,那么就会显示【num - 1】个,最后一个给到【\0】,也就是对于字符串而言的结束符
  • 若是最大字符数num > 本行的字符数,那么除了显示本行的所有字符之外,还会读入一个换行符,接着就不会往下读了。若是需要读取下一行数据,则需要再次使用这个函数进行读取

在这里插入图片描述

1.3 格式化输入输出【fprintf和fscanf】

fprintf
fscanf

  • 看到这个【fprintf】和【fscanf】是不是又想起来我们之前学的【printf】和【scanf】呢,我们对其进行一个对比。如下图所示
int fprintf ( FILE * stream, const char * format, ... );
int fscanf ( FILE * stream, const char * format, ... );

在这里插入图片描述
在这里插入图片描述

  • 既然是进行格式化的输入输出,那我们就来尝试写一些不同格式的内容到文件里去,这里直接定义一个结构体
typedef struct student {char name[20];int height;float score;
}st;
st s = { "zhangsan", 175, 95.5 };//写文件
fprintf(pf, "%s %d %f", s.name, s.height, s.score);
  • 可以看到,就写进去了

在这里插入图片描述

还是一样也可以读出来打印

在这里插入图片描述

1.4 二进制输入输出【fwrite和fread】

fwite
fread

然后继续看一下

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

在这里插入图片描述
在这里插入图片描述

  • 可以看到文件中是写入了一些数据,但是呢写进去的东西是乱码的样子,看不太懂

在这里插入图片描述

  • 既然都是以二进值写的,那么我们也需要以二进值读
    可以看到,以二进值的方式读出来了
    在这里插入图片描述

2、拓展:默认打开的三个流

对于任何一个C语言程序,只要运行起来,就会默认地打开三个流

  • stdin - 标准输入流 - 键盘
  • stdout - 标准输出流 - 屏幕
  • stderr - 标准输错误 - 屏幕

  • 通过观看源码可以知晓,他们都是以宏定义的形式存放在内存中的,之前我们说过,对于宏定义而言是在程序开始之前就定义好的,也就是当程序运行起来之后,那它们就会存在了

在这里插入图片描述

  • 然后我们去程序中运行一下试试
int ch = fgetc(stdin);
fputc(ch, stdout);
  • 然后可以看到,我们确实可以使用【stdin】和【stdout】这两个流来进行输入和输出

在这里插入图片描述

int ch = 0;
fscanf(stdin, "%c", &ch);
fprintf(stdout, "%c", ch);

在这里插入图片描述

3、对比一组函数

  • 上面讲到了8个有关文件顺序读写的库函数,接下去给大家对比一下一组函数
    • scanf / fscanf / sscanf
    • printf / fprintf / sprintf

在这里插入图片描述

  • 主要还是来看看【sprintf】和【sscanf】这两个新面貌
int sprintf ( char * str, const char * format, ... );

在这里插入图片描述

int sscanf ( const char * s, const char * format, ...);

在这里插入图片描述

  • 接下去我们通过代码来看看是不是真的可以实现
char buf[100] = { 0 };
st s = { "zhangsan", 170, 95.5f };
st tmp = { 0 };//能否将这个结构体的成员转化为字符串
sprintf(buf, "%s %d %f", s.name, s.height, s.score);
printf("%s\n", buf);//能否将这个字符串中内容还原为一个结构体数据呢
sscanf(buf, "%s %d %f", tmp.name, &(tmp.height), &(tmp.score));printf("%s %d %f", tmp.name, tmp.height, tmp.score);
  • 可以看到,我将一个结构体数据以格式化的形式写到了一个字符串中,然后又从这个字符串中以格式化的形式读取数据到一个结构体变量中,这么转换来转换去,完全没有问题。

在这里插入图片描述


  • 那可能这么讲还是有点抽象,我们通过一个现实中开发的场景再来描述一下。比如说前端给到用户一个收集信息的表单,用户输入数据之后呢,前端就将这些信息用“+”号做了一个拼接给到后端,后端呢为了要识别这些信息,一定会创建一个结构体,里面包含这些信息的,这个时候就可以使用到我们上面所说的【sscanf】以格式化的方式去读取这个字符串了,然后就可以解析出用户的这些数据,然后去进行一个处理了
  • 当然在现实的软件开发中,是不会这么去做的,因为有现成封装的API可以调用,库里面会提供一个【序列化/反序列化】的API可以调用,开发者无需考虑其底层的实现

五、文件的随机读写

1、fseek

根据文件指针的位置和偏移量来定位文件指针

fseek

  • 首先来看看它的相关介绍
  • 可以看到,最重要的还是最后的那个参数,因为有三个选项可以使用。
int fseek ( FILE * stream, long int offset, int origin );

在这里插入图片描述

  • 然后通过代码我们再来实现一下这个功能。首先看到是使用到了【SEEK_SET】从文件的起始位置开始偏移,因为文件的起始是从第一个字符开始,向后偏移三位就到了【d】的位置

在这里插入图片描述
在这里插入图片描述

  • 接下来我们再来看一种。刚才是从前往后偏移,现在则是从后往前偏移,那就要使用到【SEEK_END】

在这里插入图片描述

  • 最后一个是【SEEK_CUR】,也就是从当前位置向后偏移

在这里插入图片描述

补充一个实际案例

FILE* pFile;
pFile = fopen("example.txt", "wb");
fputs("This is an apple.", pFile);
fseek(pFile, 9, SEEK_SET);
fputs(" sam", pFile);
fclose(pFile);
  • 偏移到This is a然后修改后面的内容

在这里插入图片描述

2、ftell

fteel

  • 返回文件指针相对于起始位置的偏移量
long int ftell ( FILE * stream );

在这里插入图片描述

  • 这个很简单,就是返回当前文件指针所在流中的位置

在这里插入图片描述
补充一个实际案例

FILE* pFile;
long size;
pFile = fopen("myfile.txt", "rb");
if (pFile == NULL) perror("Error opening file");
else
{fseek(pFile, 0, SEEK_END); //non-portablesize = ftell(pFile);fclose(pFile);printf("Size of myfile.txt: %ld bytes.\n", size);
}
  • 这个案例很巧妙地结合了我们上面所学过的【fseek】和【ftell】,求出了这个文件的字节大小

在这里插入图片描述

3、rewind

rewind

  • 让文件指针的位置回到文件的起始位置
void rewind ( FILE * stream );

在这里插入图片描述

  • 可以看到,我们又读到了a,表明文件指针pf确实回到到了起始位置

在这里插入图片描述


补充一个实际案例

int n;
FILE* pFile;
char buffer[27];
pFile = fopen("myfile.txt", "w+");
for (n = 'A'; n <= 'Z'; n++)fputc(n, pFile);rewind(pFile);	 //当文件指针pFile重新回到起始位置
fread(buffer, 1, 26, pFile);	//通过文件指针读入26个字母到buffer字符数组中
fclose(pFile);
buffer[26] = '\0';		//'\0'表示字符串的结束位置
puts(buffer);
  • 这个案例就是将1~26个大写英文字母写入文件,然后在让文件指针回到起始位置,在使用二进制的读取方式将文件中的内容读取到字符数组中,最后为字符串设置结束标志,打印出来便是文件中写入的内容

在这里插入图片描述

六、文本文件和二进制文件

  • 【二进制文件】:数据在内存中以二进制的形式存储。不加转换的输出到外存
  • 【文本文件】:以ASCII字符的形式存储的文件。在外存上以ASCII码的形式存储,则需要在存储前转换

字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储
在这里插入图片描述

上面就是一个十进制的数值10相关的两种存储形式,我测试了一下,以二进制的形式存放到文件里只占4个字节,但是以ASCLL码的形式存放到文件里就需要占5个字节

  • 接下去我们通过下面这段代码来看看二进制的存储形式
int main()
{int a = 10000;FILE* pf = fopen("test.txt", "wb");fwrite(&a, 4, 1, pf);//二进制的形式写到文件中fclose(pf);pf = NULL;return 0;
}

在这里插入图片描述

在这里插入图片描述

七、文件读取结束的判定

  • 牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束

1、被错误使用的feof

  • 去网上看很多的代码可以发现,大家几乎都错误地使用了【feof】这个函数,认为它和EOF一样就是用来判断文件是否结束,但是并不是这样,我们一起来探究一下这个函数

  • 从中我们可以知晓【feof】应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束

    在这里插入图片描述

2、fgetc、fgets、fscanf、fread结束判断解读

  • 对于上面的四个读取文件函数,要怎么去判断它们是否\结束呢?我们通过观察这些函数的返回值来看看

fgetc

  • 如果读取正常,返回读取到的字符的ASCLL码值
  • 如果读取失败,返回EOF

在这里插入图片描述


fgets

  • 如果读取正常,返回读取到的数据的地址
  • 如果读取失败,返回NULL

在这里插入图片描述


fscanf

  • 如果读取正常,返回的是格式串中指定的数据个数
  • 如果读取失败,返回的是小于格式串中指定的数据个数

在这里插入图片描述


fread

  • 如果读取正常,返回的是等于要读取的数据个数
  • 如果读取失败,返回的是小于要读取的数据个数

在这里插入图片描述

3、实例代码走读

文本文件操作

  • 首先通过文件指针以读的形式打开了这个文件,然后去判断一下是否打开成功。(fp == NULL)
  • 因为条件表达式为真也就是1的时候会进入if判断,那!pf == 1可以推出pf == 0等价于pf == NULL
  • 接下去的话就是去这个文件中一个读取内容然后输出,若是文件到达了EOF,也就是【fgetc】的结束判断条件,此时才可以使用【feof】去进行判断,所以可以看出【feof】是在文件结束之后去判断文件是因为什么而结束的。【ferror】若是成立的话表示这个文件是因为I/O的读取的问题中断的;若不是【feof】判断满足就表示其是正常结束
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);

二进制文件操作

  • 这是一个二进制的文件操作,所以以二进制的形式打开,然后使用二进制的写法【fwrite】从a这个数组的首元素地址开始拿取SIZE个大小为double的数据通过fp这个文件指针写出到文件中
    讲一下这里的(sizeof *a)是什么意思,a是数组的首元素地址,然后通过【 * 】解引用可以获取到每个数组元素的大小了
  • 数组a中的数据写入文件后,就要再打开这个文件然后将文件中的内容个读出来,我们将其保存在一个变量中然后对这个变量进行一个判断
  • 因为这个是一个二进制文件,因此我们要去判断它返回的个数是否小于需要读取的个数,若是成立则表示没有读完就结束了,若是和SIZE的个数相同的话表示都读完了,然后我们将读取到数组b中的内容输出一下即可
  • 若是没有读完但是文件又结束了,那么此时使用【feof】判断成立了,将不对的信息打印出来即可,若是没有到达文件末尾但是又读取结束了,进入了【ferror】的判断,表示文件的I/O流出现问题了
enum { SIZE = 5 };
int main()
{double a[SIZE] = { 1.,2.,3.,4.,5. };double b[SIZE];FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式fwrite(a, sizeof *a, SIZE, fp); // 写 double 的数组fclose(fp);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 handlingif (feof(fp))printf("Error reading test.bin: unexpected end of file\n");else if (ferror(fp)) {perror("Error reading test.bin");}}fclose(fp);
}

八、文件缓冲区

  • ANSIC 标准采用【缓冲文件系统】处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。

  • 从内存向磁盘输出数据【写】会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。

  • 如果从磁盘向计算机【读】入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。

  • 缓冲区的大小根据C编译系统决定的

下面是有关文件缓冲区的示意图
在这里插入图片描述

下面是实例代码

int main()
{FILE* pf = fopen("test.txt", "w");fputs("abcdef", pf);	//先将代码放在输出缓冲区printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");Sleep(10000);printf("刷新缓冲区\n");fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)//注:fflush 在高版本的VS上不能使用了printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");Sleep(10000);fclose(pf);//注:fclose在关闭文件的时候,也会刷新缓冲区pf = NULL;return 0;
}

拓展:文件外排序

1、前言

  • 对于文件中的数据,一般都是很大的,不像我们上面所讲的十二十个数,可能会有成千上百的数据需要我们去排序,此时效率最高的就是【归并排序】了,因为面对海量的数据而言,像效率较高的【快速排序】需要克服三数取中的困难,还有像【堆排序】【希尔排序】这些,都无法支持随机访问,所以很难去对大量的文件进行一个排序,速度会非常之慢。即使是有文件函数【fseek()】这样的函数可以使文件指针偏移,还是很难做到高效。因为磁盘的速度比起内存差了太多太多了,具体的我不太清楚大概有差个几千倍这样,
  • 所以我们就想到了【归并排序】,它既是内排序,也是外排序,而且性能也不差,算是速度较快的几个排序之一了。但是要如何进行归并呢?

2、思路解析

在这里插入图片描述

  • 回忆一下归并排序的原理,就是两个有序区有序,然后两两一归才使得整体可以有序,如果左右都无需,那么继续对其进行左右分割归并
  • 但是本次,我要教给你的你是另外一种思路:

将一个大文件平均分割成N份,保证每份的大小可以加载到内存中,然后使用快排将其排成有序再写回一个个小文件,此时就拥有了文件中归并的先决条件

  • 具体示意图如下

在这里插入图片描述

  • 这里我设置一个这样的规则,令文件1为【1】,文件2位【2】,它们归并之后即为【12】,然后再让【12】和文件3即【3】归并变成【123】,以此类推,所以最后归出的文件名应该是【12345678910】

3、代码详解

下面是大文件分割成10个小文件的逻辑,首先来讲解一下这块,代码中很多内容涉及到文件操作,如果有文件操作还不是很懂的小伙伴记得再去温习一下

  • 整体的逻辑就在于从文件中读取100个数据,但是分批进行读取,每次首先去读9个数,然后当读到第十个数的时候,先将其加入数组中,然后再对数组中的这10个数进行排序。排完序后就将这个10个数通过文件指针再写到一个小文件中
  • 接着当第二次循环上来的时候,就开始读第11~20个数;以此往复,直到读完这个100个数为止,那此时我们的工程目录下就会出现10个小文件,就是对这100个数的分隔排序后的结果
void MergeSortFile(const char* file)
{FILE* fout = fopen(file, "r");if (!fout){perror("fopen fail");exit(-1);}int num = 0;int n = 10;int i = 0;int b[10];char subfile[20];int filei = 1;//1.读取大文件,然后将其平均分成N份,加载到内存中后对每份进行排序,然后再写回小文件memset(b, 0, sizeof(int) * n);while (fscanf(fout, "%d\n", &num) != EOF){if (i < n - 1){b[i++] = num;	//首先读9个数据到数组中}else{b[i] = num;		//再将第十个输入放入数组QuickSort(b, 0, n - 1);		//对其进行排序sprintf(subfile, "%d", filei++);FILE* fin = fopen(subfile, "w");if (!fin){perror("fopen fail");exit(-1);}//再进本轮排好序的10个数以单个小文件的形式写到工程文件下for (int j = 0; j < n; ++j){fprintf(fin, "%d\n", b[j]);}fclose(fin);i = 0;		//i重新置0,方便下一次的读取memset(b, 0, sizeof(int) * n);}}
}
  • 我们来看一下排序的结果

在这里插入图片描述

  • 将大文件分成10个小文件后,接下去就是要对这个10个小文件进行归并,具体规则我上面已经说了
  • 下面就是单趟归并的逻辑的,就和我们上面说到的归并排序的代码是很类似的,只不过这里是文件的操作而已。要注意的是对于文件来说是有一个文件指针的,若是你读取了一个之后那么文件指针这个结构体中的数据标记就会发生变化,标记为当然所读内容的下一个了
  • 所以我们不能将读取读取小文件中的数据的操作放在while循环中,应该单独将其抽离出来进行判断才才对。若是哪个文件中的数小,那么就将这个数写到新的【mfile】文件中去,然后继续读取当前文件的后一个内容
//文件归并逻辑
void _MergeSortFile(const char* file1, const char* file2, const char* mfile)
{FILE* fout1 = fopen(file1, "r");if (!fout1){perror("fopen fail");exit(-1);}FILE* fout2 = fopen(file2, "r");if (!fout2){perror("fopen fail");exit(-1);}FILE* fin = fopen(mfile, "w");if (!fin){perror("fopen fail");exit(-1);}int num1, num2;//返回值拿到循环外来接受int ret1 = fscanf(fout1, "%d\n", &num1);int ret2 = fscanf(fout2, "%d\n", &num2);while (ret1 != EOF && ret2 != EOF){if (num1 < num2){fprintf(fin, "%d\n", num1);ret1 = fscanf(fout1, "%d\n", &num1);}else{fprintf(fin, "%d\n", num2);ret2 = fscanf(fout2, "%d\n", &num2);}}while (ret1 != EOF){fprintf(fin, "%d\n", num1);ret1 = fscanf(fout1, "%d\n", &num1);}while (ret2 != EOF){fprintf(fin, "%d\n", num2);ret2 = fscanf(fout2, "%d\n", &num2);}fclose(fout1);fclose(fout2);fclose(fin);
}

最后在打开文件后不要忘了将文件关闭哦,不然就白操作了

  • 当然上面是一个单趟的逻辑,我们还要对【file1】【file2】【mfile】进行一个迭代
//利用互相归并到文件,实现整体有序
char file1[100] = "1";
char file2[100] = "2";
char mfile[100] = "12";
for (int i = 2; i <= n; ++i)
{_MergeSortFile(file1, file2, mfile);//迭代strcpy(file1, mfile);sprintf(file2, "%d", i + 1);sprintf(mfile, "%s%d", mfile, i + 1);}
  • 大概就是这么一个迭代的过程

在这里插入图片描述
在这里插入图片描述

整体代码展示

//文件归并逻辑
void _MergeSortFile(const char* file1, const char* file2, const char* mfile)
{FILE* fout1 = fopen(file1, "r");if (!fout1){perror("fopen fail");exit(-1);}FILE* fout2 = fopen(file2, "r");if (!fout2){perror("fopen fail");exit(-1);}FILE* fin = fopen(mfile, "w");if (!fin){perror("fopen fail");exit(-1);}int num1, num2;//返回值拿到循环外来接受int ret1 = fscanf(fout1, "%d\n", &num1);int ret2 = fscanf(fout2, "%d\n", &num2);while (ret1 != EOF && ret2 != EOF){if (num1 < num2){fprintf(fin, "%d\n", num1);ret1 = fscanf(fout1, "%d\n", &num1);}else{fprintf(fin, "%d\n", num2);ret2 = fscanf(fout2, "%d\n", &num2);}}while (ret1 != EOF){fprintf(fin, "%d\n", num1);ret1 = fscanf(fout1, "%d\n", &num1);}while (ret2 != EOF){fprintf(fin, "%d\n", num2);ret2 = fscanf(fout2, "%d\n", &num2);}fclose(fout1);fclose(fout2);fclose(fin);
}/*文件外排序*/
void MergeSortFile(const char* file)
{srand((unsigned int)time(NULL));FILE* fout = fopen(file, "r");if (!fout){perror("fopen fail");exit(-1);}//先写100个随机数进文件//for (int i = 0; i < 100; ++i)//{//	int num = rand() % 100;//	fprintf(fout, "%d\n", num);//}int num = 0;int n = 10;int i = 0;int b[10];char subfile[20];int filei = 1;//1.读取大文件,然后将其平均分成N份,加载到内存中后对每份进行排序,然后再写回小文件memset(b, 0, sizeof(int) * n);while (fscanf(fout, "%d\n", &num) != EOF){if (i < n - 1){b[i++] = num;	//首先读9个数据到数组中}else{b[i] = num;		//再将第十个输入放入数组QuickSort(b, 0, n - 1);		//对其进行排序sprintf(subfile, "%d", filei++);FILE* fin = fopen(subfile, "w");if (!fin){perror("fopen fail");exit(-1);}//再进本轮排好序的10个数以单个小文件的形式写到工程文件下for (int j = 0; j < n; ++j){fprintf(fin, "%d\n", b[j]);}fclose(fin);i = 0;		//i重新置0,方便下一次的读取memset(b, 0, sizeof(int) * n);}}//利用互相归并到文件,实现整体有序char file1[100] = "1";char file2[100] = "2";char mfile[100] = "12";for (int i = 2; i <= n; ++i){_MergeSortFile(file1, file2, mfile);//迭代strcpy(file1, mfile);sprintf(file2, "%d", i + 1);sprintf(mfile, "%s%d", mfile, i + 1);}
}

运行结果展示

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/41781.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习 C++ 的opencv实现SVM图像二分类的测试 (三)【附源码】

机器学习 C 的opencv实现SVM图像二分类的测试 (三) 数据集合下载地址&#xff1a;https://download.csdn.net/download/hgaohr1021/89506900 根据上节得到的svm.xml&#xff0c;测试结果为&#xff1a; #include <stdio.h> #include <time.h> #include <o…

yolov5 json 和 txt数据格式关系

训练阶段 和 推理阶段数据格式转换说明 关于yolov5 数据格式一直以来都傻傻分不清楚&#xff0c;这下进行了一个梳理&#xff0c;做了笔记&#xff0c;也希望可帮助到有需要的有缘人~ 转换部分代码

大厂面试官赞不绝口的后端技术亮点【后端项目亮点合集(2):消息队列、ElasticSearch、Mysql等亮点合集】

本文将持续更新~~ 历史文章&#xff1a; 后端项目亮点合集&#xff08;1&#xff09;&#xff1a;Redis篇_后端项目有什么亮点-CSDN博客 本文的作用&#xff1a; &#xff08;1&#xff09;简历优化&#xff1a;针对自己的简历&#xff0c;对Redis亮点进行优化升级&#xff0c;…

虚拟机交叉编译基于ARM平台的opencv(ffmpeg/x264)

背景&#xff1a; 由于手上有一块rk3568的开发板&#xff0c;需要运行yolov5跑深度学习模型&#xff0c;但是原有的opencv不能对x264格式的视频进行解码&#xff0c;这里就需要将ffmpegx264编译进opencv。 但是开发板算力有限&#xff0c;所以这里采用在windows下&#xff0c;安…

绝缘子陶瓷绝缘子玻色绝缘子聚合物绝缘子检测数据集VOC+YOLO格式2050张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2050 标注数量(xml文件个数)&#xff1a;2050 标注数量(txt文件个数)&#xff1a;2050 标注…

Ubuntu 22.04远程自动登录桌面环境

如果需要远程自动登录桌面环境&#xff0c;首先需要将Ubuntu的自动登录打开&#xff0c;在【settings】-【user】下面 然后要设置【Sharing】进行桌面共享&#xff0c;Ubuntu有自带的桌面共享功能&#xff0c;不需要另外去安装xrdp或者vnc之类的工具了 点开【Remote Desktop】…

Orangepi配合IIC驱动OLED屏幕

目录 一、OLED屏幕 二、Orangepi的IIC接口及OLED屏幕硬件接线 2.1 Orangepi的IIC接口&#xff1a; 2.2 Orangepi与OLED屏幕硬件接线&#xff1a; 三、wiringPi库示例代码 3.1 wiringPi库OLED屏幕示例代码&#xff1a; 3.2 OLED显示自己想要的字符&#xff1a; 一、OLED屏…

unix高级编程系列之文件I/O

背景 作为linux 开发者&#xff0c;我们不可避免会接触到文件编程。比如通过文件记录程序配置参数&#xff0c;通过字符设备与外设进行通信。因此作为合格的linux开发者&#xff0c;一定要熟练掌握文件编程。在文件编程中&#xff0c;我们一般会有两类接口函数&#xff1a;标准…

Mysql慢日志、慢SQL

慢查询日志 查看执行慢的SQL语句&#xff0c;需要先开启慢查询日志。 MySQL 的慢查询日志&#xff0c;记录在 MySQL 中响应时间超过阀值的语句&#xff08;具体指运行时间超过 long_query_time 值的SQL。long_query_time 的默认值为10&#xff0c;意思是运行10秒以上(不含10秒…

阿里云RDS云数据库库表恢复操作

最近数据库中数据被人误删了,记录一下恢复操作方便以后发生时进行恢复. 1.打开控制台&#xff0c;进入云数据库实例. 2.进入实例后 &#xff0c;点击右侧的备份恢复&#xff0c;然后看一下备份时间点&#xff0c;中间这边都是阿里云自动备份的备份集&#xff0c;基本都是7天一备…

详解「一本通 5.1 练习 1」括号配对(区间DP经典题)

一.题目 二.思路 题目的大意是说:给你一个只由[ ] ( )构成的字符串&#xff0c;请问需要增加多少个字符才能使其变为一个合法的括号序列。 因为添加若干字符使其达到匹配的目的等价于将不匹配的字符去除使得字符串达到匹配的目的 所以这题只需计算出已匹配完成的括号数,再…

中英双语介绍伦敦金融城(City of London)

中文版 伦敦金融城&#xff0c;通常称为“金融城”或“城”&#xff08;The City&#xff09;&#xff0c;是英国伦敦市中心的一个著名金融区&#xff0c;具有悠久的历史和全球性的影响力。以下是关于伦敦金融城的详细介绍&#xff0c;包括其地理位置、人口、主要公司、历史背…

【优化论】约束优化算法

约束优化算法是一类专门处理目标函数在存在约束条件下求解最优解的方法。为了更好地理解约束优化算法&#xff0c;我们需要了解一些核心概念和基本方法。 约束优化的核心概念 可行域&#xff08;Feasible Region&#xff09;&#xff1a; 比喻&#xff1a;想象你在一个园艺场…

基于机器学习的永磁同步电机矢量控制策略-高分资源-下载可用!

基于机器学习的永磁同步电机矢量控制策略 优势 训练了RL-Agent&#xff0c;能够提高电机在非线性负载下的性能。 部分程序 仿真结果 转矩估计及dq轴电流。 代码有偿&#xff0c;50&#xff0c;需要的可以联系。

数学建模算法目标规划

在人们的生产实践中&#xff0c;经常会遇到如何利用现有资源来安排生产&#xff0c;以取得最大经济 效益的问题。此类问题构成了运筹学的一个重要分支—数学规划&#xff0c;而线性规划(Linear Programming 简记 LP)则是数学规划的一个重要分支。特别是在计算机能处理成千上万个…

pycharm如何使用jupyter

目录 配置jupyter新建jupyter文件别人写的方法&#xff08;在pycharm种安装&#xff0c;在网页中使用&#xff09; pycharm专业版 配置jupyter 在pycharm终端启动一个conda虚拟环境&#xff0c;输入 conda install jupyter会有很多前置包需要安装&#xff1a; 新建jupyter…

可变参数 Collections 不可变集合 Stream流

目录 1.可变参数&#xff1a; 2.Collections: 3.不可变集合&#xff1a; 4.Stream流: 1、什么是流 2、如何生成流 1.单列集合获取Stream流 2.双列集合获取Stream流 3.数组获取Stream流&#xff1a; 4.一堆零散数据&#xff1a; Stream接口中的静态方法 3.Stream流的…

解决分布式环境下session共享问题

在分布式环境下&#xff0c;session会存在两个问题 第一个问题:不同域名下&#xff0c;浏览器存储的jsessionid是没有存储的。比如登录时认证服务auth.gulimall.com存储了session&#xff0c;但是搜索服务search.gulimall.com是没有这个session的&#xff1b; 第二个问题&…

基于SpringBoot的校园台球厅人员与设备管理系统

本系统是要设计一个校园台球厅人员与设备管理系统&#xff0c;这个系统能够满足校园台球厅人员与设备的管理及用户的校园台球厅人员与设备管理功能。系统的主要功能包括首页、个人中心、用户管理、会员账号管理、会员充值管理、球桌信息管理、会员预约管理、普通预约管理、留言…

w3wp.exe 中发生未处理的 Microsoft ,NETFramework 异常。

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…