完整文档链接: 基于C/C++的学籍管理系统说明书
Github链接: 基于C/C++的学籍管理系统
项目背景
本文回顾了我在大学期间进行的一个C语言课程设计项目——学籍管理系统。该系统利用C语言的数据结构与算法知识,实现了一个基于命令行的学生信息管理程序。
通过这个项目的学习,我对C语言有了更深的理解,也锻炼了自己的编程能力。现在回顾这个项目,可以看到自己的成长与收获。
第 1 章 系统设计
1.1 总体设计
1.1.1 系统功能结构图

1.2 详细设计
1.2.1 程序中所用的主要数据的数据类型选择所用的数据结构
结构体,数组,链表。
1.2.2 程序中用到的所有函数的名称、功能,调用关系
- 目录 void DisplayMainMenu();
使用switch语句实现选择功能搭配while语句实现循环调用学生信息的添加,学生信息的删除,学生信息的修改,学生信息的插入,以及退出系统各项操作,并标注序号实现目录。 - 链表建立 struct node *CreateLink();
程序开始运行时从文件database.txt读取数据并生成链表返回头指针。 - 数据录入 int DataInput()
学生基本信息文件可以在磁盘建立,采用添加文件内容方式向data.txt中录入学生成绩基木信息。 - 数据查询 struct node *DataSearch(node *head);
调用链表建立函数生成的头指针,根据输入的学号遍历链表查询数据,找到相同的学号时把查找到的结果输出到屏幕上。 - 数据插入 struct node * DataDele (struct node *head)
调用链表建立函数生成的头指针,遍历链表数据,根据输出到屏幕上的文件数据内容输入数字选择某行并插入在这一行之前,并更新链表返回新的头指针。 - 数据删除 struct node *DataDele(struct node *head)
调用链表建立函数生成的头指针,遍历链表数据,根据输出到屏幕上的文件数据内容输入需要删除学生信息的学号,找到相同学号的学生数据删除该数据,并更新链表返回新的头指针。 - 数据修改 struct node *DataChange(node *head)
调用链表建立函数生成的头指针,遍历链表数据,根据输出到屏幕上的文件数据内容输入需要修改学生信息的学号,找到相同学号的学生数据修改该数据,并更新链表返回新的头指针。 - 链表更新 struct node *CreateLink();
数据插入、数据删除、数据修改三项操作后返回新的头指针链表自动更新,数据录入后遍历文件返回头指针更新链表。 - 文件更新 void ReLink(node *head)
数据录入后文件自动更新,数据插入、数据删除、数据修改三项操作返回的新头指针被调用遍历链表更新文件内容。 - 密码设置或修改 void COSpassword()
读取输入密码并存入文件中调用于设置密码和修改密码。 - 验证密码 int Check(char word[])
读取存储密码的文件,存在则判断密码输入是否正确,不存在则创建文件并调用函数设置密码。 - 数据加密与解密 void encryption(char* infname, char* outfname)
本模块同时用于对用户密码和对录入学生的数据进行加密和解密。
加密:传入文件名时读取infname文件内容按位异或加密数据存入文件名为outfname的文件中并且清空infname文件中内容。
解密:同理,由于将数据两次按位异或相同数值即可恢复初始值达到解密效果,所以仅需反向使用读取outfname文件内容并再次按位异或在写回infname中即可实现解密。 - 学生数据导出 void save(node* head, char* way)
程序关闭后学生数据被加密无法读取,本函数将当前所有学生数据生成文本文档另存为在用户指定位置。
第2章 编码设计
2.1 程序中结构体类型的定义
struct node {
char no[12];/*学号*/
char name[40];/*姓名*/
char tele[20];/*电话号码*/
char jg[60];/*籍贯*/
char addr[80];/*通讯地址*/
char D[18];/*身份证号码*/
struct node *next;
2.2 程序中调用的头文件、全局变量、程序中通用的符号常量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char trash_can[10001]; //回收多余字符,增强程序健壮性
char a[20] = "key.txt";//储存被加密密码文件名
char b[20] = "password.txt";//储存密码文件名
char c[20] = "keydata.txt";//储存被加密数据文件名
char d[20] = "data.txt";//储存数据文件名
2.3 所有函数的原型,功能、参数的意义
2.3.1 void DisplayMainMenu()
//菜单函数
void DisplayMainMenu() {
system("color fc");//白底红字
printf("**********************欢迎进入学籍信息系统**********************\n");
printf("\t\t\t(1) 数据录入\n ");
printf("\t\t\t(2) 数据查询\n ");
printf("\t\t\t(3) 数据修改\n ");
printf("\t\t\t(4) 数据删除\n ");
printf("\t\t\t(5) 数据插入\n ");
printf("\t\t\t(6) 退出程序\n ");
printf("****************************************************************\n");
printf("请输入你要执行的任务序号1~6:");
}
2.3.2 struct node *CreateLink()
用法一
- 程序开始运行时从文件data.txt中逐行读取数据;
- 采用尾插法使用指针p1,p2录入数据并建立链表,p1不断创建新结点,p2不断指向p1指向的前一个结点实现链表的创建返回头指针;
用法二
- 数据录入操作结束后从文件data.txt中逐行再次读取数据;
- 同用法一第2条再次重建链表返回头指针。
struct node* CreateLink() {/*尾插法建立链表*/
node* head = (node*)malloc(sizeof(node));
/*临时存储数据数组*/
char no[12];
char name[40];
char tele[20];
char jg[60];
char addr[80];
char D[18];
node* p, * q;
p = q = head; //初始化双指针指向头结点
FILE* fp = fopen("data.txt", "a+");//可读/写数据,原来的文件不被删去,位置指针移到文件末尾
//若文件不存在则新建文件,避免打不开文件情况出现
while (fscanf(fp, "%s %s %s %s %s %s\n", no, name, tele, jg, addr, D) != EOF) {//数据从文件到数组临时存储
q = (node*)malloc(sizeof(node)); //开辟下一个新节点
/*数组中数据存入链表*/
strcpy(q->no, no);
strcpy(q->name, name);
strcpy(q->tele, tele);
strcpy(q->jg, jg);
strcpy(q->addr, addr);
strcpy(q->D, D);
/*建立结点间连接*/
p->next = q;
p = q;
}
p->next = NULL;
fclose(fp);
return head;
}
2.3.3 void DataInput()
void DataInput() {//数据录入函数原型,本函数直接更新本地文本文件内容
system("cls");/*临时存储数据数组*/
char no[12];/*学号*/
char name[40];/*姓名*/
char tele[20];/*电话号码*/
char jg[60];/*籍贯*/
char addr[80];/*通讯地址*/
char D[19];/*身份证号码*/
char a, b;char trash_can[80];
puts("Enter 学生学号:");
while (gets(no) != NULL && no[0] != '\0') { //每次录入前读取到空行就结束录入
puts("Enter 学生姓名:");
gets(name);
puts("Enter 学生电话号码:");
gets(tele);
puts("Enter 学生籍贯:");
gets(jg);
puts("Enter 学生通讯地址:");
gets(addr);
puts("Enter 身份证号码:");
gets(D);
printf("确定要录入该数据么(仅读取输入第一个字符)y/anything else:");
scanf("%c", &b);
gets(trash_can);//回收多余字符
if (b == 'y') {
FILE* fp;
fp = fopen("data.txt", "a+");
//程序开始时运行文件就已存在或新被建立,不存在打不开文件的情况
fprintf(fp, "%s %s %s %s %s %s\n", no, name, tele, jg, addr, D);
fclose(fp);
printf("成功录入"); }
else printf("成功取消录入");
puts("empty line to stop\tor\tEnter anything to continue :\n");
if (a = getchar() != '\n') {
printf("Enter 学生学号\n");
gets(trash_can);//回收多余字符
continue;}
else printf("Bye!\n");
}
}
2.3.4 struct node *DataSearch(node *head)
- 调用链表头指针,根据输入的学号遍历链表查询数据;
- 找到相同的学号时把查找到的结果输出到屏幕上。
struct node* DataSearch(node* head) {//数据查询函数原型
system("cls");
char num[13];//存储查找的学号值
int flag = 0;
if (head->next == NULL) {
printf("\nThe list is void!\n");
return head; }
printf("输入查找学生的学号");scanf("%s", num);
struct node* p1;
p1 = head; //初始化指针指向头结点
while (p1->next != NULL) {//将Pl指针不断向后移动,搜索目标结点
p1 = p1->next;
if (atoi(num) == atoi(p1->no)) {
if (flag == 0) {
printf("找到下列数据\n");
printf("\n学号\t\t姓名\t 电话号码\t 籍贯\t\t通讯地址\t 身份证号码\n");
}flag = 1;
printf("%s \t%s \t%s \t %s \t%s \t\t %s\n", p1->no, p1->name, p1->tele, p1->jg, p1->addr, p1->D);}
}
if (flag == 0)printf("\nThe node is not found!\n");
return head;
}
2.3.5 void save(node* head, char* way)
//程序关闭后学生数据被加密无法读取,本函数将当前所有学生数据生成文本文档另存为在用户指定位置。
void save(node* head, char* way) {
node* p = head->next;
FILE* w = fopen(way, "r");
if (w == NULL) { //路径下无重名文件
FILE* w = fopen(way, "w");
fprintf(w, "学号\t\t姓名\t 电话号码\t\t 籍贯\t\t通讯地址\t\t 身份证号码\n");
while (p) {//遍历链表将数据存入文件
fprintf(w, "%s \t%s \t%s \t %s \t%s \t\t %s\n", p->no, p->name, p->tele, p->jg, p->addr, p->D);
p = p->next;
}
fclose(w);
}else {
printf("路径下有重名文件请更改路径或更换文件名重试\n");
fclose(w);}
}
2.3.6 void encryption(char* infname, char* outfname)
//按位异或正向使用解密,反向使用加密
void encryption(char* infname, char* outfname) { //加密解密函数
int key = 24;
FILE* fp1, * fp2;
fp1 = fopen(infname, "r");
fp2 = fopen(outfname, "w");
char ch1 = fgetc(fp1);
while (ch1 != EOF) {//逐字读取
fputc(ch1 ^ key, fp2);
ch1 = fgetc(fp1);
} //将需加密文件加密后保存到文件中
fp1 = fopen(infname, "w");//清空
fclose(fp1);
fclose(fp2);
}
2.3.7 struct node *DataChange(node *head)
1.调用链表的头指针,遍历链表数据;
2.根据输出到屏幕上的文件数据内容输入需要修改学生信息的学号,找到相同学号的学生数据修改该数据,更新链表返回新的头指针。
struct node* DataChange(node* head) {//数据修改函数原型
system("cls");
/*临时存储数据数组*/
char num[13];
char no[12];/*学号*/
char name[40];/*姓名*/
char tele[20];/*电话号码*/
char jg[60];/*籍贯*/
char addr[80];/*通讯地址*/
char D[19];/*身份证号码*/
/*收集并储存信息*/
if (head->next == NULL) {
printf("\nThe list is void!\n");
return head;
}
printf("输入查找的学号");
scanf("%s", num);
getchar();//回收回车
char a;
struct node* p1, * p2;
//空链表,不做任何处理
p1 = head;
//将Pl指针不断向后移动,搜索目标结点
while (atoi(num) != atoi(p1->no) && p1->next != NULL) {
p2 = p1;
p1 = p1->next;
}
if (atoi(num) == atoi(p1->no)) {
printf("找到下列数据\n");
printf("\n学号\t\t姓名\t 电话号码\t 籍贯\t\t通讯地址\t 身份证号码\n");
printf("%s \t%s \t%s \t %s \t%s \t\t %s\n", p1->no, p1->name, p1->tele, p1->jg, p1->addr, p1->D);
/*收集并储存信息多信息待开发*/
puts("Enter 学生学号:");
gets(no);
puts("Enter 学生姓名:");
gets(name);
puts("Enter 学生电话号码:");
gets(tele);
puts("Enter 学生籍贯:");
gets(jg);
puts("Enter 学生通讯地址:");
gets(addr);
puts("Enter 身份证号码:");
gets(D);
printf("确定要修改该数据么y/anything:");
scanf("%c", &a);
gets(trash_can);//回收多余字符
if (a == 'y') {
strcpy(p1->name, name);
strcpy(p1->tele, tele);
strcpy(p1->no, no);
strcpy(p1->D, D);
strcpy(p1->jg, jg);
strcpy(p1->addr, addr);
node* p = head->next;
printf("修改后数据");
printf("\n学号\t\t姓名\t 电话号码\t 籍贯\t\t通讯地址\t 身份证号码\n");
printf("%s \t%s \t%s \t %s \t%s \t\t %s\n", p1->no, p1->name, p1->tele, p1->jg, p1->addr, p1->D);
}
else
printf("取消修改成功");
}
else
printf("\nThe node is not found!\n");
return head;
}
2.3.8 struct node *DataDele(struct node *head)
- 调用链表的头指针,遍历链表数据;
- 根据输出到屏幕上的文件数据内容输入需要删除学生信息的学号,找到相同学号的学生数据删除该数据;
- 提示用户是否确定删除数据;
- 删除数据释放内存更新链表返回新的头指针。
struct node* DataDele(struct node* head) {
system("cls");
if (head->next == NULL) {
printf("\nThe list is void!\n");
return head; //空链表,不做任何处理
}
node* p = head->next;
printf("学号\t\t姓名\t 电话号码\t 籍贯\t\t通讯地址\t 身份证号码\n");
while (p) {
//输出链表节点数据到屏幕
printf("%s \t%s \t%s \t %s \t%s \t\t %s\n", p->no, p->name, p->tele, p->jg, p->addr, p->D);
p = p->next;
}
char num[13];
char a;
printf("\n输入删除学生的学号\n");
scanf("%s", num);
struct node* p1, * p2;
p1 = head->next;
//将Pl指针不断向后移动,搜索目标结点
while (atoi(num) != atoi(p1->no) && p1->next != NULL) {
p2 = p1;
p1 = p1->next;
}
//如果找到目标结点,则将其删除
//如果目标结点是头结点,则需要更新头指针
if (atoi(num) != atoi(p1->no)) {
printf("\nThe node is not found!\n");
}
else {
printf("确定要删除该数据么y/anything:");
getchar();
scanf("%c", &a);
if (a == 'y') {
if (p1 != head->next)
p2->next = p1->next;
else
head->next = p1->next;
free(p1);
printf("\n删除成功\n");
}
else {
printf("\n取消删除成功\n");
return head;
} }
return head;
}
2.3.9 void COSpassword()
//读取输入密码并存入文件中调用于设置密码和修改密码。
void COSpassword() {
START:
FILE* pf = fopen("password.txt", "w");
char password[80];
printf("请输入密码:");
gets(password);
if (password[0] == '\0' || password[0] == '\n') {
printf("\n密码不能为空,请重新设置\n");
goto START; }
fprintf(pf, "%s", password);
fclose(pf);
pf = NULL;//及时置NULL}
2.3.10 int Check(char word[])
//读取存储密码的文件,存在则判断密码输入是否正确,不存在则创建文件并调用函数设置密码
int Check(char word[]) {
FILE* pf = fopen("password.txt", "r");
if (pf == NULL) {
printf("首次使用登陆密码未设置\n");
FILE* pf = fopen("password.txt", "a+");
COSpassword();
return 1; }
char password[80];
fscanf(pf, "%s", password);
if (strcmp(password, word) == 0)
printf("密码输入正确\n");
else return 0;
fclose(pf);
pf = NULL;//及时置NULL
return 1;}
2.3.11 struct node* DataInsert(struct node* head)
//数据插入
1.调用链表头指针,遍历链表数据;
2.通过给选项和文件中数据标序号的方式,方便用户操作;
(1)在尾部插入输入-1;
(2)插入在第几行之前输入1~n\n;
请输入插入位置(输入-2取消插入操作):______;
3.根据输出到屏幕上的文件数据内容输入数字选择某行并插入在这一行前;
4.更新链表返回新的头指针。
struct node* DataInsert(struct node* head) {
system("cls");
int i = 1;
node* p = head->next;
char a;
printf("\n序号\t 学号\t\t姓名\t 电话号码\t 籍贯\t\t通讯地址\t 身份证号码\n");
while (p) {
//输出链表节点数据到屏幕
printf("(%d)\t %s \t%s \t%s \t %s \t%s \t\t %s\n", i++, p->no, p->name, p->tele, p->jg, p->addr, p->D);
p = p->next;
}
if (i == 1)
printf("\n!!!!!链表为空,输入-1直接添加\n\n");
double position;
TX:
printf("(1)在尾部插入输入-1\n(2)插入在第几行之前输入1~n\n\n请输入插入位置(输入-2取消插入操作):");
scanf("%lf", &position);
gets(trash_can);//回收多余字符
if (position == -2) {
printf("取消本次插入操作成功");
return head;
}
if (position == 0 || (int)position < -1 || int(position) != position || position >= i) {
printf("\n\n输入错误,请再次输入\n");
goto TX;
}
char no[12];/*学号*/
char name[40];/*姓名*/
char tele[20];/*电话号码*/
char D[18];/*身份证号码*/
char jg[60];/*籍贯*/
char addr[80];/*通讯地址*/
struct node* p1, * p2;
p1 = head;
puts("Enter 学生学号:");
getchar();
gets(no);
puts("Enter 学生姓名:");
gets(name);
puts("Enter 学生电话号码:");
gets(tele);
puts("Enter 学生籍贯:");
gets(jg);
puts("Enter 学生通讯地址:");
gets(addr);
puts("Enter 身份证号码:");
gets(D);
/*收集并储存信息*/
printf("确定要在该行前插入该数据么 (仅读取输入第一个字符)y/anything else:");
scanf("%c", &a);
if (a == 'y') {
//在尾部插入
if (position == -1) {
while (p1->next != NULL) {
p2 = p1;
p1 = p1->next;
}
p2 = (node*)malloc(sizeof(node));
p1->next = p2;
p2->next = NULL;
strcpy(p2->name, name);
strcpy(p2->tele, tele);
strcpy(p2->no, no);
strcpy(p2->D, D);
strcpy(p2->jg, jg);
strcpy(p2->addr, addr);
}
// 在某个位置插入
if (position > 0) {
while (position > 0 && p1->next != NULL) {
position--;
p2 = p1;
p1 = p1->next;
}
struct node* newnode = (node*)malloc(sizeof(node));
p2->next = newnode;
strcpy(newnode->name, name);
strcpy(newnode->tele, tele);
strcpy(newnode->no, no);
strcpy(newnode->D, D);
strcpy(newnode->jg, jg);
strcpy(newnode->addr, addr);
newnode->next = p1;
}
printf("插入成功");
}
else
printf("取消插入成功");
return head;
}
2.3.12 void ReLink(node *head)
- 数据插入、数据删除、数据修改三项操作后返回的新头指针被调用用于遍历链表;
- 将每项数据逐行存入文件data.txt,更新文件内容。
void ReLink(node* head) {
node* p = head->next;
FILE* w = fopen("data.txt", "w");//只能向文件写数据,若指定的文件不存在则创建它,如果存在则先删除它再重建一个新文件
//每次更新链表重写所有文件,程序开始时运行文件就已存在或新被建立,不存在打不开文件的情况
while (p) {//遍历链表将数据存入文件
fprintf(w, "%s %s %s %s %s %s\n", p->no, p->name, p->tele, p->jg, p->addr, p->D);
p = p->next;
}
printf("\n");
fclose(w);
return;
}
2.3.13 int main()
int main() {
FILE* pf = fopen("key.txt", "r");
char ch1 = fgetc(pf);
if (pf != NULL && ch1 != EOF)
encryption(a, b); //解密密码
ch1 = fgetc(pf);
pf = fopen("keydata.txt", "r");
if (pf != NULL && ch1 != EOF)
encryption(c, d); //解密文件
char password[80];
pf = fopen("password.txt", "r");
fclose(pf);
if (pf != NULL) {
printf("请输入密码:");
gets(password);
}
if (Check(password) == 1) {
node* head;
head = CreateLink();//读取文件数据建立链表
int x, flag = 1;
encryption(b, a);//加密密码
while (flag) {
DisplayMainMenu(); // 因为要多次调用工作菜单,要采用子函数的形式
if (scanf("%d", &x) == 0)
x = 999;
gets(trash_can);//回收多余字符
switch (x) {
case 1:
DataInput();
head = CreateLink();
break;
case 2:
ReLink(DataSearch(head));
break;
case 3:
ReLink(DataChange(head));
break;
case 4:
ReLink(DataDele(head));
break;
case 5:
ReLink(DataInsert(head));
break;
case 6:
flag = 0;
break;
case 7:
COSpassword();
printf("\n修改密码成功\n\n");
encryption(b, a);//加密密码
break;
case 8:
char way[80];
printf("例子:D:\\a.txt\n");
printf("请输入另存为路径及文件名:");
gets(way);
save(head, way);
break;
default:
printf("\n输入错误\n\n");
gets(trash_can);//回收多余字符
break;
}
}
system("pause");
encryption(d, c);//加密文件
return 0;
}
else
printf("\n密码输入错误\n\n");
return 0;
}
3. 个人收获
通过这个项目,我对C语言有了更深入的理解,提高了编程能力。主要收获包括:
- 熟练使用C语言语法
- 理解指针和内存管理
- 能够设计和实现基本数据结构
- 具备完整的项目实现经验
- 初步了解软件设计方法论
这个项目使我对编程更有兴趣,也锻炼了坚持完成一个项目的意志。为我以后从事计算机编程工作打下了基础。
C/C++实现的学籍管理系统设计与实现

本文介绍了基于C/C++的学籍管理系统,包括系统设计、编码实现和主要功能,如数据录入、查询、修改、删除和插入。系统利用链表数据结构,实现了对学生信息的管理,并采用了数据加密和解密技术保证信息安全。
1030

被折叠的 条评论
为什么被折叠?



