[C语言] 通讯录|静态 动态 文件 链表 多版本讲解

学校的期末小作业,相当于对我们本学期所学内容的一个总结。只要对标题所指内容有所了解即可轻松读懂本题解。下面我们按照要求一步步由浅入深地解决这个问题。

目录

静态版本

定义类型

添加

输出

查找

修改

删除

动态版本

定义类型

对静态版本的修改

添加 

文件操作

文件加载

文件保存

链表

定义类型

初始化

添加

输出

查找

修改

删除

退出、保存文件、销毁


静态版本

定义类型

#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;
}

 本人较懒,通讯录的排序以及动态版本的空间的减容下次再搞吧。

另外这道题高度综合本学期课内知识,还是要把各个知识点吃透。寒假继续加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

世真

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值