学校的期末小作业,相当于对我们本学期所学内容的一个总结。只要对标题所指内容有所了解即可轻松读懂本题解。下面我们按照要求一步步由浅入深地解决这个问题。
目录
静态版本
定义类型
#define MAX 1000
typedef 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 2
void 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;
}
本人较懒,通讯录的排序以及动态版本的空间的减容下次再搞吧。
另外这道题高度综合本学期课内知识,还是要把各个知识点吃透。寒假继续加油!