⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:数据结构初阶
⭐代码仓库:Data Structure
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!
这是计科C语言课程设计的大作业,因为写了2k多行的代码,所以我将此代码放在我的Gittee仓库里面了,大家看完讲解需要原代码的直接去我的Gittee仓库进行Clone即可。
Gittee仓库(数据结构)
老规矩,三板斧:Slist.h,Slist.c和text.c,这三个文件是很关键的,我们需要在text.c中进行测试,写一个函数进行一个函数的测试,Slist.h包含头文件,函数的声明和链表结构体的实现;Slist.c包含了函数的定义和实现以及菜单的打印。
在这里我们要了解几点知识,首先是我们需要改变链表结构的时候,是需要传链表的地址,也就是需要用二级指针去接收,而不改变链表的结构仅仅是改变链表里的数据,只需要用一级指针去接收即可,这样可以单纯改变数据。
以下均在SList.c中:
我这里用的是尾插法,也就是找到尾结点,再尾结点->next直接指向newnode即可,注意,这里我们是直接改变了链表的结构,改变了链表指向性的问题,所以我们用的是二级指针:
核心code:
这里的增加一个结点我是放到一个函数里,我们直接实现一下这个函数:
流程图:
打印可以说是最简单的代码,我们只需要遍历链表进行打印即可,注意,这里空链表也可以打印,这里只是打印,不是改变链表的结构,所以我们就只需要用一级指针接收即可:
流程图:
先在我们.c文件的根目录里创建一个.txt文档然后进行写入,用的是fprintf函数,我们在进行训练完C进阶文件的信息后,我们能够知道fprintf函数是将链表中的信息存放到文件.txt中,是以原本的信息直接打印进去,我们可以遍历链表,一个一个存入文件当中即可:
如果大家对于文件的操作有所疑问的,大家可以看一看以前我介绍的C语言文件操作。
注意:这里我们进行写入文件用的是什么进制我们读出用的就是什么进制的函数,这里我加载到的是2的文件,所以我们可以往前看到的是文件的写入用的是fprintf,然后文件的加载用的是fscanf。其次,我们需要怎么进行操作文件的加载呢??这个其实不难,我们先创建一个临时的结构体空间,然后将文件中的信息读取到新创建的结构体空间中,再使用memcpy或者memmove或者直接进行赋值进行存储到新的结点里,再将结点进行插入链表即可。
此次查找是按照不同的信息进行查找,我们根据不同的信息进行查找,是根据球员不同的信息进行查找,所以,这里用一个switch-case语句进行协调,似乎看起来很麻烦,但是,这几个函数都是可以复用的,比如我们在比较字符串的时候我们用strcmp库函数进行比较,比较整型和浮点型的话直接进行比较数额大小即可,以下我列两个模板:
No.1:字符串比较
No.2:数额比较
No.3组合查找
组合查找就跟我们前面差不多,加一个&&即可,我这里只写了查询学习等级和运动等级的组合查找,并没有写其他的组合查找,因为其他的组合查找都是和这个组合查找一样的过程与代码,我们直接上代码:
按照身份证找到人,想把这个人删除怎么办??我们只需要记忆要删除人的前一个以及后一个,要删的人的前一个结点进行保存,然后将前一个人的结点指向next->next,再将要删的人的结点进行释放即可,再将这个人的信息进行释放即可:
思路比较简单,我们找到需要插入的位置,我们仅仅需要按照身份证找到需要插入人的信息,直接往后插入即可,为什么要往后插入,我们不往前插入呢??因为往前插入是很麻烦的,要判断是不是头结点,是的话,我们直接新结点就是头结点,不是的话我们前插,大家有没有发现有点麻烦?所以C++中的STL库中的插入是尾插,而且人们的习惯都是往后排队,不可能插队吧!?所以我们选择往后插入。
//指插(找到位置再插入到位置的后面)
void InsertCBAPlayerAfter(SLTCBANode** pphead) {
//assert(pphead);
int pos = 0;
int ret = 0;
int pos1 = 1;
SLTCBANode* prev = *pphead;//prev为头结点
if (prev == NULL) {//空链表
printf("还未存入球员信息,请重试!\n");
system("pause");//暂停
system("cls");//清屏
return;
}
printf("请输入要插入哪个人的后面(序号)\n");
scanf("%d", &pos);//找位置
while (prev) {//算算链表数
ret++;
prev = prev->next;
}
prev = *pphead;
if (pos > ret || pos <= 0) {//用户输入位置过大
printf("找不到需要插入的位置,请重新试!\n");
printf("此时只存了%d个球员的信息\n", ret);
system("pause");//暂停
system("cls");//清屏
return;
}
SLTCBANode* temp = NULL;//临时空间
if (pos == 1) {//在头结点
SLTCBANode* newnode = BuyListNode();//建立一个新的结点
printf("请输入球员身份证号码>\n");
scanf("%s", newnode->cbaplayer.ID);
printf("请输入球员姓名(英文)>\n");
scanf("%s", newnode->cbaplayer.name);
printf("请输入球员性别(女f,男m)>\n");
scanf("%s", newnode->cbaplayer.sex);
printf("请输入球员年龄>\n");
scanf("%d", &newnode->cbaplayer.age);
printf("请输入球员的球衣号码>\n");
scanf("%d", &newnode->cbaplayer.number);
printf("请输入球员身高(cm)>\n");
scanf("%f", &newnode->cbaplayer.high);
printf("请输入球员体重(kg)>\n");
scanf("%f", &newnode->cbaplayer.weight);
printf("请输入球员臂展(cm)>\n");
scanf("%f", &newnode->cbaplayer.reach);
printf("请输入球员体脂率(小数)>\n");
scanf("%f", &newnode->cbaplayer.BodyFatPercentage);
printf("请输入球员运动等级(优秀outstanding,良好good,及格pass)>\n");
scanf("%s", newnode->cbaplayer.PEResult);
printf("请输入球员学习等级(优秀outstanding,良好good,及格pass)>\n");
scanf("%s", newnode->cbaplayer.LearingScore);
newnode->next = prev->next;
prev->next = newnode;
printf("增加成功\n");
system("pause");//暂停
system("cls");//清屏
return;
}
else if (pos == ret) {
InputTailCBAPlayer(&prev);//尾插
}
else {//不在头结点
while (prev->next != NULL) {
//找相等的位置,先定义pos1,再++,直至与pos相同,找到位置
//pos1 = 1;
pos1++;
prev = prev->next;
if (pos == pos1) {//相等进行插入操作
SLTCBANode* newnode1 = BuyListNode();//建立一个新的结点
printf("请输入球员身份证号码>\n");
scanf("%s", newnode1->cbaplayer.ID);
printf("请输入球员姓名(英文)>\n");
scanf("%s", newnode1->cbaplayer.name);
printf("请输入球员性别(女f,男m)>\n");
scanf("%s", newnode1->cbaplayer.sex);
printf("请输入球员年龄>\n");
scanf("%d", &newnode1->cbaplayer.age);
printf("请输入球员的球衣号码>\n");
scanf("%d", &newnode1->cbaplayer.number);
printf("请输入球员身高(cm)>\n");
scanf("%f", &newnode1->cbaplayer.high);
printf("请输入球员体重(kg)>\n");
scanf("%f", &newnode1->cbaplayer.weight);
printf("请输入球员臂展(cm)>\n");
scanf("%f", &newnode1->cbaplayer.reach);
printf("请输入球员体脂率(小数)>\n");
scanf("%f", &newnode1->cbaplayer.BodyFatPercentage);
printf("请输入球员运动等级(优秀outstanding,良好good,及格pass)>\n");
scanf("%s", newnode1->cbaplayer.PEResult);
printf("请输入球员学习等级(优秀outstanding,良好good,及格pass)>\n");
scanf("%s", newnode1->cbaplayer.LearingScore);
newnode1->next = prev->next;
prev->next = newnode1;
printf("增加成功\n");
system("pause");//暂停
system("cls");//清屏
return;
}
//prev = prev->next;//比控制范围多了的数据,指向空,越界(可以在此进行调试,将这段代码屏蔽掉进行调试)
if (prev == NULL) {//越界
printf("越界,插入不了!\n");
break;
}
}
}
//if (prev == NULL) {
// printf("超出范围\n");
// system("pause");//暂停
// system("cls");//清屏
//}
}
修改成员信息比较简单,我们仅仅需要直接将需要修改的信息覆盖掉即可,看似似乎很难,但是我们思路是先找到需要找到的人,人的身份证号是互不相同的,所以我们寻找的依据是身份证号,找到了那个人以后,我们进行scanf直接进行覆盖的操作,接下来我们看以下下面的模板进行操作:
流程图:
筛选可以根据自己的喜好进行筛选,只要是在这个范围内进行筛选出来进行打印即可,我们这里的筛选都是大同小异,我这里仅仅放一个筛选的条件即可:
统计球员个数那就是直接遍历链表,进行加加操作,最后进行return即可:
排序似乎看上去很难,但是其实并没有想象中的那么复杂,我们在进行排序的时候是可以分升序和降序的,我们只需要放在一个switch-case子语句里进行选择即可,进行排序其实并不复杂,我们只需要进行地址的交换和数据的交换即可,那我们直接动手开始做吧!
我们只需要将头结点赋给prev1,然后prev1往后遍历,而prev2永远比prev1往后一位进行比较即可,按照升序和降序进行操作即可,直至遍历完链表。
计算这个模块仅仅是一个娱乐的项目,我在网上找了体侧、学习和体脂率的计算公式,进行调用即可:
//计算菜单
void MathMenuInterface() {
printf("\t ******************************************************\t\t\n");
printf("\t **************** 欢迎来到计算菜单 **************\t\t\n");
printf("\t *************** 0.退出 1.体脂率 *************\t\t\n");
printf("\t *************** 2.学习等级 3.体育等级 *************\t\t\n");
printf("\t *************** 请选择 *************\t\t\n");
printf("\t ******************************************************\t\t\n");
}
//计算(体脂率,学习成绩,体育成绩)
void MathMenu() {
system("cls");
int input = 0;
float math = 0.0f;
float chinese = 0.0f;
float english = 0.0f;
float fiftymeters = 0.0f; //10
float basketballskills = 0.0f; //60
float Standinglongjump = 0.0f; //10
float eightymeters = 0.0f; //20
MathMenuInterface();
do {
printf("请选择\n");
scanf("%d", &input);
switch (input) {
case 0: {
printf("退出程序\n");
break;
}
case 1:{
printf("请输入性别(男male,女female):>\n");
char Sex[10];
scanf("%s", Sex);
int i = strlen(Sex);
if (i == 4) {
printf("请分别输入腰围(cm)和体重(kg)\n");
float waistline = 0.0f;
float weight = 0.0f;
scanf("%f %f", &waistline, &weight);
float BFP = ((waistline * 0.74) - (weight * 0.082 + 44.74)) / weight * 100;
printf("体脂率为%.1f\n", BFP);
}
else {
printf("请分别输入腰围(cm)和体重(kg)\n");
float waistline1 = 0.0f;
float weight1 = 0.0f;
scanf("%f %f", &waistline1, &weight1);
float BFP = ((waistline1 * 0.74) - (weight1 * 0.082 + 34.89)) / weight1 * 100;
printf("体脂率为%.1f\n", BFP);
}
break;
}
case 2:
//学习等级
printf("请分别输入语文数学和英语分数(百分制)\n");
scanf("%f %f %f", &chinese, &math, &english);
float avarage = (chinese + math + english) / 3;
if (avarage >= 85 && avarage <= 100) {
printf("outstanding\n");
}
else if (avarage >= 75 && avarage < 85) {
printf("good\n");
}
else if (avarage >= 60 && avarage < 75) {
printf("pass\n");
}
else {
printf("no pass\n");
}
break;
case 3: {
//体育等级
printf("请输入50米,篮球技巧,立定跳远和800米跑步成绩(百分制)\n");
scanf("%f %f %f %f", &fiftymeters, &basketballskills, &Standinglongjump, &eightymeters);
float sum = 0.1 * fiftymeters + 0.6 * basketballskills + 0.1 * Standinglongjump + 0.2 * eightymeters;
if (sum >= 90) {
printf("outstanding\n");
}
else if (sum >= 80 && sum < 90) {
printf("good\n");
}
else if (sum >= 70 && sum < 80) {
printf("pass\n");
}
else {
printf("no pass\n");
}
break;
}
}
} while (input);
system("pause");//暂停
system("cls");//清空
}
判断其实也就是复用比较函数接口,我们仅仅需要进行比较即可,如果比较相等就通过,不相等就输出不相匹配的信息,如下:
而进入主函数我们想单纯判断判断自己的话,有一个细节,我们已经链表遍历到末尾了,所以,这个链表是已经进行改变了,我们需要将链表重新放到头的位置:
流程图:
清空就是销毁链表,把空间还给操作系统,可以理解为,我搬家了,这块空间我还给国家了,至于这块空间以后会被谁借用,我不知道,我只是把这块空间还掉了,销毁链表就是置为空即可:
报表就是要将这个链表打印地更加美观即可,与打印模块相同:
流程图:
遇事不决先创建一个用户的文件,我们将这个文件放在与.c和.h一样地方的根目录下,这样子写起来只需要用相对地址即可,较为简洁,所以我们先创建一个吧!
注册这块难度就上来了,我们进行注册的话是打开文件进行文件的写入,用的是fread函数,将我们在控制台上进行写入的信息存到文件中,这其中的细节当然是我们要先比较我们的账号是否被征用,以及我们确认两次是否输入一样的密码,这些直接用strcmp函数进行比较,但在这中间,我们当然也看到过很多问题,就比如说我输入的数额大于我开辟的空间的大小了,这就需要我们将这些错误进入调试一看,发现空间不够,我们增大空间。也可能出现进不来文件的情况,我们就需要仔细检查一下fread函数是否输入错误。
//用户注册
void Register() {
users ReceiveUserInput, ReadFromFile;//ReceiveUserInput是用来接收用户输入的,ReadFromFile是用来从文件中读取的
char temp[MAX_NAME] = "";//20个存储空间,用着下面做判断的
printf("欢迎来到注册界面>\n");
FILE* pFileRiver = fopen("users.txt", "rb");//二进制只读方式打开文件,rb--只可以读
fread(&ReadFromFile, sizeof(users), 1, pFileRiver);//将文件里面的信息存储到ReadFromFile中
if (pFileRiver == NULL) {//空
perror("fopen::pFileRiver\n");
return;
}
printf("请输入你的账号>");//用户名
scanf("%s", ReceiveUserInput.id);
while (1) {//让文件运行起来
if (strcmp(ReceiveUserInput.id, ReadFromFile.id)) {//用户名不相同
if (!feof(pFileRiver)) {//未读到文件结尾也可能认为用户名不同
fread(&ReadFromFile, sizeof(users), 1, pFileRiver);//继续读到文件的结尾
}
else {//读到文件末尾了
break;//跳出文件即可
}
}
else {//用户名相同,重复了
printf("该ID已被别人征用,请换个ID\n");
//Sleep(1000);
fclose(pFileRiver);
pFileRiver = NULL;
return;//直接返回
}
}
//创建成功
printf("请输入您的姓名>");
scanf("%s", ReceiveUserInput.name);
printf("请输入您的性别>");
scanf("%s", ReceiveUserInput.sex);
printf("请输入您的电话号码>");
scanf("%s", ReceiveUserInput.phone);
printf("请输入您的密码>");
scanf("%s", ReceiveUserInput.password);
/*int i = 0;
for (i = 0; i < 30; i++){
ReceiveUserInput.password[i] = _getch();
if (ReceiveUserInput.password[i] == '\r'){
break;
}
else
{
printf("*");
}
}*/
printf("请确认您的密码>");
scanf("%s", temp);//进行判断密码是否正确
/*for (i = 0; i < 30; i++){
temp[i] = _getch();
if (temp[i] == '\r'){
break;
}
else{
printf("*");
}
}*/
//判断
do {
if (strcmp(ReceiveUserInput.password, temp) == 0) {//两次密码输入相同
pFileRiver = fopen("users.txt", "ab");//ab--只可以写
fwrite(&ReceiveUserInput, sizeof(users), 1, pFileRiver);//将信息写入ReceiveUserInput中
printf("\n账号注册成功,请登录\n");
fclose(pFileRiver);
pFileRiver = NULL;
return;//直接返回不解释
}
else {//两次密码输入不同
printf("\n两次密码不一致,请重新输入>\n");
printf("请输入您的密码>");
scanf("%s", ReceiveUserInput.password);
/*for (i = 0; i < 20; i++) {
ReceiveUserInput.password[i] = _getch();
if (ReceiveUserInput.password[i] == '\r') {
break;
}
else
{
printf("*");
}
}
printf("\n");*/
printf("请确认您的密码>");
scanf("%s", temp);//进行判断密码是否正确
/*for (i = 0; i < 20; i++) {
temp[i] = _getch();
if (temp[i] == '\r') {
break;
}
else {
printf("*");
}
}
printf("\n");*/
}
} while (1);
}
登录难度不大,我们找到文件以后,只需要我们进行密码的匹配即可,但这里同样有个细节就是,我们运用fread的时候是需要先读一个匹配匹配的,之后再往后遍历匹配,因为我进入过调试,我看到了当我仅仅是直接用feof函数读到末尾发现,可能会有不匹配的项目,一直弹不出正确的答案,就相当于我们让机器人找东西的时候,是需要先给它看看这个东西大致是长什么样子的,不是单纯的告诉它让它去找橘子香蕉这样的,是需要先给它看看橘子香蕉长什么样,再让它去找。这样进行先读一个再往后遍历读是正确的。
//登录用户
int Login() {
users ReceiveUserInput, ReadFromFile;//ReceiveUserInput是用来接收用户输入的,ReadFromFile是用来从文件中读取的
FILE* PFile = fopen("users.txt", "rb");//只读文件
if (!PFile) {
perror("fopen::PFil\n");
return;
}
printf("欢迎来到登录界面\n");
fread(&ReadFromFile, sizeof(users), 1, PFile);//先读一个匹配匹配
printf("请输入账号>");
scanf("%s", ReceiveUserInput.id);
while (1) {
if (strcmp(ReceiveUserInput.id, ReadFromFile.id) == 0) {//匹配成功
break;
}
else {
if (!feof(PFile)) {//未读到文本的结尾
fread(&ReadFromFile, sizeof(users), 1, PFile);//往后读
}
else {
printf("此账号不存在,请退出重试\n");
fclose(PFile);
PFile = NULL;
//system("pause");//暂停
//system("cls");//清屏
return 0;
}
}
}
int count = 0;
int count1 = 2;
printf("您只有三次输密码的机会!\n");
do {
printf("请输入密码>");
int i = 0;
for (i = 0; i < 20; i++) {
ReceiveUserInput.password[i] = _getch();
if (ReceiveUserInput.password[i] == '\r') {
break;
}
else
{
printf("*");
}
}
//i = i + 1;
ReceiveUserInput.password[i++] = '\0';
printf("\n");
//scanf("%s", ReceiveUserInput.password);
if (!strcmp(ReceiveUserInput.password, ReadFromFile.password)) {//密码匹配成功
printf("\n登录成功,欢迎使用\n");
printf("\n按任意键继续\n");
fclose(PFile);
PFile = NULL;//这个有或无都没什么大影响
return 1;
}
else {
printf("密码错误,请重新输入\n");
printf("还有%d次机会!\n", count1);
count1--;
count++;
}
if (count >= 3) {
break;
}
} while (strcmp(ReceiveUserInput.password, ReadFromFile.password));//输入密码相同即可登录
printf("三次机会输入密码均错误,请退出重试!\n");
return 0;
}
这里用到了加密,我们可以了解一下_getch函数就是不回显,它单纯是读入一个字符到缓冲区,而不进行显示出来,'\r’是回车键,我们读到不是回车键就输出*,是回车键就退出,那很多人就问了,我密码输入错误想按spaceback怎么办???那当然了spaceback也是一个字符,那我们就退出重试!!
找回密码就相当于信息的匹配,我们只需要将我们以前的信息进行匹配即可,匹配完蹦出来密码即可。
//找回密码
void Reback() {
users a, b;
FILE* fp = fopen("users.txt", "r");
char temp[20];
int count = 0;
printf("欢迎来到找回密码界面!\n");
fread(&b, sizeof(users), 1, fp);
printf("请输入账号\n");
scanf("%s", &a.id);
while (1){
if (strcmp(a.id, b.id) == 0){
break;
}
else{
if (!feof(fp)){
fread(&b, sizeof(users), 1, fp);
}
else{
printf("查无此人,请重新输入!\n");
//Sleep(500);
fclose(fp);
return;
}
}
}
printf("请输入姓名:\n");
scanf("%s", a.name);
do {
if (strcmp(a.name, b.name)) {
printf("姓名输入错误!请重新输入!\n");
scanf("%s", a.name);
}
} while (strcmp(a.name, b.name));
printf("请输入电话号码:\n");
scanf("%s", a.phone);
do {
if (strcmp(a.phone, b.phone)) {
printf("电话号码输入错误!请重新输入!\n");
scanf("%s", a.phone);
}
} while (strcmp(a.phone, b.phone));
printf("请输入性别:\n");
scanf("%s", a.sex);
do {
if (strcmp(a.sex, b.sex)) {
printf("性别输入错误!请重新输入!\n");
scanf("%s", a.sex);
}
} while (strcmp(a.sex, b.sex));
printf("您的密码是:%s\n", b.password);
system("pause");
system("cls");
}
我们在进行上面的函数接口的书写完成后,进行一步一步调试,那当然,写出来的肯定不是完美的,我们进入主函数中再进行调试,进行运行,一步一步进行看,想象不同的奇葩的“数”,输入看效果,那这里主函数直接移步到我的Gittee中吧~~~
⭐代码仓库:Data Structure
以上便是整个课设的思路与想法,实际上用到的大多数知识都是我们日常见到的知识,是我们学习过的知识,当我们把这些知识掌握了以后,相信敲一个课设不是很难,但是主要在于细心和调试的运用。