学校的期末小作业,相当于对我们本学期所学内容的一个总结。只要对标题所指内容有所了解即可轻松读懂本题解。下面我们按照要求一步步由浅入深地解决这个问题。
目录
静态版本
定义类型
添加
输出
查找
修改
删除
动态版本
定义类型
对静态版本的修改
添加
文件操作
文件加载
文件保存
链表
定义类型
初始化
添加
输出
查找
修改
删除
退出、保存文件、销毁
静态版本
定义类型
#define MAX 1000typedef struct student{int no; //学号char name[10]; //姓名int score; //成绩}student;typedef struct students{student data[MAX]; //创建int sz;//当前人数}students;
增删查改输出都需要知道当前人数,所以可以把结构体数组和人数再封装一个结构体。
初始化、打印菜单、选项
void menu(){printf(">>************************\n");printf(">>****1.添加 2.输出****\n");printf(">>****3.查找 4.修改****\n");printf(">>****5.删除 0.退出****\n");printf(">>************************\n");}enum option{退出,添加,输出,查找,修改,删除,};int main(){int input = 0;students stu = { 0 };do{menu();printf(">>请选择:");scanf("%d", &input);switch (input){case 添加:AddStu(&stu);break;case 输出:PrintStu(&stu);break;case 查找:SearchStu(&stu);break;case 修改:ModifyStu(&stu);break;case 删除:DelStu(&stu);break;case 退出:printf(">>退出\n");break;default:printf(">>选择错误,重新选择\n");break;}} while (input);return 0;}
以下关于各个功能的实现:
添加
首先判断是否已满,然后进行添加,每一次都添加在当前人数下标位置上,每添加一个sz++
void AddStu(students* pc){if (pc->sz == MAX){printf(">>已满,无法添加\n");return;}printf(">>请输入学号:");scanf("%d", &pc->data[pc->sz].no);printf(">>请输入姓名:");scanf("%s", pc->data[pc->sz].name);printf(">>请输入成绩:");scanf("%d", &pc->data[pc->sz].score);pc->sz++;printf(">>增加成功\n");}
输出
通过循环一个个输出即可
void PrintStu(const students* pc){int i;printf("%-5s\t%-10s\t%-5s\n", "学号", "姓名", "成绩");for (i = 0; i < pc->sz; i++){printf("%-5d\t%-10s\t%-5d\n", pc->data[i].no, pc->data[i].name, pc->data[i].score);}}
查找
由于修改和删除也需要用到查找功能,所以可以再封装一个函数,也可以再写个FindByName,FindByScore之类的函数,防止学号相同。找到返回下标,找不到返回-1,最后输出即可。
static int FindByNo(students* pc, int no){int i;for (i = 0; i < pc->sz; i++){if (pc->data[i].no == no)return i;}return -1;}void SearchStu(students* pc){int no;printf(">>请输入要查找的人的学号:");scanf("%d", &no);int pos = FindByNo(pc, no);if (pos == -1){printf(">>要查找的人不存在\n");return;}else{printf("%-5s\t%-10s\t%-5s\n", "学号", "姓名", "成绩");printf("%-5d\t%-10s\t%-5d\n", pc->data[pos].no, pc->data[pos].name, pc->data[pos].score);}}
修改
引用上个函数判断是否存在,然后输出。
void ModifyStu(students* pc){int no;printf(">>请输入要修改的人的学号:");scanf("%d", &no);int pos = FindByNo(pc, no);if (pos == -1){printf(">>要修改的人不存在\n");return;}else{printf(">>请输入学号:");scanf("%d", &pc->data[pos].no);printf(">>请输入姓名:");scanf("%s", pc->data[pos].name);printf(">>请输入成绩:");scanf("%d", &pc->data[pos].score);printf(">>修改成功\n");}}
删除
首先判断通讯录是否为空,然后查找。删除只要将其后面的信息依次往前挪即可,注意i<pc->sz-1,原本的最后一个不需要被覆盖,因为只要最后sz--了,以后也访问不到。
void DelStu(students* pc){int no, i;if (pc->sz == 0){printf(">>无人可删\n");return;}printf(">>请输入要删除人的学号:");scanf("%d", &no);int pos = FindByNo(pc, no);if (pos == -1){printf(">>要删除的人不存在\n");return;}for (i = pos; i < pc->sz-1; i++){pc->data[i] = pc->data[i + 1];}pc->sz--;printf(">>删除成功\n");}
静态版本最简单,但缺点很明显
大量的空间被浪费掉了。下面使用动态内存分配进行改造。
动态版本
定义类型
在类型定义方面,我们需要增加一个capacity变量记录当前最大容量,判断是否已满,是否需要扩容时需要用到。
typedef struct student{int no; //学号char name[10]; //姓名int score; //成绩}student;typedef struct students{student* data; //创建int sz;//当前人数int capacity; //当前最大容量}students;
自然地,对这个结构体变量不能简单的使用={0}进行初始化,而要写一个初始化函数:设置初始容量DEFAULT_SZ为3,每次扩容的增量INC_SZ为2。
#define DEFAULT_SZ 3#define INC_SZ 2void InitStu(students* pc){pc->data = (student*)malloc(DEFAULT_SZ * sizeof(student));if (pc->data == NULL){perror("InitStu");return;}pc->sz = 0;pc->capacity = DEFAULT_SZ;}
接下来思考功能方面有哪些需要改动
对静态版本的修改
添加
增容一般都发生在添加时,所以只需要修改添加函数即可,每次添加要判断是否已满,若已满,则使用realloc重新申请一块空间。
void AddStu(students* pc){if (pc->sz == pc->capacity){student* ptr = (student*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(student));if (ptr != NULL){pc->data = ptr;pc->capacity += INC_SZ;printf("增容成功\n");}else{perror("AddStu");printf(">>增加失败\n");return;}}printf(">>请输入学号:");scanf("%d", &pc->data[pc->sz].no);printf(">>请输入姓名:");scanf("%s", pc->data[pc->sz].name);printf(">>请输入成绩:");scanf("%d", &pc->data[pc->sz].score);pc->sz++;printf(">>增加成功\n");}
安全起见,退出时可以将其销毁。
void DestoryStu(students* pc){free(pc->data);pc->data = NULL;pc->sz = 0;pc->capacity = 0;}
其他函数可以照常运行。
文件操作
通讯录每次运行都不会保留上次的数据,所以需要文件操作将其保存到硬盘。
文件加载
每次运行都应加载上次保存的文件,也就是在初始化函数内调用LoadStu函数。
由于读文件时也要判断通讯录是否已满需要增容,所以对之前写过的直接封装函数void CheckCapacity(students* pc)
fread的返回值即为此次读到的元素个数,当未读到信息时while循环停止。
void CheckCapacity(students* pc){if (pc->sz == pc->capacity){student* ptr = (student*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(student));if (ptr != NULL){pc->data = ptr;pc->capacity += INC_SZ;printf("增容成功\n");}else{perror("AddStu");printf(">>增加失败\n");return;}}}void LoadStu(students* pc){FILE* pf = fopen("students.dat", "r");if (pf == NULL){perror("LoadStu");return;}//读文件student tmp = { 0 };while (fread(&tmp, sizeof(student), 1, pf)){CheckCapacity(pc);pc->data[pc->sz] = tmp;pc->sz++;}//关闭文件fclose(pf);pf == NULL;}
文件保存
通过循环一个个写进去即可。在退出时销毁前调用。
void SaveStu(students* pc){FILE* pf = fopen("students.dat", "w");if (pf == NULL){perror("SaveStu");return;}//写文件int i;for (i = 0; i < pc->sz; i++){fwrite(pc->data + i, sizeof(student), 1, pf);}//关闭文件fclose(pf);pf = NULL;}
其余不变。
链表
使用链表更加方便,无需考虑当前人数,是否已满和是否需要扩容。
定义类型
typedef struct student{int no; //学号char name[10]; //姓名int score; //成绩}student;typedef struct students{student data;struct students* next;}LinkList;
初始化
初始化如果没有数据就只会有一个头节点。下面加载文件采用后插法形成链表。
LinkList* InitStu(){LinkList* head;head = (LinkList*)malloc(sizeof(LinkList));if (head == NULL){return NULL;}head->next = NULL;LoadStu(head);return head;}void LoadStu(LinkList* head){FILE* pf = fopen("students.dat", "r");if (pf == NULL){perror("LoadStu");return;}//读文件student tmp = { 0 };LinkList* node;while (fread(&tmp, sizeof(student), 1, pf)){node = (LinkList*)malloc(sizeof(LinkList));if (node == NULL){return;}node->data = tmp;head->next = node;head = node;}head->next = NULL;//关文件fclose(pf);pf = NULL;}
添加
这里使用前插法,因为是头节点传参,如果采用后插法则不知道结尾在哪。
void AddStu(LinkList* head){LinkList* node = (LinkList*)malloc(sizeof(LinkList));if (node == NULL){return;}printf(">>请输入学号:");scanf("%d", &node->data.no);printf(">>请输入姓名:");scanf("%s", node->data.name);printf(">>请输入成绩:");scanf("%d", &node->data.score);node->next = head->next;head->next = node;printf(">>添加成功\n");}
输出
void PrintStu(LinkList* head){head = head->next;printf("%-5s\t%-10s\t%-5s\n", "学号", "姓名", "成绩");while (head != NULL){printf("%-5d\t%-10s\t%-5d\n", head->data.no, head->data.name, head->data.score);head = head->next;}}
查找
之前是返回下标,链表就返回节点的指针就好,未找到则返回NULL。
static LinkList* FindByNo(LinkList* head, int no){head = head->next;while (head != NULL){if (head->data.no == no){return head;}head = head->next;}return NULL;}void SearchStu(LinkList* head){int no;printf(">>请输入要查找人的学号:");scanf("%d", &no);LinkList* node = FindByNo(head, no);if (node == NULL){printf(">>要查找的人不存在\n");return;}else{printf("%-5s\t%-10s\t%-5s\n", "学号", "姓名", "成绩");printf("%-5d\t%-10s\t%-5d\n", node->data.no, node->data.name, node->data.score);}}
修改
void ModifyStu(LinkList* head){int no;printf(">>请输入要修改人的学号:");scanf("%d", &no);LinkList* node = FindByNo(head, no);if (node == NULL){printf(">>要修改的人不存在\n");return;}else{printf(">>请输入学号:");scanf("%d", &node->data.no);printf(">>请输入姓名:");scanf("%s", node->data.name);printf(">>请输入成绩:");scanf("%d", &node->data.score);}printf(">>修改成功\n");}
删除
由于链表的删除需要知道前一节点的位置,所以查找时不能使用FindByNo函数。
void DelStu(LinkList* head){int no;printf(">>请输入要删除人的学号:");scanf("%d", &no);LinkList* last = head;while (head != NULL){head = head->next;if (head->data.no == no){break;}last = last->next;}if (head == NULL){printf(">>要删除的人不存在\n");return;}last->next = head->next;free(head);printf(">>删除成功\n");}
退出、保存文件、销毁
void SaveStu(LinkList* head){FILE* pf = fopen("students.dat", "w");if (pf == NULL){perror("SaveStu");return;}//写文件head = head->next;while (head != NULL){fwrite(&head->data,sizeof(student),1,pf);head = head->next;}//关闭文件fclose(pf);pf = NULL;}void DestoryStu(LinkList* head){LinkList* last = head;while (head != NULL){head = head->next;free(last);last = head;}}
主函数
int main(){int input = 0;LinkList* pt = InitStu();do{menu();printf(">>请选择:");scanf("%d", &input);switch (input){case 添加:AddStu(pt);break;case 输出:PrintStu(pt);break;case 查找:SearchStu(pt);break;case 修改:ModifyStu(pt);break;case 删除:DelStu(pt);break;case 退出://保存到文件SaveStu(pt);//销毁学生们DestoryStu(pt);printf(">>退出\n");break;default:printf(">>选择错误,重新选择\n");break;}} while (input);return 0;}
本人较懒,通讯录的排序以及动态版本的空间的减容下次再搞吧。
另外这道题高度综合本学期课内知识,还是要把各个知识点吃透。寒假继续加油!