学生信息管理系统--C语言实现

此前已经同组员们合作实现了一个较为简易的影院管理系统,积累了一些相关的经验,不至于独立上手制作学生管理系统时一头雾水。

也的确的使得所学习的内容具象化了,不是流于表面的一道道题,更重要的是提供了一个接口,一个浅浅地接触社会、工作中,所真实需求的技能,或者能力的接口。

抛砖引玉,第一次独立操作,很多方式都很老套简单,请多指教

一、开发环境:

开发工具:DEV C++ 5.11

开发语言:C语言

二、功能需求及说明:

  1. 数据录入:录入系统所需的数据信息。将录入的数据使用链表这一数据结构进行组织和管理。

  2. 数据存储:将录入的数据以文件的形式进行存储,优先推荐使用二进制文件格式。如果使用二进制文件存储,需要将数据按照一定的格式和结构转换为二进制形式,然后写入文件中。如果是文本文件存储,则需要将数据按照一定的文本格式(如JSON等)组织好,再写入文件中。

  3. 数据读写:能够从存储文件中读取数据,如果是二进制文件,可以直接按照数据的结构和格式从文件中读取二进制数据,并将其转换回相应的数据结构(如链表中的节点)。可以将数据写入到存储文件中,对于二进制文件,直接将数据以二进制形式写入;对于文本文件,则需要将数据按照文件的文本格式组织好后写入.

  4. 数据修改:可以对已存在的数据进行修改操作,即对链表中的某些节点数据进行更新。通过查找需要修改的数据节点,然后将其数据内容进行替换或更新,从而实现数据的修改。

  5. 数据插入:可以将新的数据插入到链表的任意指定位置。

  6. 数据删除:可以对已存在的数据进行删除操作。

  7. 数据查询:按要求对数据进行查找。

  8. 登录注册:将用户的账号和密码信息存储在文件中,用于实现用户的登录功能。在用户注册时,将新用户的账号和密码信息写入文件中进行存储;在用户登录时,从文件中读取账号密码信息,与用户输入的账号密码进行比对,从而实现用户的登录验证。

 ​ 三、具体实现:

<基本框架>:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define MAX_USERNAME_LENGTH 20
#define MAX_PASSWARD_LENGTH 20
#define MAX_NAME_LENGTH 20
#define MAX_CLASS_LENGTH 20
#define MAX_SCORES_LENGTH 50
// 细化超过限定范围数目情况 
typedef struct UserNode
{
	char username[MAX_USERNAME_LENGTH]; // 账号 
	char passward[MAX_PASSWARD_LENGTH]; // 密码 
	int role; // 身份:0-学生,1-教师,2-管理员
	struct UserNode *next; 
} UserNode;

typedef struct StudentNode
{
	char name[MAX_NAME_LENGTH]; // 姓名 
	char Class[MAX_CLASS_LENGTH]; // 班级-涉及专业 
	int scoreCount; // 根据专业不同,科目亦不同 
	double scores[MAX_SCORES_LENGTH]; // 成绩 
	struct StudentNode *next;
} StudentNode;

// 初始各链表的头结点 
UserNode *userList = NULL;
StudentNode *studentList = NULL;

// 加载用户数据 
void loadUsersFromFile()
{
	
}

// 加载学生数据 
void loadStudentsFromFile()
{
	
}

// 主界面 
void mainMenu()
{
	
}

// 常规删除链表操作 
void freeList()
{
	
}

int main()
{
	// 事先加载所有信息,便于后续使用
	// 实际为读取文件存入链表 
	loadUsersFromFile();
	loadStudentsFromFile();
	
	// 主页面 
	mainMenu();
	// 释放内存 
	freeList();
	
	return 0;
}

1. 相关load函数用于从文件中加载相关数据,并通过各自链表进行存储,以便于后续的增删改查操作

2. freeList用于在存储完成数据后释放内存,防止泄露

应当注意的是,由于个人实现以及操之过急,很多方面都没有来得及细化明确(后面不提了,过于追求完成而草率处理,羞愧) 

例如对于存储的数组,若实际应用中用户传入过长的输入,应当进行相关的反馈处理;

再例如所谓的班级,能否通过事先进行相关班级及其对应的科目的初始化,确保输入的正确以及规范化合理化;

这都是在假定项目可以真实应用的情况下,需要进行的切实的思考,毕竟完工是一个很简单的事情,可现实工作并不只是要求完工就万事大吉。

(1)从文件中加载数据:

void loadUsersFromFile()
{
	FILE *file = fopen("users.dat", "rb");
	if (file == NULL)
	{
		return ;
	}
	
	// 常规尾插法,持续更新prev 
	UserNode *prev = NULL;
	while (1)
	{
		UserNode *newNode = (UserNode*)malloc(sizeof(UserNode));
		// 读取完毕 
		if (fread(newNode->username, sizeof(char), MAX_USERNAME_LENGTH, file) != MAX_USERNAME_LENGTH)
		{
			free(newNode);
			break;
		}
		fread(newNode->passward, sizeof(char), MAX_PASSWARD_LENGTH, file);
		fread(&newNode->role, sizeof(int), 1, file);
		newNode->next = NULL;
		
		if (userList == NULL)
		{
			userList = newNode;
		}
		else
		{
			prev->next = newNode;
		}
		prev = newNode;
	}
	fclose(file);
}

void loadStudentsFromFile()
{
	// (如法炮制)
	FILE *file = fopen("students.dat", "rb");
	if (file == NULL)
	{
		return ;
	}
	
	StudentNode *prev = NULL;
	while (1)
	{
		StudentNode *newNode = (StudentNode*)malloc(sizeof(StudentNode));
		if (fread(newNode->name, sizeof(char), MAX_NAME_LENGTH, file) != MAX_NAME_LENGTH)
		{
			free(newNode);
			break;
		}
		fread(newNode->Class, sizeof(char), MAX_CLASS_LENGTH, file);
		fread(newNode->scores, sizeof(double), MAX_SCORES_LENGTH, file);
		fread(&newNode->scoreCount, sizeof(int), 1, file);
		newNode->next = NULL;
		
		if (studentList == NULL)
		{
			studentList = newNode;
		}
		else
		{
			prev->next = newNode;
		}
		prev = newNode;
	}
	fclose(file);
}

从事实上讲,用户和学生没有本质区别,只是实现过程中的处理的数据不一样,流程基本一致。

1. 事先尝试打开用于存储的文件

显然,如果file为NULL,说明此文件目前没有被创建,可以直接返回,因为并不影响后续的使用

2. 而存储数据不同

存储时,由于相关文件无法创建打开,就会造成后续操作无法进行,因此需要进行相关的反馈报错处理

这里通过常规的尾插法进行数据存储,而当fread的姓名或者账号的长度不再是定义的最大长度时,就说明读取已经完结,或者干脆出错了,就应当停止了。(一定记得关闭文件)

(一个tip):
// 常规删除链表操作 
void freeList()
{
	UserNode *curr1 = userList;
	while (curr1 != NULL)
	{
		UserNode *tmp1 = curr1;
		curr1 = curr1->next;
		free(tmp1);
	}
	
	StudentNode *curr2 = studentList;
	while (curr2 != NULL)
	{
		StudentNode *tmp2 = curr2;
		curr2 = curr2->next;
		free(tmp2);
	}
}

实际为一个简单的对单链表的删除操作

(2)主界面:

void mainMenu()
{
	// 未退出则始终循环 
	int choice;
	do
	{
		system("pause"); // 暂停清屏,一方面看起来方便简洁
		system("cls"); // 另一方面,更加符合现实情况,后续亦会使用
		printf("1. 注册\n");
		printf("2. 登录\n");
		printf("3. 修改密码\n");
		printf("4. 退出\n");
		printf("请选择: ");
		scanf("%d", &choice);
		// 根据选择进入不同函数 
		switch (choice)
		{
			case 1:
				registerUser(); // 注册
				break;
			case 2:
			{
				// 不同返回值进入各自的界面 
				int role = loginUser(); // 登录
				if (role != -1)
				{
					switch (role)
					{
						case 0:
							studentQueryScores(); // 学生
							break;
						case 1:
							teacherManageStudents(); // 教师
							break;
						case 2:
							adminManage(); // 管理员
							break;
					}
				}
				break;
			}
			case 3:
				changePassward(); // 修改密码
				break;
			case 4:
				system("cls");
				printf("退出系统\n");
				system("pause");
				break;
			default:
				printf("无效输入,请重试\n");
		}
	} while (choice != 4);
}

<相关核心函数的实现>:

// 保存数据
void saveUsersToFile()
{
    
}

void saveStudentsToFile()
{
    
}

void registerUser() // 注册
{

}

int loginUser() // 登录
{

}

void studentQueryScores() // 学生
{

}

void changePassward() // 修改密码
{

}

void teacherManageStudents() // 教师
{

}

void adminManage() // 管理员
{

}
(一个tip):

为确保数据的持续更新,每次增删改查等操作后,都会,且应当进行相关的文件保存操作,以保证对数据的更改能存入文件,而不滞后性

(1)数据保存:

void saveUsersToFile()
{
	FILE *file = fopen("users.dat", "wb");
	if (file == NULL)
	{
		printf("打开失败\n");
		return ;
	}
	
	UserNode *curr = userList;
	while (curr != NULL)
	{
		fwrite(curr->username, sizeof(char), MAX_USERNAME_LENGTH, file);
		fwrite(curr->passward, sizeof(char), MAX_PASSWARD_LENGTH, file);
		fwrite(&curr->role, sizeof(int), 1, file);
		curr = curr->next;
	}
	fclose(file);
}

void saveStudentsToFile()
{
	FILE *file = fopen("students.dat", "wb");
	if (file == NULL)
	{
		printf("打开失败\n");
		return ;
	}
	
	StudentNode * curr = studentList;
	while (curr != NULL)
	{
		fwrite(curr->name, sizeof(char), MAX_NAME_LENGTH, file);
        fwrite(curr->Class, sizeof(char), MAX_CLASS_LENGTH, file);
        fwrite(&curr->scoreCount, sizeof(int), 1, file);
        fwrite(curr->scores, sizeof(float), MAX_SCORES_LENGTH, file);
        curr = curr->next;
	}
	fclose(file); 
}
此刻若打开失败,应当进行反馈,因为我们的目的是写入文件,而非读取。

(2) 注册:

void registerUser()
{
	UserNode *newUser = (UserNode*)malloc(sizeof(UserNode));
	
	printf("输入用户名:");
	scanf("%s", newUser->username);
	
	// 遍历链表,确保账号首次注册 
	UserNode *curr = userList;
	while (curr != NULL)
	{
		if (strcmp(curr->username, newUser->username) == 0)
		{
			printf("用户名已注册\n");
			free(newUser);
			return ;
		}
		curr = curr->next;
	}
	printf("输入密码:");
	scanf("%s", newUser->passward);
	
	// 身份验证细化,防止恶意注册 
	int role;
	do
	{
		printf("输入身份:(0-学生,1-教师,2-管理员)");
		scanf("%d", &role);
		if (role < 0 || role > 2)
		{
			printf("无效输入\n");
		}
		// 循环保证数字输入正确 
	} while (role < 0 || role > 2);
	newUser->role = role;
	newUser->next = userList; // 接入链表 
	userList = newUser;
	
	saveUsersToFile(); // 保存 
	printf("操作成功\n");
}

同样的,作为一个练习项目,它本身可以处理的草率,但还是从现实角度考虑:

如何进行能保证诸如:

1. 多次输入强制退出

2. 身份细化上,不会出现学生恶意注册管理员、教师的情况

(第一反应是进行类似验证码机制,比如为每个人或者每类人发布一串码,这个码具有某些性质,可以利用相关性质进行身份验证,只是在个人的实现上有些难以处理)

(3)登录:

int loginUser()
{
	char username[MAX_USERNAME_LENGTH];
	char passward[MAX_PASSWARD_LENGTH];
	printf("输入账号:");
	scanf("%s", username);
	printf("输入密码:");
	scanf("%s", passward);
	
	UserNode *curr = userList;
	while (curr != NULL) // 对比文件内的数据,是否存在账号密码完全对应的情况
	{
		if (strcmp(curr->username, username) == 0 &&
			strcmp(curr->passward, passward) == 0)
			{
				printf("登陆成功\n");
				return curr->role;
			}
	}
	printf("账号密码有误\n");
	return -1;
}

与其他函数不同,登录函数有着确定身份的作用,通过返回相关的role以保证进入界面不同

(4)相关的学生、教师、管理员界面:

1/ 学生:
void studentQueryScores()
{
	char className[MAX_CLASS_LENGTH];
	printf("输入班级:");
	scanf("%s", className);
	
	bool check = false; // 查看是否存在此班
	StudentNode *curr = studentList;
	while (curr != NULL)
	{
		if (strcmp(curr->Class, className) == 0)
		{
			check = true;
			printf("姓名: %s\n", curr->name);
			printf("成绩: \n");
			for (int i = 0; i < curr->scoreCount; i ++)
			{
				printf("第 %d 个科目成绩为 %.2f\n", i + 1, curr->scores[i]);
			}
			printf("\n"); 
		}
		curr = curr->next;
	}
	if (check == false)
	{
		printf("查无此班\n");
		system("pause"); 
	}
}

该区块较为简单的实现了学生查看全班成绩的功能;

从事实上说,该区块可以结合对学生结点的细化来修改,即:是否可以通过事先声名好专业班级列表,改为选择而不是手动输入,以确保数据的规范化

(一个tip):

在教师和管理员的实现上,出于对视觉方面即可能因为函数过多带来不必要错误的考虑,最终选择了将各自模块写在了case内部,现实开发中应当避免这种行为。

2/ 教师:
void teacherManageStudents()
{ 
	int choice;
	do
	{
		system("pause");
		system("cls");
		printf("1. 添加学生信息\n");
        printf("2. 删除学生信息\n");
        printf("3. 修改学生信息\n");
        printf("4. 查询学生信息\n");
        printf("5. 查询班内成绩\n");
        printf("6. 返回\n");
        printf("请选择操作: ");
        scanf("%d", &choice);
        
        switch(choice)
        {
        	case 1:
        	{
        		StudentNode *newStudent = (StudentNode*)malloc(sizeof(StudentNode));
				if (newStudent == NULL)
				{
					printf("内存分配失败\n");
					break;
				}
				
				printf("输入学生姓名: ");
                scanf("%s", newStudent->name);
                printf("输入学生班级: ");
                scanf("%s", newStudent->Class);
                printf("输入成绩数量: ");
                scanf("%d", &newStudent->scoreCount);
                for (int i = 0; i < newStudent->scoreCount; i++)
				{
                    printf("输入第 %d 门成绩: ", i + 1);
                    scanf("%lf", &newStudent->scores[i]);
                }
                newStudent->next = studentList;
                studentList = newStudent;
                saveStudentsToFile();
                printf("添加成功\n");
        		break;
			}
			case 2:
			{
				char name[MAX_NAME_LENGTH];
                printf("输入要删除的学生姓名: ");
                scanf("%s", name);
                
                StudentNode *curr = studentList;
                StudentNode *prev = NULL;
                while (curr != NULL)
                {
                	if (strcmp(curr->name, name) == 0)
					{
                        if (prev == NULL)
						{
                            studentList = curr->next;
                        }
						else
						{
                            prev->next = curr->next;
                        }
                        free(curr);
                        saveStudentsToFile();
                        printf("删除成功\n");
                        break;
                    }
                    prev = curr;
                    curr = curr->next;
				}
				break;
			}
			case 3:
			{
				char name[MAX_NAME_LENGTH];
                printf("输入要修改的学生姓名: ");
                scanf("%s", name);
                
                StudentNode *curr = studentList;
                while (curr != NULL)
				{
                    if (strcmp(curr->name, name) == 0)
					{
                        printf("输入新的学生姓名: ");
                        scanf("%s", curr->name);
                        printf("输入新的学生班级: ");
                        scanf("%s", curr->Class);
                        printf("输入新的成绩数量: ");
                        scanf("%d", &curr->scoreCount);
                        for (int i = 0; i < curr->scoreCount; i++)
						{
                            printf("输入第 %d 门成绩: ", i + 1);
                            scanf("%lf", &curr->scores[i]);
                        }
                        saveStudentsToFile();
                        printf("修改成功\n");
                        break;
                    }
                    curr = curr->next;
                }
                break;
            }
            case 4:
            {
            	char name[MAX_NAME_LENGTH];
                printf("输入要查询的学生姓名: ");
                scanf("%s", name);
                
                StudentNode *curr = studentList;
                while (curr != NULL)
				{
                    if (strcmp(curr->name, name) == 0)
					{
                        printf("姓名: %s\n", curr->name);
                        printf("班级: %s\n", curr->Class);
                        printf("成绩: ");
                        for (int i = 0; i < curr->scoreCount; i++)
						{
                            printf("%.2f ", curr->scores[i]);
                        }
                        printf("\n");
                        break;
                    }
                    curr = curr->next;
                }
                break;
			}
			case 5:
			{
				char className[MAX_CLASS_LENGTH];
                printf("输入班级名称: ");
                scanf("%s", className);
                
                StudentNode *curr = studentList;
                while (curr != NULL)
				{
                    if (strcmp(curr->Class, className) == 0)
					{
                        printf("姓名: %s\n", curr->name);
                        printf("成绩: ");
                        for (int i = 0; i < curr->scoreCount; i++)
						{
                            printf("%.2f ", curr->scores[i]);
                        }
                        printf("\n");
                    }
                    curr = curr->next;
                }
                break;
			}
			case 6:
				printf("返回\n");
				system("pause");
				break;
			default:
				printf("无效输入\n");
		}
	} while (choice != 6);
}
3/ 管理员:
void adminManage()
{
	int choice;
	do
	{
		system("pause");
		system("cls");
        printf("1. 添加学生账号\n");
        printf("2. 删除学生账号\n");
        printf("3. 修改学生账号密码\n");
        printf("4. 查询学生账号信息\n");
        printf("5. 增删改查学生成绩\n");
        printf("6. 导出学生数据\n");
        printf("7. 返回\n");
        printf("请选择操作: ");
        scanf("%d", &choice);
        
		switch (choice)
        {
        	case 1: // 初始账号初始化 
        	{
        		UserNode *newUser = (UserNode*)malloc(sizeof(UserNode));
                if (newUser == NULL)
				{
                    printf("内存分配失败");
                    break;
                }
                
                strcpy(newUser->username, "student");
                int count = 0;
				
                UserNode *curr = userList;
                // 获取初始账号个数 
                while (curr != NULL)
				{
                    if (strstr(curr->username, "student") != NULL)
					{
                        count ++;
                    }
                    curr = curr->next;
                }
                sprintf(newUser->username + 7, "%d", count + 1);
                strcpy(newUser->passward, "123456");
                newUser->role = 0;
				
                newUser->next = userList;
                userList = newUser;
                saveUsersToFile();
                printf("初始账号添加成功\n用户名: %s,初始密码: 123456\n", newUser->username);
                break;
			}
			case 2:
			{
				char username[MAX_USERNAME_LENGTH];
                printf("输入要删除的学生名: ");
                scanf("%s", username);
                
                UserNode *curr = userList;
                UserNode *prev = NULL;
                while (curr != NULL)
				{
                    if (strcmp(curr->username, username) == 0 && curr->role == 0)
					{
                        if (prev == NULL)
						{
                            userList = curr->next;
                        }
						else
						{
                            prev->next = curr->next;
                        }
                        free(curr);
                        
                        saveUsersToFile();
                        printf("删除成功\n");
                        break;
                    }
                    prev = curr;
                    curr = curr->next;
                }
                break;
			}
			case 3:
			{
				char username[MAX_USERNAME_LENGTH];
                char newPassward[MAX_PASSWARD_LENGTH];
                printf("输入要修改的学生名: ");
                scanf("%s", username);
                
                UserNode *curr = userList;
                while (curr != NULL)
				{
                    if (strcmp(curr->username, username) == 0 && curr->role == 0)
					{
                        printf("输入新密码: ");
                        scanf("%s", newPassward);
                        strcpy(curr->passward, newPassward);
                        saveUsersToFile();
                        printf("修改成功\n");
                        break;
                    }
                    curr = curr->next;
                }
                break;
			}
			case 4:
			{
				char username[MAX_USERNAME_LENGTH];
                printf("输入要查询的学生名: ");
                scanf("%s", username);
                
                UserNode *curr = userList;
                while (curr != NULL)
				{
                    if (strcmp(curr->username, username) == 0 && curr->role == 0)
					{
                        printf("用户名: %s,密码: %s\n", curr->username, curr->passward);
                        break;
                    }
                    curr = curr->next;
                }
                break;
			}
			case 5:
			{
				teacherManageStudents(); // 和教师相同功能
				break; 
			}
			case 6:
			{
				FILE *eFile = fopen("stuList.dat", "wb");
                if (eFile == NULL)
				{
                    break;
                }
                
                StudentNode *curr = studentList;
                while (curr != NULL)
				{
                    fwrite(curr->name, sizeof(char), MAX_NAME_LENGTH, eFile);
                    fwrite(curr->Class, sizeof(char), MAX_CLASS_LENGTH, eFile);
                    fwrite(&curr->scoreCount, sizeof(int), 1, eFile);
                    fwrite(curr->scores, sizeof(float), MAX_SCORES_LENGTH, eFile);
                    curr = curr->next;
                }
                fclose(eFile);
                printf("已导出\n");
                break;
			}
			case 7:
				printf("返回\n");
				system("pause");
				break;
			default:
				printf("无效输入\n");
		}
	} while (choice != 7);
}

模拟实现管理员设置初始账号,并发给教师进行细化修改的情况,即:

1. 建立student n (n为序号数字)账号,并初始化诸如123456的密码

(sprintf大法好)

2. (一系列的增删改查)

3. 导出文件,类似备份的操作

(5)修改密码:

void changePassward()
{
	char username[MAX_USERNAME_LENGTH];
	char oldPassward[MAX_PASSWARD_LENGTH];
	char newPassward[MAX_PASSWARD_LENGTH];
	printf("输入用户名:");
	scanf("%s", username);
	printf("输入旧密码:");
	scanf("%s", oldPassward);
	
	UserNode *curr = userList;
	while (curr != NULL)
	{
		if (strcmp(curr->username, username) == 0 &&
			strcmp(curr->passward, oldPassward) == 0)
			{
				printf("请输入新密码:");
				scanf("%s", newPassward);
				strcpy(curr->passward, newPassward);
				printf("修改成功\n");
				return ;
			}
		curr = curr->next;
	}
	printf("账号密码有误\n");
}

同样的,遍历寻找oldPassward和账号完全匹配的用户,这里依旧可以细化处理输入新密码时,格式和长度的问题

四、总结:

虽然事前已经通过课设简单的完成过部分TTMS的模块,真正的到手上手自己独立的小项目,哪怕很多部分都很像,依旧感到有些困难。

不过即使过程中很困难,做出一个哪怕这么很多方面有些草率的成品后,还是会有一些成就感,真正的感受的自己学的东西到底可以用来做什么,而不是考个试做个题这么简单。

项目实现简单,请多指教!orz

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值