【开发细节】用C语言基础写学生管理系统(三)

前情回顾


  1. 自定义preconf.h头文件。作用:被核心头文件所包含,从而调用相关已定义的数据
  2. 定义BooleanStatus两种返回值类型,本质上是int类型
  3. 定义SqListElemType了两个结构体,实则属于独立结构的Struct嵌套
  4. 定义并初始化SysConfig结构体

一、本次目标


根据学生管理系统的功能特性与线性表的ADT描述,确定并编写相关的基本操作,便于后续的文件调用。

图片名称 GitHub:https://github.com/ITchujian/StudentManagementSystem_2022_C

注:为方便分享本次开发的经历,我把分析过程以及代码书写过程,以文字、图片形式合计放于开发记录中,但是一些非常基础的行为动作我将不会讲解或者阐述。
当前位置:【开发细节】用C语言基础写学生管理系统(三)
可跳转:

二、开发记录


步骤1

ADT——Abstract Data Type,意为抽象数据类型,我的理解:

描述数据结构的数学模型,以及该模型所能做出的操作

请注意,我们所使用的是线性表的顺序存储结构,而非链式结构,这一点已经在1.1文章的代码中有所体现。
因此,对于学生管理系统的线性表ADT如下:

  • 数据对象:D={学生1,学生2,……,学生n},n>=0
  • 数据关系:R={相邻学生之间存在先后关系}
  • 基本操作表:
序号函数名操作结果
1InitList初始化,构造空的线性表
2DestroyList销毁线性表
3ClearList清空线性表
4ListEmpty判断空的线性表
5ListLength返回线性表长度
6GetElem获取线性表的数据元素
7LocateElem返回满足一定条件的数据元素的位序
8SearchElem通过数据项搜索线性表
9ListInsert向线性表插入元素
10ListDelete向线性表删除元素
11ListTraverse遍历线性表的数据元素,执行同类型操作
12SwapElem交换两个数据元素的位置
13ListSort排序整个线性表
以上表格中,绝大部分的操作我们都将可能用到,拭目以待吧。

步骤2

创建kernel_list.h(代表这是一个与表相关的核心文件,后续的其他文件都需要调用该头文件中的操作)
在这里插入图片描述
此时VS2022已经给我们的头文件顶行加上了如下代码:

#pragma once

看来微软官方的IDE也比较推荐我们使用这种😂

步骤3

引入自定义的头文件preconf.h

#include "preconf.h"

步骤4

这一步,我们将实现基本操作表中的所有操作,函数的命名和表中呈现一样,使用大驼峰命名法(根据开发规范,此为核心文件,函数命名即为如此)。
初始化,构造空的线性表
通过malloc函数申请一片内存,然后判断内存是否分配成功;
赋予SqList结构体中的length初始值0,即现在0个学生;
初始分配空间值为128,单位为sizeof(ElemType)。

Status InitList(SqList* L)
{
	L->elem = (ElemType*)malloc(LIST_INIT_SIZE * sizeof(ElemType));
	if (!(L->elem))
		exit(OVERFLOWED);
	L->length = 0;
	L->list_size = LIST_INIT_SIZE;
	return OK;
}

销毁线性表
释放动态内存;
长度为0;
分配空间也为0;

Status DestroyList(SqList* L)
{
	free(L->elem);
	L->elem = NULL;
	L->length = 0;
	L->list_size = 0;
	return OK;
}

清空线性表

Status ClearList(SqList* L)
{
	L->length = 0;
	return OK;
}

判断空的线性表

Boolean ListEmpty(SqList* L)
{
	if (L->length == 0)
		return TRUE;
	else
		return FALSE;
}

返回线性表长度
这里可能我们会用不到,因为涉及长度的话,我们直接引用L->length就行了,但是写写也无妨,强迫症作祟🤣

int ListLength(SqList* L)
{
	return L->length;
}

获取线性表的数据元素
将第i个数据元素的值传给同类型的e,如果i不合法,退出并返回ERROR

Status GetElem(SqList* L, int i, ElemType* e)
{
	if (i < 1 || i > ListLength(L))
		exit(ERROR);
	*e = *((L->elem) + i - 1);
	return OK;
}

返回满足一定条件的数据元素的位序
我记得在上一章中写了,C++在兼容C的有些地方是有差异的,在学习Python、Java的同时,也比较浅显的了解了部分的C++语法。其中比较有意思的是,在C中,如果这样写

int Test(float(*tt)())
{
	return 0;
}

你是否会认为参数中的函数是无参数的函数呢?
其实并不是的,如果无参数,那其实我们应该这样写

int Test(float(*tt)(void))
{
	return 0;
}

加上void,才是函数无参的最好证明,而为我们在C++中,这种做法是不可取的,我们的项目是基于C++的,尽管是C编写的,所以明白这一点,我们才能避免一些错误和大坑。
以下的函数指针Status(*compare)(ElemType, ElemType)表示一个返回类型是Status且拥有两个参数的函数,参数类型是ElemType。

int LocateElem(SqList* L, ElemType* e, Status(*compare)(ElemType, ElemType))
{
	ElemType* p;
	int i = 1;
	p = L->elem;
	while (i <= (L->length) && !(*compare)(*e, *(p++)))
		++i;
	if (i <= (L->length))
		return i;
	else
		return 0;
}

通过数据项搜索线性表
参数cur_e是搜索项,在之前的定义结构中,我注明了学号是所有学生表中的唯一值,它是不可重复的,因此,我们查询学生信息时,必须利用好这一点;
参数mode_manner表示搜索的偏差,如果为-1,即寻找某学号对应同学的前驱,0则为查询该同学本身,1则为查询某学号对应同学的后继。
最终将搜索出来的同学,传给参数e。

Status SearchElem(SqList* L, int cur_e, ElemType* e, int mode_manner)
{
	ElemType* r = L->elem;
	int i = 1;
	while (i <= L->length && r->num != cur_e)
	{
		i++;
		r++;
	}
	if (i > L->length)
		return INFEASIBLE;
	switch (mode_manner)
	{
	case -1:
		*e = *(--r);
		return OK;
	case 0:
		*e = *r;
		return OK;
	case 1:
		*e = *(++r);
		return OK;
	default:
		return ERROR;
	}
	return OK;
}

向线性表插入元素
在第i(i∈[1, n])个元素之前插入一个元素,需将i→n个元素向后移动一个位置。

Status ListInsert(SqList* L, int i, ElemType e)
{
	if (i < 1 || i >(L->length) + 1)
		return ERROR;
	if (L->length >= L->list_size)
	{
		ElemType* new_base = (ElemType*)realloc(L->elem, (L->length + LIST_INCREMENT) * sizeof(ElemType));
		if (!new_base)
			exit(OVERFLOWED);
		L->elem = new_base;
		L->list_size += LIST_INCREMENT;
	}
	ElemType* q = &(L->elem[i - 1]);
	ElemType* p = &(L->elem[(L->length) - 1]);
	for (p = &(L->elem[(L->length) - 1]); p >= q; --p) /* 插入位置及之后的元素右移 */
		*(p + 1) = *p;
	*q = e;
	++(L->length);
	return OK;
}

向线性表删除元素
删除第i(i∈[1, n])个元素,需将i+1→n个元素向前移动一个位置。

Status ListDelete(SqList* L, int i, ElemType* e)
{
	if (i < 1 || i > L->length)
		return ERROR;
	ElemType* p = L->elem + i - 1;
	*e = *p;
	ElemType* q = L->elem + L->length - 1;
	for (++p; p <= q; ++p)
		*(p - 1) = *p;
	--(L->length);
	return OK;
}

遍历线性表的数据元素,执行同类型操作

Status ListTraverse(SqList* L, void(*visit)(ElemType*))
{
	int i;
	ElemType* p = L->elem;
	for (i = 1; i <= L->length; ++i)
		visit(p);
	return OK;
}

交换两个数据元素的位置

Status SwapElem(ElemType* x, ElemType* y)
{
	ElemType* z = (ElemType*)malloc(sizeof(ElemType*)+1);
	if (z == NULL)
		return OVERFLOWED;
	*z = *x;
	*x = *y;
	*y = *z;
	return OK;
}

排序整个线性表
因为目前只学过冒泡排序,所以这里就用它吧
sort_manner≤0时,表示降序排序
sort_manner>1时,表示升序排序
key为排序的主键,根据项目情况,它只能排序的项有:

  • 语文 l
  • 数学 m
  • 英语 e
  • 平均分 a
  • 总分 s
  • 学号 n
Status ListSort(SqList* L, int sort_manner, char key)
{
	int i, j;
	Boolean flag = TRUE;
	for (i = 0; i < (L->length) - 1 && flag == TRUE; i++)
	{
		flag = FALSE;
		for (j = 0; j < (L->length) - i - 1; j++)
		{
			if (sort_manner <= 0)
			{
				switch (key)
				{
				case 'a':
					if ((L->elem)[j].average_score < (L->elem)[j + 1].average_score)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 's':
					if ((L->elem)[j].sum_score < (L->elem)[j + 1].sum_score)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'l':
					if ((L->elem)[j].score_literature < (L->elem)[j + 1].score_literature)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'm':
					if ((L->elem)[j].score_math < (L->elem)[j + 1].score_math)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'e':
					if ((L->elem)[j].score_english < (L->elem)[j + 1].score_english)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'n':
					if ((L->elem)[j].num < (L->elem)[j + 1].num)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				default:
					printf("该主键不支持排序\n");
					return ERROR;
				}
			}
			else
			{
				switch (key)
				{
				case 'a':
					if ((L->elem)[j].average_score > (L->elem)[j + 1].average_score)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 's':
					if ((L->elem)[j].sum_score > (L->elem)[j + 1].sum_score)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'l':
					if ((L->elem)[j].score_literature > (L->elem)[j + 1].score_literature)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'm':
					if ((L->elem)[j].score_math > (L->elem)[j + 1].score_math)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'e':
					if ((L->elem)[j].score_english > (L->elem)[j + 1].score_english)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'n':
					if ((L->elem)[j].num > (L->elem)[j + 1].num)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				default:
					printf("该主键不支持排序\n");
					return ERROR;
				}
			}
		}
	}
	return OK;
}

kernel_list.hの的完整代码:

#pragma once  // 解决宏名引发的各种奇怪问题/Solve various strange problems caused by macro names
/*********************************************************************
 * 转载请注明来源/Reprint please indicate the source
 * @FileName kernellist.h
 * @Description 核心线性表/core linear table
 * @History
 * version      author      data       introduction and operations
 *  1.0         初见     2022-01-23             Create
 *  ***         ***      ****-**-**             *******
 */

#include "preconf.h"
 /*********************************************************************
  * @chujian(cn) 引入自定义头文件
  * @chujian(en) Import custom header files
  */

Status InitList(SqList* L)
{
	L->elem = (ElemType*)malloc(LIST_INIT_SIZE * sizeof(ElemType));
	if (!(L->elem))
		exit(OVERFLOWED);
	L->length = 0;
	L->list_size = LIST_INIT_SIZE;
	return OK;
}
/*********************************************************************
 * @chujian(cn) 初始化
 * @chujian(en) Initialize
 */

Status DestroyList(SqList* L)
{
	free(L->elem);
	L->elem = NULL;
	L->length = 0;
	L->list_size = 0;
	return OK;
}
/*********************************************************************
 * @chujian(cn) 销毁
 * @chujian(en) Destroy
 */

Status ClearList(SqList* L)
{
	L->length = 0;
	return OK;
}
/*********************************************************************
 * @chujian(cn) 清空
 * @chujian(en) Reset
 */

Boolean ListEmpty(SqList* L)
{
	if (L->length == 0)
		return TRUE;
	else
		return FALSE;
}
/*********************************************************************
 * @chujian(cn) 判断空表
 * @chujian(en) Judge the empty table
 */

int ListLength(SqList* L)
{
	return L->length;
}
/*********************************************************************
 * @chujian(cn) 返回长度
 * @chujian(en) Retrun length of the table
 */

Status GetElem(SqList* L, int i, ElemType* e)
{
	if (i < 1 || i > ListLength(L))
		exit(ERROR);
	*e = *((L->elem) + i - 1);
	return OK;
}
/*********************************************************************
 * @chujian(cn) 获取第i个数据元素
 * @chujian(en) Get the i-th data element
 */

int LocateElem(SqList* L, ElemType* e, Status(*compare)(ElemType, ElemType))
{
	ElemType* p;
	int i = 1;
	p = L->elem;
	while (i <= (L->length) && !(*compare)(*e, *(p++)))
		++i;
	if (i <= (L->length))
		return i;
	else
		return 0;
}
/*********************************************************************
 * @chujian(cn) 返回满足特定关系的数据元素位序
 * @chujian(en) Returns the bit order of data elements that satisfy a specified relationship
 */

Status SearchElem(SqList* L, int cur_e, ElemType* e, int mode_manner)
{
	ElemType* r = L->elem;
	int i = 1;
	while (i <= L->length && r->num != cur_e)
	{
		i++;
		r++;
	}
	if (i > L->length)
		return INFEASIBLE;
	switch (mode_manner)
	{
	case -1:
		*e = *(--r);
		return OK;
	case 0:
		*e = *r;
		return OK;
	case 1:
		*e = *(++r);
		return OK;
	default:
		return ERROR;
	}
	return OK;
}
/*********************************************************************
 * @chujian(cn) 学号搜索引擎
 * @chujian(en) Student ID Search Engine
 */

Status ListInsert(SqList* L, int i, ElemType e)
{
	if (i < 1 || i >(L->length) + 1)
		return ERROR;
	if (L->length >= L->list_size)
	{
		ElemType* new_base = (ElemType*)realloc(L->elem, (L->length + LIST_INCREMENT) * sizeof(ElemType));
		if (!new_base)
			exit(OVERFLOWED);
		L->elem = new_base;
		L->list_size += LIST_INCREMENT;
	}
	ElemType* q = &(L->elem[i - 1]);
	ElemType* p = &(L->elem[(L->length) - 1]);
	for (p = &(L->elem[(L->length) - 1]); p >= q; --p) /* 插入位置及之后的元素右移 */
		*(p + 1) = *p;
	*q = e;
	++(L->length);
	return OK;
}
/*********************************************************************
 * @chujian(cn) 插入
 * @chujian(en) insert
 */

Status ListDelete(SqList* L, int i, ElemType* e)
{
	if (i < 1 || i > L->length)
		return ERROR;
	ElemType* p = L->elem + i - 1;
	*e = *p;
	ElemType* q = L->elem + L->length - 1;
	for (++p; p <= q; ++p)
		*(p - 1) = *p;
	--(L->length);
	return OK;
}
/*********************************************************************
 * @chujian(cn) 删除
 * @chujian(en) delete
 */

Status ListTraverse(SqList* L, void(*visit)(ElemType*))
{
	int i;
	ElemType* p = L->elem;
	for (i = 1; i <= L->length; ++i)
		visit(p);
	return OK;
}
/*********************************************************************
 * @chujian(cn) 遍历所有数据进行同等操作
 * @chujian(en) Iterate over all data for the same operation
 */

Status SwapElem(ElemType* x, ElemType* y)
{
	ElemType* z = (ElemType*)malloc(sizeof(ElemType*)+1);
	if (z == NULL)
		return OVERFLOWED;
	*z = *x;
	*x = *y;
	*y = *z;
	return OK;
}
/*********************************************************************
 * @chujian(cn) 交换两个数据元素的位置
 * @chujian(en) Swap the positions of two data elements
 */

Status ListSort(SqList* L, int sort_manner, char key)
{
	int i, j;
	Boolean flag = TRUE;
	for (i = 0; i < (L->length) - 1 && flag == TRUE; i++)
	{
		flag = FALSE;
		for (j = 0; j < (L->length) - i - 1; j++)
		{
			if (sort_manner <= 0)
			{
				switch (key)
				{
				case 'a':
					if ((L->elem)[j].average_score < (L->elem)[j + 1].average_score)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 's':
					if ((L->elem)[j].sum_score < (L->elem)[j + 1].sum_score)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'l':
					if ((L->elem)[j].score_literature < (L->elem)[j + 1].score_literature)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'm':
					if ((L->elem)[j].score_math < (L->elem)[j + 1].score_math)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'e':
					if ((L->elem)[j].score_english < (L->elem)[j + 1].score_english)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'n':
					if ((L->elem)[j].num < (L->elem)[j + 1].num)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				default:
					printf("该主键不支持排序\n");
					return ERROR;
				}
			}
			else
			{
				switch (key)
				{
				case 'a':
					if ((L->elem)[j].average_score > (L->elem)[j + 1].average_score)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 's':
					if ((L->elem)[j].sum_score > (L->elem)[j + 1].sum_score)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'l':
					if ((L->elem)[j].score_literature > (L->elem)[j + 1].score_literature)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'm':
					if ((L->elem)[j].score_math > (L->elem)[j + 1].score_math)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'e':
					if ((L->elem)[j].score_english > (L->elem)[j + 1].score_english)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				case 'n':
					if ((L->elem)[j].num > (L->elem)[j + 1].num)
					{
						SwapElem(&((L->elem)[j]), &((L->elem)[j + 1]));
						flag = TRUE;
					}
					break;
				default:
					printf("该主键不支持排序\n");
					return ERROR;
				}
			}
		}
	}
	return OK;
}
/*********************************************************************
 * @chujian(cn) 冒泡排序(看起来比较繁琐,因为多个不同的排序项)
 * @chujian(en) Bubble Sor(Seems cumbersome here because of multiple different sort items)
 */

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

顾平安6

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

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

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

打赏作者

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

抵扣说明:

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

余额充值