目录
1、程序描述
只使用C语言,制作一个命令行运行的点名程序,该程序运行时用指定txt文件作为“班级”花名册,运行后再界面上显示随机抽取名字的过程,速度由快到慢,逐渐定格到某一个名字上,保存到文件中,程序结束。
2、程序功能
- 文件形式保存和读取
- 显示所有同学点名情况
- 查询单个同学点名情况
- 开始多个名字点名
- 开始单个名字点名
- 保存点名
- 点名次数归零
- 字体颜色变化
3、功能详细实现过程
学生结构体声明和定义
“班级花名册”上记录的都是一个同学的信息,在随机点名器这个程序中,我们就需要定义一个结构体保存每个同学的信息,在这里,我们就需要同学名字,点名次数和同学编号,这样就可以定义出下面这样的一个结构体:
#define TOTAL 15 //学生人数
struct Student
{
char name[30]; //学生姓名
int num; //学生编号
int frequency; //点名次数
} stu[TOTAL]; //整个班级的学生信息数组
班级的人数是可变的,这里就定义一个宏定义TOTAL来表示班级的人数,然后利用结构体数组来存放所有学生的信息。
菜单(menu)函数
定义完结构体之后,就需要开始进行整个程序的框架搭建,就可以写出一个菜单(menu)函数,方便我们使用程序和单个功能的测试:
//菜单函数
void menu()
{
printf("********************************************\n");
printf("********************************************\n");
printf("***********欢迎使用上课随机点名程序*********\n");
printf("***** 输入a : 显示所有同学点名情况 *****\n");
printf("***** 输入b : 查询单个同学点名情况 *****\n");
printf("***** 输入c : 开始单个名字点名 *****\n");
printf("***** 输入d : 开始多个名字点名 *****\n");
printf("***** 输入e : 保存点名 *****\n");
printf("***** 输入f : 点名次数归零 *****\n");
printf("***** 输入q : 退出 *****\n");
printf("********************************************\n");
printf("********************************************\n");
}
文件读取和保存函数
完成菜单函数的实现后,就可以进行单个功能函数的实现,显示所有同学点名情况功能和查询单个同学点名情况功能实现之前,我们是不是少了什么东西?是不是少了文件读取和保存?不然怎么知道“班级”同学的信息?所以我们先写出txt文件读取和保存的功能函数出来:
//读取文件函数
void readFile()
{
FILE * fp = fopen("studentname.txt", "rb");
if (fp == NULL)
{
printf("打开文件失败\n");
exit(1);
}
else
{
for (int i = 0; i < TOTAL; i++)
{
fscanf(fp, "%d %s %d\n", &stu[i].num, stu[i].name, &stu[i].frequency);
}
}
fclose(fp);
}
//保存文件函数
void saveFile()
{
FILE* fp = fopen("studentname.txt", "wb");
if (fp == NULL)
{
printf("\n写入文件失败\n");
return;
}
else
{
for (int i = 0; i < TOTAL; i++)
{
fprintf(fp, "%d %s %d\n", stu[i].num, stu[i].name, stu[i].frequency);
}
}
fclose(fp);
printf("\n保存成功!\n");
printf("\n");
}
这里用到了文件操作的内容,对此感到不是很熟悉的读者可以查看我的这篇博客:
C语言入门篇——文件操作篇_sakura0908的博客-CSDN博客,里面有比较详细的文件操作函数的讲解,对以上的函数就可以理解地比较好了。
查询函数
读取了“班级”文件之后,我们就可以获取所有的学生信息了,就可以实现查询所有同学情况和单个同学点名情况,这两个功能函数是非常容易实现的:
//查询所有学生的点名信息
void all_print()
{
//显示所有同学点名情况
printf("\n点名情况\n");
for (int i = 0; i < TOTAL; i++)
{
printf("%-8d\t%-8s\t被点名%d次\n", stu[i].num, stu[i].name, stu[i].frequency);
}
printf("\n");
}
//查询单个学生的点名信息
void single_print(int num)
{
for (int i = 0; i < TOTAL; i++)
{
if (stu[i].num == num)
{
printf("%-8d\t%-8s\t被点名%d次\n", stu[i].num, stu[i].name, stu[i].frequency);
printf("\n");
break;
}
}
}
查询所有同学情况直接使用for循环对整个结构体数组遍历打印就可以实现;查询单个同学点名情况就比较复杂一点点,传入要查询同学的编号,遍历结构体数组之后,通过比结构体中的学生编号返回对应的结构体数组的下标。
点名函数
点名函数是这个随机点名器中最核心的功能了,在实现这个功能之前,我们先来了解一下rand这个函数,因为在点名函数中就用到了rand函数,不会使用该函数的话会出现一些读者意想不到的错误运行结果,
rand函数
rand函数,C语言中用来产生一个随机数的函数。
rand函数界限:stdlib.h头文件中有宏#define RAND_MAX 0x7fff
rand产生一个0-0x7fff的随机数,即最大是32767的一个数
rand函数头文件:
#include <stdlib.h>
rand函数原型:
int rand(void);
rand()函数每次调用前都会查询是否调用过srand(seed),是否给seed设定了一个值,如果有那么它会自动调用srand(seed)一次来初始化它的起始值;若之前没有调用srand(seed),那么系统会自动给seed赋初始值,即srand(1)自动调用它一次
srand函数:srand函数是随机数发生器的初始化函数
srand函数头文件:
#include <stdlib.h>
srand函数原型:
void srand(unsigned int seed);
这个函数需要提供一个种子,如srand(1),用1来初始化种子。rand()产生随机数时,如果用srand(seed)播下种子之后,一旦种子相同,产生的随机数将是相同的。当然很多时候刻意让rand()产生的随机数随机化,用时间作种子 srand(time(NULL)),这样每次运行程序的时间肯定是不相同的,产生的随机数肯定就不一样了。
我们常常使用系统时间来初始化,使用time函数来获取系统时间,得到的值是一个时间戳,即2023年5月15日13点到现在时间的秒数,然后将得到的time_t类型数据转化为(unsigned int)的数,然后再传给srand函数
srand((unsigned int)time(NULL));//我们在使用rand和srand时,主要使用的就是这一种初始化方法!!
如果仍然觉得时间间隔太小,可以在(unsigned)time(0)或者(unsigned)time(NULL)后面乘上某个合适的整数。
time的参数传NULL表示不需要经过参数获得到的time_t数据,time函数原型如下
time函数头文件:
#include <time.h>
time函数原型:
time_t time(time_t *tloc);//time_t类型被定义为一个长整型
还有另外一种初始化种子的方式如下,用进程的pid作为种子值seed,在同一个程序中,这样的种子的值是相同的
srand((unsigned int)getpid());
rand函数的使用,如果想要表示一个数是从0开始到最大值的,比如说,想要产生一个0-99之间的随机数,那么用法如下:
int num = rand() % 100;
如果想要产生一个数是从1开始到最大值的,比如说,想要产生一个1-100之间的随机数,那么用法如下:
int num = rand() % 100 + 1;
需要注意最后+1和不+1的区别,+1的最小值是1,不+1的最小值是0
点名函数实现
在随机点名的时候需要实现名字闪烁的功能,就需要用到\r这个转义字符,它的作用,是将光标移动到行首,但不换行,此时如果输出信息,那么这些信息就会覆盖原有的内容。重复这个过程,妥善处理一行内容长短不一的问题,就可以在同一样中不断闪现不同的名字。单个同学点名函数如下:
//单个学生点名函数
void Named()
{
int id = -1;
int t = 300;
//为rand()配置随机数产生的“种子”系数,获取当前系统时间
srand((unsigned)time(NULL));
printf("\n开始点名\n");
//循环15次随机点数功能
for (int i = 0; i < 15; i++)
{
for (int j = 0; j < TOTAL; j++)
{
//求余取得名字对应的数组下标和其对应的内存地址
id = rand() % 15;
//实现名字闪烁的功能
printf("\r \r");
printf("%s", stu[id].name);
fflush(stdout);
}
//闪烁时间的慢慢增长
Sleep(t);
t += 60;
}
printf("\n幸运者是:%s\n", stu[id].name);
//增加抽到同学的点名次数
stu[id].frequency += 1;
printf("\n");
}
多个学生点名的函数值需要在单个同学点名的情况上稍加修改(增添点名学生个数)就可以实现了,升级版的点名函数为:
//学生点名函数
void Named(int num)
{
int id = -1;
int t = 300;
//为rand()配置随机数产生的“种子”系数,获取当前系统时间
srand((unsigned)time(NULL));
printf("\n开始点名\n");
//循环15次随机点数功能
for (int k = 0; k < num; k++)
{
for (int i = 0; i < 15; i++)
{
for (int j = 0; j < TOTAL; j++)
{
//求余取得名字对应的数组下标和其对应的内存地址
id = rand() % 15;
//实现名字闪烁的功能
printf("\r \r");
printf("%s", stu[id].name);
fflush(stdout);
}
//闪烁时间的慢慢增长
Sleep(t);
t += 60;
}
printf("\n幸运者是:%s\n", stu[id].name);
//增加抽到同学的点名次数
stu[id].frequency += 1;
}
printf("\n");
}
点名次数归零函数
这个函数的功能顾名思义就知道是把学生的点名次数归零,实现步骤也很简单,遍历整个学生信息结构体数组,把每个学生的的结构体中的点名次数重新赋值为0就可以实现了:
//点名次数归零函数
void to_Zero()
{
for (int i = 0; i < TOTAL; i++)
{
stu[i].frequency = 0;
}
printf("\n点名次数归零\n");
printf("\n");
}
字体颜色变化函数
0=黑色 8=灰色
1=蓝色 9=淡蓝色
2=绿色 10=淡绿色
3=蓝色 11=浅绿色
4=红色 12=淡红色
5=紫色 13=淡紫色
6=黄色 14=淡黄色
7=白色 15=亮白色
int color_num = 0;
//字体颜色变化函数
void color(int x)
{
if (x >= 0 && x <= 15)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);
else//其他白色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
}
//前缀++运算符优先级比求模运算符优先级要高
color(++color_num % 16);
对上面的操作符优先级有问题的可以查看这篇博客里面的运算符优先级介绍:
C语言入门篇——操作符篇_sakura0908的博客-CSDN博客
4、运行效果
这里只提供部分运行效果截图
5、源码分享
功能函数上面写的差不多了,最后附上程序的主函数,有兴趣的读者可以copy下来自己运行一下这个有趣的程序:
int main()
{
int num = 0;
char c = '\0';
while (1)
{
menu();
readFile();
while ((c = getchar()) != 'q')
{
color(++color_num % 16);
printf("请输入你要选择的功能:");
c = getchar();
switch (c)
{
case 'a':
color(++color_num % 16);
all_print();
break;
case 'b':
color(++color_num % 16);
printf("请输入你要查询学生的学生编号:");
scanf("%d", &num);
single_print(num);
break;
case 'c':
color(++color_num % 16);
Named(1);
break;
case 'd':
color(++color_num % 16);
printf("请输入你要点名的人数:");
scanf("%d", &num);
Named(num);
break;
case 'e':
color(++color_num % 16);
saveFile();
break;
case 'f'://点名次数归零
color(++color_num % 16);
to_Zero();
break;
case 'q':
printf("\n欢迎下次使用随机点名器!\n");
exit(0);
}
}
}
return 0;
}