前言
- 作者使用的编辑器为Visual Studio 2019,版本号16.5.3(2020年4月12日最新版)。
- 本文篇幅会很长,我会将我自己编写这个程序和优化的过程全部写下来。其中涉及到的诸多函数,会在另外的一篇博文中解析。(去看看<—还没写好先别急,写好了会把链接放上(⊙ω⊙))有一定基础的同学阅读时间在15分钟左右,所以建议先收藏(✿◠‿◠)!
- 我会分阶段介绍编写程序和优化完善,我将函待解决的问题和可以实现的想法写在了每一段的最前面,本文对函数使用和结构介绍内容较少,如果有不懂的地方可以私信给我哦(⊙▽⊙)。
- 截止到本篇博文发表,我学习C/C++的时间还不到四个月,如有错误请多多指教!
- 详细使用教程:青年大学习自动名单核对程序(使用教程)
- 2021年6月更新:我写了一个窗口程序,用来实现本文的操作,提取码:mchx。
- 2021年7月更新:对于窗口程序的使用教程在这里。(目前还没有写好)
编写本程序的原因
这个小程序来自于一个很偶然的想法,我作为班级团支书,核对青年大学习的名单是一件既费时又费力还没有技术含量的事情。那为何不写一个能够自动核对名单的小程序呢?
构建程序
想法固然很简单,但是实现起来就很麻烦。基于C/C++最最最基础的内容,我目前也只能构建单机控制台程序(就是那个毫无美感的黑方块)。
但是不得不说,从一开始有这个想法,到开始编写的过程,是无比艰辛的。我对C语言还停留在字符串、循环等等最基本的概念上。本文也是在有一定C/C++基础上撰写的,我会尽量用基础论述问题。
第一阶段:构建主体
问题or想法:用程序实现对比功能,并将不符合某些要求的量输出到屏幕上。
首先我们知道:一个个名字都是由字符串构成的,而很多名字在一起就是字符串数组了。所以我们比较的其实是两个字符串数组(班级名单、青年大学习完成名单)之间不同的地方。所以我们可以得知整个程序的核心:
核心:比较字符串数组之间的不同。
方法:
- 字符串数组A中的一个元素与字符串数组B中的所有元素依次比较(班级名单中的一个人名与青年大学习完成名单作比较);
- 如果出现相同则证明这个元素在字符串数组B中存在(如果这个名字在青年大学习完成名单中有,则证明这名同学完成青年大学习)。
使用函数:extern int strcmp(const char *s1,const char *s2)【string compare“字符串比较”的缩写】
//略去头文件和主函数
char buf_1[8][],buf_2[8][];//分别代表班级名单、青年大学习完成名单
for (int i = 1; i < n; i++)//进行判断
{
while(strcmp(buf_2[i], buf_1[m]) != 0)
{
m++;
if (m == 80)
{
printf("[%d]%s\t\t未完成", i, buf_2[i]);
break;
}
}
if (strcmp(buf_2[i], buf_1[m]) == 0)
{
printf("[%d]%s\t\t已完成", i, buf_2[i]);
}
m = 1;
}
第二阶段:输入输出
青年大学习每次都有不一样的名单,所以我们需要重新编辑字符串数组。那么总不能直接更改程序吧,所以我们需要从文件中读取相关信息并保存到程序中。查阅了很多大佬的项目之后,发现直接保存在文本文档是最简单的方法。
问题or想法:将文本文档里的数据读取到程序里的字符串数组。
方法:预读取文件,读取成功的话开始依次读取信息
使用函数:
FILE * fopen(const char *filename, const char *mode)【读取文本文件】
int fscanf(FILE * stream, const char * format, [argument…])【从输入流(stream)中读入数据,存储到argument中】
//略去头文件
void File_search(FILE* n,const char* a)//尝试打开文件,注意文件保存格式为(ANSL)防止出现错漏(下同)
{
if ((n = fopen(a, "r")) == NULL)
{
printf("fail to read & %s", a);//如果不存在,输出fail to read +文件名,并结束程序
exit(1);
}
}
int main()
{
File_search(fp, "new.txt");//检测文件“new.txt”是否存在
File_search(fg, "date.txt");//检测文件“date.txt”是否存在
for (int i = 1; fscanf(fp, "%s%s", buf_1[i], buf_11[i]) != EOF; i++);//将学习名单写入数组buf_1
for (int i = 1; fscanf(fg, "%s%s", buf_2[i], buf_22[i]) != EOF; i++);//将班级名单写入数组buf_2
}
第三阶段:构建完整程序
这就没啥废话可以说了,直接亮代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
#define MAX_LINE 80//全局常量
void File_search(FILE* n,const char* a)//尝试打开文件,注意文件保存格式为(ANSL)防止出现错漏(下同)
{
if ((n = fopen(a, "r")) == NULL)
{
printf("fail to read & %s", a);
exit(1);
}
}
int main()
{
FILE* fp = fopen("new.txt","r");// new 文件指针
FILE* fg = fopen("date.txt","r");// date 文件指针
char buf_1 [MAX_LINE][25] = { 0 };//学习情况名单数组
char buf_11[MAX_LINE][25] = { 0 };//承接无用字符
char buf_2 [MAX_LINE][25] = { 0 };//班级学生名单数组
char buf_22[MAX_LINE][25] = { 0 };//承接组别字符
int Class_size = 80;//班级总人数
File_search(fp, "new.txt");//检测文件“new.txt”是否存在
File_search(fg, "date.txt");//检测文件“date.txt”是否存在
for (int i = 1; fscanf(fp, "%s%s", buf_1[i], buf_11[i]) != EOF; i++); //将学习名单写入数组buf_1
for (int i = 1; fscanf(fg, "%s%s", buf_2[i], buf_22[i]) != EOF; i++); //将班级名单写入数组buf_2
for (int i = 1; i < Class_size; i++) //进行判断
{
int m = 1;//循环变量
while (strcmp(buf_2[i], buf_1[m]) != 0)
{
m++;
if (m == MAX_LINE)
{
printf("[%d]%s\t未完成", i, buf_2[i]);
printf("\t%s\n", buf_22[i]);
break;
}
}
if (strcmp(buf_2[i], buf_1[m]) == 0)
{
printf("[%d]%s\t已完成", i, buf_2[i]);
printf("\t%s\n", buf_22[i]);
}
}
Sleep(10000);
return 0;
}
第四阶段:优化程序
有的同学跟我提出,能不能选择性输出,就比如说只输出未完成的同学姓名,选择性输出组别。
可以啊!
问题or想法:在程序最开始选择输出模式(输入“是”或“否”)
方法:编写新的输出控制函数,通过不同的返回值控制主函数输出方式的不同
使用函数:
char * gets_s(char * buffer,size_t sizeInCharacters)【从键盘上读取输入的字符放到字符串数组里】
extern int strcmp(const char * s1,const char * s2)【string compare“字符串比较”的缩写】
输出控制函数:
int Output(const char* a)//通用输出引擎
{
char d[8] = { "是" };//是字符串
char f[8] = { 0 };
printf(a);
gets_s(f, 3);
if (strcmp(d, f) == 0)
return 1;
else
return 0;
}
主函数引用:
Output_1 = Output("请问是否需要只输出未完成(填写‘是’或‘否’):");//第一个输出设置返回值引入
Output_2 = Output("请问是否需要输出组别(填写‘是’或‘否’):");//第二个输出设置返回值引入
输出模式引用:
for (int i = 1; i < Class_size; i++) //进行判断
{
int m = 1;//循环变量
while (strcmp(buf_2[i], buf_1[m]) != 0)
{
m++;
if (m == MAX_LINE)
{
printf("[%d]%s\t未完成", i, buf_2[i]);
if (Output_2 == 1)//是否输出组别
printf("\t%s", buf_22[i]);
printf("\n");
break;
}
}
if (strcmp(buf_2[i], buf_1[m]) == 0)
{
if (Output_1 == 0)
{
printf("[%d]%s\t已完成", i, buf_2[i]);
if (Output_2 == 1)//是否输出组别
printf("\t%s", buf_22[i]);
printf("\n");
}
}
}
PS:为何要写通用输出控制函数呢?
这样写的好处:以后如果需要增加控制模式,可以直接在主函数调用本控制函数,最多增加4-5行即可完成控制变量增加。
最后我们不妨再优化一下,加入未完成人数统计。这样这个程序就完整的写出来啦!
程序实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
#define MAX_LINE 80//全局常量
int Output(const char* a)//通用输出引擎
{
char d[8] = { "是" };//是字符串
char f[8] = { 0 };
printf(a);
gets_s(f, 3);
if (strcmp(d, f) == 0)
return 1;
else
return 0;
}
void File_search(FILE* n,const char* a)//尝试打开文件,注意文件保存格式为(ANSL)防止出现错漏(下同)
{
if ((n = fopen(a, "r")) == NULL)
{
printf("fail to read & %s", a);
exit(1);
}
}
int main()
{
char buf_1[MAX_LINE][25] = { 0 };//学习情况名单数组
char buf_11[MAX_LINE][25] = { 0 };//承接无用字符
char buf_2[MAX_LINE][25] = { 0 };//班级学生名单数组
char buf_22[MAX_LINE][25] = { 0 };//承接组别字符
FILE* fp = fopen("new.txt","r");// new 文件指针
FILE* fg = fopen("date.txt","r");// date 文件指针
int Class_size = 80;//班级总人数
int Output_1 = 0;//输出设置1返回值
int Output_2 = 0;//输出设置2返回值
int Unfinished_student = 0;//未完成学生人数
Output_1 = Output("请问是否需要只输出未完成(填写‘是’或‘否’):");//第一个输出设置返回值引入
Output_2 = Output("请问是否需要输出组别(填写‘是’或‘否’):");//第二个输出设置返回值引入
File_search(fp, "new.txt");//检测文件“new.txt”是否存在
File_search(fg, "date.txt");//检测文件“date.txt”是否存在
for (int i = 1; fscanf(fp, "%s%s", buf_1[i], buf_11[i]) != EOF; i++); //将学习名单写入数组buf_1
for (int i = 1; fscanf(fg, "%s%s", buf_2[i], buf_22[i]) != EOF; i++); //将班级名单写入数组buf_2
for (int i = 1; i < Class_size; i++) //进行判断
{
int m = 1;//循环变量
while (strcmp(buf_2[i], buf_1[m]) != 0)
{
m++;
if (m == MAX_LINE)
{
printf("[%d]%s\t未完成", i, buf_2[i]);
if (Output_2 == 1)//是否输出组别
printf("\t%s", buf_22[i]);
printf("\n");
Unfinished_student++;//未完成人数统计
break;
}
}
if (strcmp(buf_2[i], buf_1[m]) == 0)
{
if (Output_1 == 0)
{
printf("[%d]%s\t已完成", i, buf_2[i]);
if (Output_2 == 1)//是否输出组别
printf("\t%s", buf_22[i]);
printf("\n");
}
}
}
printf("\n\t我班共有%d人未完成", Unfinished_student);//显示未完成人数
Sleep(10000);
return 0;
}