IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【1.6 C案例】请君与我用C语言写一个千行的学生管理系统 -> 正文阅读

[C++知识库]【1.6 C案例】请君与我用C语言写一个千行的学生管理系统

前情回顾


完成了所有自定义头文件的编写

一、本次目标


完成程序主入口,一一对应功能实现算法,不断调直到所有功能基本上正常。

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

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

二、开发记录


步骤1

引用自定义头文件,sysbrowse.hsysdoc.hsysmodify.h
定义main主函数,在主函数中,先加载系统设置文件config.bin

loadConfig();

步骤2

定义一个线性表类型的变量:

SqList student_list;

然后将其初始化:

InitList(&student_list);

步骤3

加载线性表配置list_path.bin,最后加载学生信息students.bin

loadList(&student_list);
loadStu(&student_list);

步骤4

根据程序的功能结构:
在这里插入图片描述
利用do~while()switch~case~default实现功能的选择,如下:

do
{
	int home_select;
	scanf("%d", &home_select);
	switch (home_select)
	{
	case 1:
	{
		// 学生信息浏览系统
		break;
	}
	case 2:
	{
		// 学生信息修改系统
		break;
	}
	case 3:
	{
		// 保存信息
		break;
	}
	case 4:
	{
		// 系统设置
		break;
	}
	case 0:
		// 退出程序
		return 0;
	default:
		// 判定其他的选择
		break;
	}
} while (TRUE);

在Switch语句前,我需要编写一个在主页显示的菜单:

Status homeMenu(void)
{
	printf("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n");
	printf("┃                            学 生 信 息 管 理 系 统                          ┃\n");
	printf("┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫\n");
	printf("┃请选择操作:                                                                  ┃\n");
	printf("┃                             1 > 学生信息浏览系统                            ┃\n");
	printf("┃                             2 > 学生信息修改系统                            ┃\n");
	printf("┃                             3 > [重要]保存信息                              ┃\n");
	printf("┃                             4 > 系 统 设 置                                 ┃\n");
	printf("┃                             0 > 退 出 系 统                                 ┃\n");
	printf("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n");
	printf("\nINPUT:");
	return OK;
}

然后,在Switch语句前添加:

system("cls");  // 为了每次循环后对之前的操作进行清屏
homeMenu();

步骤5

学生信息浏览系统中,我们将其分为5个部分:
在这里插入图片描述
同理制作一个选择器,0返回主页的菜单选择界面。
并将sysbrowse.hkernel_list.h中的相关方法一一部署,如下展示:

Status browseStuSystem(SqList* L)
{
	do
	{
		system("cls");
		browseMenu();
		int browse_select;
		scanf("%d", &browse_select);
		switch (browse_select)
		{
		case 1:
		{
			putAllList(L);
			system("pause");
			break;
		}
		case 2:
		{
			int student_id;
			printf("若学号不存在,则返回null,表示未找到\n");
			printf("INPUT:");
			scanf("%d", &student_id);
			ElemType* e = (ElemType*)malloc(sizeof(ElemType));
			if (e == NULL)
				exit(INFEASIBLE);
			Status re = SearchElem(L, student_id, e, 0);
			if (re == INFEASIBLE)
			{
				printf("null\n");
				system("pause");
				break;
			}
			putSingleList(e);
			free(e);
			system("pause");
			break;
		}
		case 3:
		{
			int sort_manner;
			char sort_key;
			printf("排序方式(0或任意负数为降序、1或任意正数为升序)\n");
			printf("INPUT:");
			scanf("%d", &sort_manner);
			getchar();
			printf("\n排序项(n-学号、l-语文、m-数学、e-英语、a-平均分、s-总分)\n");
			printf("INPUT:");
			scanf("%c", &sort_key);
			ListSort(L, sort_manner, sort_key);
			putAllList(L);
			system("pause");
			break;
		}
		case 4:
		{
			int sort_manner, i;
			char sort_key;
			ElemType* e = (ElemType*)malloc(sizeof(ElemType));
			getchar();
			printf("查询科目(l-语文、m-数学、e-英语、a-平均分、s-总分)\n");
			printf("INPUT:");
			scanf("%c", &sort_key);
			printf("正排名还是倒排名(0或任意负数为正排名、1或任意正数为倒排名)\n");
			printf("INPUT:");
			scanf("%d", &sort_manner);
			printf("第几名?\n");
			printf("INPUT:");
			scanf("%d", &i);
			rankSearch(L, sort_manner, sort_key, i, e);
			free(e);
			system("pause");
			break;
		}
		case 5:
		{
			printf("输入0则以默认方式统计[科目分数60及格、总分180分及格],非0则为自定义统计\n");
			printf("INPUT:");
			int select;
			char comp_item[5][10] = { "语文", "数学", "英语", "平均分", "总分" };
			float comp_score[5] = { 60, 60, 60, 60, 180 };
			scanf("%d", &select);
			if (select == 0)
				statistic(L, comp_score);
			else
			{
				for (int i = 0; i < 5; i++)
				{
					printf("%s:", comp_item[i]);
					scanf("%f", &comp_score[i]);
				}
				statistic(L, comp_score);
			}
			system("pause");
			break;
		}
		case 0:
			return OK;
		default:
			printf("该按键未开发任何功能,请重新输入\n");
			break;
		}
	} while (TRUE);
}

之后在主函数的选择器case 1中调用这个函数即可。

步骤6

与步骤4同一思路,对代码稍加修改即可。
其中,学生信息修改系统的代码如下:

Status changeStuSystem(SqList* L)
{
	do
	{
		system("cls");
		changeMenu();
		int change_select;
		scanf("%d", &change_select);
		switch (change_select)
		{
		case 1:
		{
			if (addStudent(L))
				printf("添加成功!\n");
			else
				printf("添加失败!\n");
			system("pause");
			break;
		}
		case 2:
		{
			int student_id;
			printf("若学号不存在,则修改失败\n");
			printf("INPUT:");
			scanf("%d", &student_id);
			if (changeElem(L, student_id) != INFEASIBLE)
				printf("修改成功!\n");
			else
				printf("修改失败!\n");
			system("pause");
			break;
		}
		case 3:
		{
			int student_id;
			printf("若学号不存在,则删除失败\n");
			printf("INPUT:");
			scanf("%d", &student_id);
			deleteElem(L, student_id);
			system("pause");
			break;
		}
		case 4:
		{
			resetAll(L);
			system("pause");
			break;
		}
		case 0:
			return OK;
		default:
			printf("该按键未开发任何功能,请重新输入\n");
			break;
		}
	} while (TRUE);
}

保存文件的代码如下:

Status ioFileStuSystem(SqList* L)
{
	getchar();
	char is_yes;
	printf("请输入y或Y确认:");
	scanf("%c", &is_yes);
	if (is_yes == 'y' || is_yes == 'Y')
	{
		writeFile(config_bin.list_path, L, sizeof(SqList), 1);
		writeFile(config_bin.file_path, L->elem, sizeof(ElemType), L->length);

	}
	else
		return INFEASIBLE;
	system("pause");
	return OK;
}

系统设置的代码如下:

Status setSystem(SqList* L)
{
	do
	{
		system("cls");
		setMenu();
		int set_select;
		scanf("%d", &set_select);
		switch (set_select)
		{
		case 1:
		{
			char dir_path[] = ".\\SMSdir";
			createFolder(dir_path);
			char config_path[128];
			sprintf(config_path, "%s\\config.bin", dir_path);
			printf("\n学生信息表存储路径(默认是.\\SMSdir\\students.bin):");
			scanf("%s", config_bin.file_path);
			writeFile(config_path, &config_bin, sizeof(SysConfig), 4);
			break;
		}
		case 2:
		{
			char dir_path[] = ".\\SMSdir";
			createFolder(dir_path);
			char config_path[128];
			sprintf(config_path, "%s\\config.bin", dir_path);
			printf("\n学生信息表备份路径(默认是.\\SMSdir\\students.bin.bak):");
			scanf("%s", config_bin.backup_path);
			writeFile(config_path, &config_bin, sizeof(SysConfig), 4);
			break;
		}
		case 3:
		{
			char dir_path[] = ".\\SMSdir";
			createFolder(dir_path);
			char config_path[128];
			sprintf(config_path, "%s\\config.bin", dir_path);
			colorMenu();
			char color[4];
			printf("\n第一个表示背景色 第二个表示字体颜色 如06或者1A\n");
			printf("INPUT:");
			scanf("%s", color);
			sprintf(config_bin.sys_color, "color %s", color);
			writeFile(config_path, &config_bin, sizeof(SysConfig), 4);
			break;
		}
		case 4:
		{
			char dir_path[] = ".\\SMSdir";
			createFolder(dir_path);
			char config_path[128];
			sprintf(config_path, "%s\\config.bin", dir_path);
			printf("\n表格内核路径(默认是.\\SMSdir\\list_path.bin):");
			scanf("%s", config_bin.list_path);
			writeFile(config_path, &config_bin, sizeof(SysConfig), 4);
			break;
		}
		case 0:
			return OK;
		default:
			printf("该按键未开发任何功能,请重新输入\n");
			break;
		}
	} while (TRUE);
}

步骤7

分别对应主函数的选择开关部署这些功能函数。
我们的代码到此貌似已经写完了,接下来就是调试程序。
编译——直接报错,我有点懵了,报错信息如下:

C4996 ‘scanf’: This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. StuMS E:\c_program\StuMS\StuMS\SMS_2022.cpp 44

看到这里,我算是想起来了,VS中使用scanf()函数会报错,scanf()在读取时不检查边界,所以可能会造成内存泄露,所以VS提供了scanf_s()来替代,但是这里我们这样应付它:
在这里插入图片描述
然后在最后一行,添加上一句?_CRT_SECURE_NO_WARNINGS
接下来,再次编译,成功!
然后,运行一下,一切正常,输入0退出一下,惊愕的一幕出现了:程序卡住。
![在这里插入图片描述](https://img-blog.csdnimg.cn/eb7866b754b64f3685e8a1f7f58e1c9b.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5Yid6KeB5bCP6I-cRw==,size_20,color_FFFFFF,t_70,g_se,x_16在这里插入图片描述

啊这,咱也不知道为啥,调试一下吧,暂且先无断点调试!
在这里插入图片描述
错误:

读取某一位置时发生访问冲突

这是为啥呢?毕竟刚学完C语言没多久,第一次遭遇这个问题,思前想后的,猜测一波,访问冲突了,那该地址不会被其它程序使用了导致不可访问吧?那也不对啊,kernel.h头文件中,线性表初始化肯定没有问题的:

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

假设,我们在初始化就分配内存空间失败了,那就导致后面使用student_list这个结构体变量也会直接报错。
而我们却是在选择器中输入0遭遇的,所以这段函数是没有问题的。
然后我们去掉InitList后的加载配置,添加两个学生,然后回到主菜单保存信息,信息保存成功了。
再次恢复原来的代码,运行,发现了这个问题:
在这里插入图片描述
不服气的我,再度运行了多次,结果我电脑上的卡巴斯基是这样提醒我的:
在这里插入图片描述
大约500多个感染对象,我寻思,这内存访问了不该访问的?挺吓人的C语言,危险之处体现的淋漓尽致。
回到正题,我分明做了存储操作的,为什么还是无法保存?存储的算法错了吗?之后我特地测试了一番存储和读取的算法,基本上是没有问题的。那么可能是这两句加载有问题了:

loadList(&student_list);
loadStu(&student_list);

思考一番,我在每一句前后写上这样一句:

printf("0x%p\n", student_list.elem);

代码如下:

InitList(&student_list);
printf("0x%p\n", student_list.elem);  // 初始化后、加载上一次保存的表信息前
loadList(&student_list);
printf("0x%p\n", student_list.elem);  // 加载上一次保存的表信息后、加载上一次的学生数据之前
loadStu(&student_list);
printf("0x%p\n", student_list.elem);  // 加载上一次的学生数据后

然后在do所在行打上一个断点,用于卡住结果,也防止清屏操作。
在这里插入图片描述
运行结果如下:
在这里插入图片描述
很神奇,student_list的地址居然发生了改变,简单三行,透露了巨大的信息。
第一个信息:验证我所说的初始化是正确的!
第二个信息:loadList(&student_list);这一行将导致分配的地址发生改变!
第三个信息:初始化的地址没有被释放,指针重新被指向了其他区域,这也难怪访问冲突了,访问了不该访问的。
随即进行逐语句调试追踪:
先打断点,然后逐语句进行,观察局部变量监视区
在这里插入图片描述
此时,我们的运行到这里:
在这里插入图片描述
F11下一语句,将跳转到sysdoc.h中,内存地址未发生变化:
在这里插入图片描述
再按F11,跳转到readFile函数中
在这里插入图片描述
下面这两语句并不影响:

FILE* fp;
fopen_s(&fp, path, "rb+");

我们直接越到fread()
在这里插入图片描述
再度执行F11在这里插入图片描述
此时直接Shift+F11跳出该函数:
在这里插入图片描述
惊奇的发现,elem的地址发生改变了,而且其中很多数据项对应显示无法读取内存,那原因就很简单了。
我们的list_path.bin文件读取的是二进制流,通过数据流形式,上一次的结构体student_list将把elem(这里的elem并不会包括学生信息,它仅仅指向地址)、lengthlist_size存储到其中。
第二次打开后,通过数据流形式,本次的结构体student_list读取到上一次的结构体信息,没想到的是,连地址也读取进来了,而该地址是否有效完全是运气所然,几乎上是无效地址,不可访问的。
因此,我们需要为list_path.bin的读写单独写一下代码。

步骤8

之前的代码如下:

  • 写入文件
writeFile(config_bin.list_path, L, sizeof(SqList), 1);
  • 读取文件
readFile(config_bin.list_path, L, sizeof(SqList), 3)

修改的方法也很简单,那就是我们仅仅只需要存储lengthlist_size就可以了。

  • 写入文件
FILE* fp;
fopen_s(&fp, config_bin.list_path, "w");
if (fp != NULL)
{
	fprintf(fp, "%d %d\n", L->length, L->list_size);
	fclose(fp);
	printf("保存成功\n");
}
  • 读取文件
FILE* fp;
fopen_s(&fp, config_bin.list_path, "r+");
if (fp != 0)
{
	fscanf_s(fp, "%d %d\n", &(L->length), &(L->list_size));
	fclose(fp);
	return OK;
}
else
	return INFEASIBLE;

经过这样一番小折腾,成功解决了,运行效果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

基本上把所有功能都试了一个遍,暂时来看还算比较完美,文件备份这一块的功能我就不做了,毕竟它也只算是相似代码的重复劳动工作,意义不大,仅仅是为了加深对C学习的综合总结。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-01-29 22:54:07  更:2022-01-29 22:54:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:23:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码