前言
在数据结构的学习过程中,我们经常需要将理论知识应用到实际问题中去。本文正是基于这一目的,通过使用线性表来构建一个简单的学生选课信息管理系统。这个系统可以实现读取课程和学生信息、添加或删除学生和课程、修改学生信息以及展示所有信息等功能。
正文
0. 系统设计思路
本系统主要利用了两种线性表的实现方式:双链表(带头结点)用于存储课程信息,循环单链表(带头结点)则用来管理学生及其选课情况。这两种数据结构的选择是出于对效率与灵活性的综合考虑。
- 课程信息:每门课程包含课程名称、编号、学分、教师姓名、上课地点等基本信息及额外信息。
- 学生信息:每个学生的信息包括姓名、学号、性别、专业、选课数量及所选课程编号等。
总体设计思路见下:
1. 数据结构定义
首先,我们需要定义用于存储课程和学生信息的数据结构。
课程信息结构 (Course)
typedef struct DNode {
char courseNumber[10]; // 课程编号
char courseName[100]; // 课程名称
char credit[5]; // 学分
char lecturer[100]; // 主讲教师
char location[100]; // 上课地点
char additionalInfo[100]; // 其他信息
struct DNode* prior; // 指向前驱结点
struct DNode* next; // 指向后继结点
} Course;
学生信息结构 (Students)
typedef struct LNode {
char studentsName[100]; // 学生姓名
char studentsNumber[15]; // 学生编号
char studentssex; // 性别
char major[100]; // 专业
char numberOfCourses; // 选课数量
char courseNumber[10][10]; // 课程编号
struct LNode* next; // 指向下一个学生结点的指针
} Students;
2. 文件读写操作
为了管理数据,我们需要从文件中读取信息并能够将修改后的信息保存回文件。下面给出学生表和课程表的示例文件:
程序首先需要对文件进行读操作,对于courses.txt和students.txt的设计思路都是通过检测机制来存储相应内容。即:
在courses.txt中,可以发现,奇数行对应的内容是课程名称、课程编号、学分、教师姓名、上课地点;偶数行对应的内容是课程的其他信息。
在students.txt中,可以发现如果一行的有效长度超过10,那么这一行的信息必然是姓名、学号、性别、专业,其中这一行的不同内容之间用逗号分隔;如果一行有效长度小于8,那么这一行的信息必然是选课数量;如果一行的有效长度等于8,那么这一行的信息必然是选课编号。
可以基于以上发现建立检测机制从而实现信息的存储。
代码见下:
读取课程信息 (ReadCourse)
void ReadCourse(Course*& L)
{
ifstream file; // 创建文件流对象
file.open("C:\Users\墨痕\Desktop\数据结构实验\实验1\源程序\实验1.3\courses.txt", std::ios::in); // 打开文件
char s[200]; // 用于存储读取的一行数据
int n = 0; // 计数器,用于确定当前行是否包含课程信息
bool isFirstNode = true, isxunhuan = true; // 标记是否是第一个节点,以及是否需要创建头结点
// 循环读取文件中的每一行
while (file.getline(s, sizeof(s)))
{
if (n % 2 == 0) // 偶数行包含课程的基本信息
{
Course* p, * r = L;
if (isFirstNode != false) // 如果是第一次读取
{
L = (Course*)malloc(sizeof(Course)); // 创建头结点
L->prior = L->next = NULL; // 初始化头结点的前后指针
r = L; // r始终指向终端结点, 开始时指向头结点
isFirstNode = false;
isxunhuan = false;
}
while (r->next != NULL) // 找到链表的最后一个结点
r = r->next;
p = (Course*)malloc(sizeof(Course)); // 创建新结点
int j0 = 0, j1 = 0, j2 = 0, j3 = 0, j4 = 0;
for (int i = 0, h = 0; s[i] != '\0'; i++) // 处理一行中的信息
{
if (s[i] == ',') // 分割符
{
h++; // 记录当前字段的位置
}
else
{
switch (h)
{
case 0: p->courseName[j0++] = s[i]; continue; // 课程名称
case 1: p->courseNumber[j1++] = s[i]; continue; // 课程编号
case 2: p->credit[j2++] = s[i]; continue; // 学分
case 3: p->lecturer[j3++] = s[i]; continue; // 主讲教师
case 4: p->location[j4++] = s[i]; continue; // 上课地点
}
}
}
p->courseName[j0] = '\0'; // 终止字符串
p->courseNumber[j1] = '\0'; // 终止字符串
p->credit[j2] = '\0'; // 终止字符串
p->lecturer[j3] = '\0'; // 终止字符串
p->location[j4] = '\0'; // 终止字符串
r->next = p; // 将新结点插入到链表中
p->prior = r; // 设置新结点的前驱指针
r = p; // 更新 r 指向新结点
r->next = NULL; // 设置新结点为链表的尾结点
}
else if (n % 2 == 1) // 奇数行包含额外信息
{
Course* q = L; // 从头结点开始查找尾结点
while (q->next != NULL) // 查找尾结点
q = q->next;
int i = 0;
for (; s[i] != '\0'; i++) // 复制额外信息到尾结点
q->additionalInfo[i] = s[i];
q->additionalInfo[i] = '\0'; // 终止字符串
}
n++; // 更新计数器
}
if (isxunhuan == true) // 如果没有读取任何信息,创建空的头结点
{
L = (Course*)malloc(sizeof(Course)); // 创建头结点
L->prior = L->next = NULL; // 初始化头结点的前后指针
}
file.close(); // 关闭文件
}
读取学生信息 (ReadStudents)
void ReadStudents(Students*& L)
{
ifstream file;
file.open("C:\Users\墨痕\Desktop\数据结构实验\实验1\源程序\实验1.3\students.txt", std::ios::in);
char s[200];
bool isFirstNode = true, isxunhuan = true; // 标记是否是第一个节点
while (file.getline(s, sizeof(s)))
{
if (strlen(s) > 8) // 处理学生基本信息行
{
Students* p, * r = L;
if (isFirstNode != false)
{
L = (Students*)malloc(sizeof(Students)); // 创建头结点
L->next = L;
r = L; // r始终指向终端结点, 开始时指向头结点
isFirstNode = false;
isxunhuan = false;
}
while (r->next != L) // 找到链表的最后一个结点
r = r->next;
p = (Students*)malloc(sizeof(Students)); // 创建新结点
int j0 = 0, j1 = 0, j3 = 0;
for (int i = 0, h = 0; s[i] != '\0'; i++) // 处理一行中的信息
{
if (s[i] == ',') // 分割符
{
h++; // 记录当前字段的位置
}
else
{
switch (h)
{
case 0: p->studentsName[j0++] = s[i]; break; // 学生姓名
case 1: p->studentsNumber[j1++] = s[i]; break; // 学生编号
case 2: p->studentssex = s[i]; break; // 性别
case 3: p->major[j3++] = s[i]; break; // 专业
}
}
}
p->studentsName[j0] = '\0'; // 终止字符串
p->studentsNumber[j1] = '\0'; // 终止字符串
p->major[j3] = '\0'; // 终止字符串
r->next = p; // 将新结点插入到链表中
r = p; // 更新 r 指向新结点
r->next = L; // 设置新结点为链表的尾结点
}
else if (strlen(s) == 1) // 处理选课数量行
{
Students* q = L; // 从头结点开始查找尾结点
while (q->next != L) // 查找尾结点
q = q->next;
int i = 0;
for (; s[i] != '\0'; i++)
q->numberOfCourses = s[i]; // 设置选课数量
}
else if (strlen(s) == 8) // 处理课程编号行
{
Students* f = L; // 从头结点开始查找尾结点
while (f->next != L) // 查找尾结点
f = f->next;
int i = 0, j = 0;
for (; j < (f->numberOfCourses - '0'); ) // 找到下一个可用的课程编号位置
{
if (strlen(f->courseNumber[j]) != 8)
break;
j++;
}
for (; s[i] != '\0'; i++) // 复制课程编号到尾结点
f->courseNumber[j][i] = s[i];
f->courseNumber[j][i] = '\0'; // 终止字符串
}
}
if (isxunhuan == true) // 如果没有读取任何信息,创建空的头结点
{
L = (Students*)malloc(sizeof(Students)); // 创建头结点
L->next = L; // 设置头结点的 next 指针指向自身
}
file.close(); // 关闭文件
}
最后需要对文件进行写操作,这时的设计思路便是遍历链表、依次写入。即:
代码见下:
写入课程信息 (WriteCouse)
void WriteCouse(Course* L) // 将课程信息写入文件
{
ofstream file; // 创建ofstream对象以操作文件
file.open("C:\Users\墨痕\Desktop\数据结构实验\实验1\源程序\实验1.3\new_courses.txt",std::ios::out); // 打开指定路径的文件以输出模式
if (L == NULL) // 如果链表为空,直接返回
return;
Course* p = L->next; // 初始化指针p指向链表头结点的下一个节点(即第一个有效节点)
while (p != NULL) // 遍历链表直到到达末尾
{
file << p->courseName << ',' << p->courseNumber << ',' << p->credit << ','<< p->lecturer << ',' << p->location << ',' << endl; // 写入课程基本信息
file << p->additionalInfo << endl; // 写入额外信息并换行
p = p->next; // 移动指针到下一个节点
}
file.close(); // 关闭文件
}
写入学生信息 (WriteStudents)
void WriteStudents(Students* L)
{
ofstream file; // 创建文件流对象
file.open("C:\Users\墨痕\Desktop\数据结构实验\实验1\源程序\实验1.3\new_students.txt", std::ios::out); // 打开文件
if (L->next == L || L == NULL) // 如果链表为空或只有一个头结点,则返回
return;
Students* p = L->next; // 初始化指针 p 指向第一个学生节点
while (p != L) // 遍历链表直到 p 指回头结点
{
file << p->studentsName << "," << p->studentsNumber << "," << p->studentssex << "," << p->major << endl; // 写入学生基本信息
file << p->numberOfCourses << endl; // 写入选课数量
for (int j = 0; j < (p->numberOfCourses - '0'); j++) // 根据选课数量写入课程编号
{
file << p->courseNumber[j] << endl; // 写入第 j 门课程的编号
}
p = p->next; // 移动指针到下一个学生节点
}
file.close(); // 关闭文件
}
3. 功能实现
因为这里建立的相应链表分别是双链表和循环单链表,基于此实现的基本功能的设计思路与博客基于双链表实现线性表 List 的典型操作和基于循环单链表实现线性表 List 的典型操作一致,故这里不再赘述。
但是代码并不完全一致,所以下面放出代码:
显示所有课程信息 (Show)
void Show(Course* L) // 输出
{
cout << "========================================" << endl;
cout << "All Courses Information" << endl; // 输出标题
cout << "========================================" << endl;
cout << "----------------------------------------" << endl;
if (L == NULL) // 如果链表为空,则直接返回
return;
Course* p = L->next; // 初始化指针 p 指向第一个课程节点
int n = 0; // 初始化计数器 n
while (p != NULL) // 遍历链表
{
cout << "Course Name:" << p->courseName << endl; // 输出课程名称
cout << "Course ID | Credits:" << p->courseNumber << "|" << p->credit << endl; // 输出课程ID和学分
cout << "Instructor Name:" << p->lecturer << endl; // 输出主讲教师
cout << "Address:" << p->location << endl; // 输出上课地点
cout << "Description:" << p->additionalInfo << endl; // 输出额外信息
cout << "----------------------------------------" << endl; // 分隔线
p = p->next; // 移动指针到下一个课程节点
}
cout << "========================================" << endl << endl << endl; // 结束输出
}
查找特定课程 (Search)
void Search(Course* L) // 查找
{
if (L == NULL) // 如果链表为空
{
cout << "Information is empty, search failed." << endl << endl << endl; // 提示信息为空
return;
}
int i = 1; // 初始化计数器 i
Course* p = L->next; // 初始化指针 p 指向第一个课程节点
char cNumber[100]; // 用于存储输入的课程编号
cout << "Please enter course ID:"; // 提示用户输入课程编号
cin >> cNumber; // 读取用户输入的课程编号
while (p != NULL && strcmp(p->courseNumber, cNumber) != 0) // 寻找匹配的课程编号
{
i++; // 增加计数器 i
p = p->next; // 移动指针到下一个课程节点
}
if (p == NULL) // 如果未找到匹配的课程
{
cout << "Course (ID=" << cNumber << ") is not found in the courses list." << endl << endl << endl; // 提示未找到课程
}
else // 如果找到了匹配的课程
{
cout << "Course Name:" << p->courseName << endl; // 输出课程名称
cout << "Course ID | Credits:" << p->courseNumber << "|" << p->credit << endl; // 输出课程ID和学分
cout << "Instructor Name:" << p->lecturer << endl; // 输出主讲教师
cout << "Address:" << p->location << endl; // 输出上课地点
cout << "Description:" << p->additionalInfo << endl << endl << endl; // 输出额外信息
}
}
添加课程 (Add)
void Add(Course*& L) // 尾插法建双链表
{
Course* s, * r;
r = L; // r始终指向终端结点, 开始时指向头结点
char cNumber[10], cName[100], lec[100], loc[100], cre[5], additional[100];
cout << "Please enter course ID:"; // 提示用户输入课程ID
cin >> cNumber; // 读取用户输入的课程ID
if (strlen(cNumber) != 8) // 检查课程ID长度是否为8位
{
cout << "Number input error, course number should be eight digits." << endl; // 提示输入错误
return;
}
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 忽略到下一个换行符
while (r->next != NULL) // 查找链表尾部
{
if (strcmp(r->courseNumber, cNumber) == 0) // 如果课程已存在
{
cout << "Course (ID=" << cNumber << ") already exists in the courses list." << endl << endl << endl; // 提示课程已存在
return;
}
r = r->next; // 移动指针到下一个课程节点
}
s = (Course*)malloc(sizeof(Course)); // 创建新结点
cout << "Please input course name:"; // 提示输入课程名称
cin.getline(cName, 100); // 读取课程名称
cout << "Please input course credits:"; // 提示输入学分
cin >> cre; // 读入学分
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 忽略到下一个换行符
cout << "Please input course instructor:"; // 提示输入主讲教师
cin.getline(lec, 100); // 读取主讲教师
cout << "Please input course address:"; // 提示输入上课地点
cin >> loc; // 读取上课地点
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 忽略到下一个换行符
cout << "Please input course description:"; // 提示输入课程描述
cin.getline(additional, 100); // 读取课程描述
for (int j = 0; j < 100; j++)
{
s->courseName[j] = cName[j]; // 课程名称
if (j < 10)
s->courseNumber[j] = cNumber[j]; // 课程编号
s->lecturer[j] = lec[j]; // 主讲教师
s->location[j] = loc[j]; // 上课地点
if (j < 5)
s->credit[j] = cre[j]; // 学分
s->additionalInfo[j] = additional[j]; // 课程描述
}
r->next = s; // 将新结点插入到链表中
s->prior = r; // 设置新结点的前驱指针
r = s; // 更新 r 指向新结点
r->next = NULL; // 设置新结点为链表的尾结点
cout << "Successfully added the Course (ID=" << cNumber << ") to the courses list." << endl << endl << endl; // 提示成功添加课程
}
删除课程 (Remove)
void Remove(Students* L, Course* L1) // 删除第i个元素
{
cout << "Please enter course ID:"; // 提示用户输入课程ID
char cNumber[10];
cin >> cNumber; // 读取用户输入的课程ID
Course* p = L1, * q = NULL; // p指向头结点, j设置为0
while (p != NULL && strcmp(p->courseNumber, cNumber) != 0) // 查找第i-1个结点p
{
q = p; // 更新q指向p
p = p->next; // 移动p到下一个结点
}
if (p == NULL) // 未找到第i-1个结点
{
cout << "Course (ID=" << cNumber << ") is not found in the courses list." << endl << endl << endl; // 提示未找到课程
return;
}
else // 找到第i-1个结点p
{
Students* s = L->next; // 初始化课程链表指针
while (s != L)
{
for (int a = 0; a < (s->numberOfCourses - '0'); a++) // 检查学生是否选择了该课程
{
if (strcmp(s->courseNumber[a], cNumber) == 0)
{
if (a == (s->numberOfCourses - '1')) // 如果是最后一个课程
{
for (int j = 0; j < 10; j++)
s->courseNumber[a][j] = '\0'; // 清空课程ID
}
else // 如果不是最后一个课程
{
for (; a < (s->numberOfCourses - '1'); a++) // 移动前面的课程ID向前覆盖被删除的课程ID
for (int j = 0; j < 10; j++)
s->courseNumber[a][j] = s->courseNumber[a + 1][j];
for (int j = 0; j < 10; j++)
s->courseNumber[a][j] = '\0'; // 清空最后的位置
}
s->numberOfCourses = s->numberOfCourses - 1; // 减少已选课程数量
}
}
s = s->next;
}
q->next = p->next; // 从双链表中删除结点p
if (p->next != NULL) // 若p结点存在后继结点, 修改其前驱指针
p->next->prior = q;
free(p); // 释放p结点
cout << "Successfully removed the Course (ID=" << cNumber << ") from the courses list." << endl << endl << endl; // 提示成功删除课程
}
}
修改课程信息 (Update)
void Update(Course* L)
{
if (L == NULL)
{
cout << "Information is empty, modification failed." << endl << endl << endl; // 如果链表为空,提示无法修改
return;
}
char cNumber[10], cName[100], lec[100], loc[100], cre[5], additional[100]; // 其他信息;
cout << "Please enter course ID:"; // 提示用户输入课程ID
cin >> cNumber; // 读取用户输入的课程ID
Course* p = L->next; // p指向第一个元素节点
while (p != NULL && strcmp(p->courseNumber, cNumber) != 0) // 找到第i个元素的前驱节点
{
p = p->next; // 移动p到下一个结点
}
if (p == NULL) // 未找到第i个元素
{
cout << "Course (ID=" << cNumber << ") is not found in the courses list." << endl << endl << endl; // 提示未找到课程
}
else
{
cout << "Before updating, the course information:" << endl; // 显示更新前的课程信息
cout << "----------------------------------------" << endl;
cout << "Course Name:" << p->courseName << endl;
cout << "Course ID | Credits:" << p->courseNumber << "|" << p->credit << endl;
cout << "Instructor Name:" << p->lecturer << endl;
cout << "Address:" << p->location << endl;
cout << "Description:" << p->additionalInfo << endl;
cout << "----------------------------------------" << endl;
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 忽略到下一个换行符
cout << "Please input course name:"; // 提示输入课程名称
cin.getline(cName, 100); // 读取课程名称
cout << "Please input course credits:"; // 提示输入学分
cin >> cre; // 读入学分
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 忽略到下一个换行符
cout << "Please input course instructor:"; // 提示输入主讲教师
cin.getline(lec, 100); // 读取主讲教师
cout << "Please input course address:"; // 提示输入上课地点
cin >> loc; // 读取上课地点
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 忽略到下一个换行符
cout << "Please input course description:"; // 提示输入课程描述
cin.getline(additional, 100); // 读取课程描述
for (int j = 0; j < 100; j++)
{
p->courseName[j] = cName[j]; // 课程名称
p->lecturer[j] = lec[j]; // 主讲教师
p->location[j] = loc[j]; // 上课地点
if (j < 5)
p->credit[j] = cre[j]; // 学分
p->additionalInfo[j] = additional[j]; // 课程描述
}
cout << "Successfully updated the Course (ID=" << cNumber << ") to the courses list." << endl << endl << endl; // 提示成功更新课程
}
}
销毁课程表 (DestroyList)
void DestroyList(Course*& L) // 销毁线性表
{
if (L == NULL)
return; // 如果链表为空,则直接返回
Course* pre = L, * p = pre->next;
while (p != NULL)
{
free(pre); // 释放前一个结点的内存
pre = p; // pre、p同步后移一个结点
p = pre->next; // 移动p到下一个结点
}
free(p); // 释放最后一个结点的内存
}
显示所有学生信息 (Display)
void Display(Students* L, Course* L1) // 输出
{
cout << "========================================" << endl;
cout << "All Students Information" << endl;
cout << "========================================" << endl;
cout << "========================================" << endl;
cout << "----------------------------------------" << endl << endl;
if (L == NULL)
{
cout << "fail." << endl; // 如果链表为空,则直接返回
return;
}
Students* p = L->next;
int n = 0;
while (p != L) // 遍历学生链表
{
cout << "Student Name:" << p->studentsName << endl; // 输出学生姓名
cout << "Student ID (Gender):" << p->studentsNumber << "(" << p->studentssex << ")" << endl; // 输出学生ID和性别
cout << "Major:" << p->major << endl; // 输出学生专业
cout << "Selected Courses (" << p->numberOfCourses << ") :" << endl; // 输出选课数量
for (int j = 0; j < (p->numberOfCourses - '0'); j++) // 遍历学生选课
{
Course* s = L1; // 初始化指针 s 指向课程链表的第一个结点
while (strcmp(s->courseNumber, p->courseNumber[j]) != 0) // 查找匹配的课程
s = s->next;
cout << " " << p->courseNumber[j] << ":" << s->courseName << endl; // 输出课程编号和课程名称
}
cout << "----------------------------------------" << endl; // 分隔线
p = p->next; // 移动指针到下一个学生结点
}
cout << "========================================" << endl; // 结束输出
}
查找特定学生 (Retrieve)
void Retrieve(Students* L) // 查找
{
if (L == NULL)
{
cout << "Information is empty, search failed." << endl; // 如果链表为空,则直接返回
return;
}
int i = 1;
Students* p = L->next;
char cNumber[100];
cout << "Please enter student ID:"; // 提示用户输入学生ID
cin >> cNumber; // 读取用户输入的学生ID
while (p != L && strcmp(p->studentsNumber, cNumber) != 0) // 查找匹配的学生ID
{
i++; // i对应结点p的序号
p = p->next;
}
if (p == L) // 没有找到返回0
{
cout << "Student (ID=" << cNumber << ") is not found in the students list." << endl; // 提示未找到学生
}
else // 找到了返回其序号
{
cout << p->studentsName << "," << p->studentsNumber << "," << p->studentssex << "," << p->major << endl; // 输出学生信息
cout << p->numberOfCourses << endl; // 输出选课数量
for (int j = 0; j < (p->numberOfCourses - '0'); j++) // 输出所选课程编号
{
cout << p->courseNumber[j] << endl;
}
}
}
插入学生 (Insert)
void Insert(Students*& L) // 尾插法建双链表
{
Students* s, * r;
r = L->next; // r始终指向终端结点, 开始时指向头结点
char cNumber[15], cName[100], maj[100], cN[10][10];
char se, num;
cout << "Please enter student ID:"; // 提示用户输入学生ID
cin >> cNumber; // 读取用户输入的学生ID
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 忽略到下一个换行符
if (strlen(cNumber) != 10) // 检查学生ID长度是否为10位
{
cout << "Student ID input error, it should be ten digits." << endl; // 提示输入错误
return;
}
while (r->next != L) // 查找链表尾部
{
if (strcmp(r->studentsNumber, cNumber) == 0) // 如果学生已存在
{
cout << "Student (ID=" << cNumber << ") already exists in the students list." << endl; // 提示学生已存在
return;
}
r = r->next; // 移动指针到下一个学生结点
}
s = (Students*)malloc(sizeof(Students)); // 创建新结点
cout << "Please enter student name:"; // 提示输入学生姓名
cin.getline(cName, 100); // 读取学生姓名
cout << "Please enter student gender(m/f):"; // 提示输入学生性别
cin >> se; // 读取学生性别
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 忽略到下一个换行符
cout << "Please enter student major:"; // 提示输入学生专业
cin.getline(maj, 100); // 读取学生专业
cout << "Please enter the number of selected courses:"; // 提示输入选课数量
cin >> num; // 读取选课数量
if (num != '0') // 如果选课数量不为0
{
cout << "Please enter course ID:" << endl; // 提示输入课程ID
for (int i = 0; i < (num - '0'); i++) // 读取课程ID
cin >> cN[i];
}
for (int j = 0; j < 100; j++)
{
s->studentsName[j] = cName[j]; // 学生姓名
if (j < 15)
s->studentsNumber[j] = cNumber[j]; // 学生编号
s->major[j] = maj[j]; // 专业
if (j < (num - '0'))
{
int i = 0;
for (; cN[j][i] != '\0'; i++) // 复制课程编号
s->courseNumber[j][i] = cN[j][i];
s->courseNumber[j][i] = '\0'; // 终止字符串
}
}
s->numberOfCourses = num; // 设置选课数量
s->studentssex = se; // 设置性别
r->next = s; // 将结点s插入结点r之后
r = s;
r->next = L; // 尾结点next域置为NULL
cout << "Successfully inserted the Student (ID=" << cNumber << ") into the students list." << endl; // 提示成功插入学生
}
删除学生 (Delete)
void Delete(Students*& L) // 删除第i个元素
{
cout << "Please enter student ID:"; // 提示用户输入学生ID
char cNumber[15];
cin >> cNumber; // 读取用户输入的学生ID
Students* p = L->next, * q = NULL; // p指向头结点的下一个结点, j设置为0
while (p != L && strcmp(p->studentsNumber, cNumber) != 0) // 查找第i-1个结点p
{
q = p;
p = p->next;
}
if (p == L) // 未找到第i-1个结点
{
cout << "Student (ID=" << cNumber << ") is not found in the students list." << endl << endl << endl; // 提示未找到学生
return;
}
else // 找到第i-1个结点p
{
q->next = p->next; // 从单向循环链表中删除结点p
free(p); // 释放p结点
cout << "Successfully deleted the Student (ID=" << cNumber << ") from the students list." << endl << endl << endl; // 提示成功删除学生
}
}
修改学生信息 (Modify)
void Modify(Students* L, Course* L1)
{
if (L->next == L) // 如果链表为空
{
cout << "Information is empty, modification failed." << endl; // 输出信息为空,修改失败
return;
}
char cNumber[15], maj[100], courNumber[10]; // 定义数组用于存储学生学号、专业和课程编号
cout << "Please enter student ID:"; // 提示用户输入学生ID
cin >> cNumber; // 读取用户输入的学生ID
Students* p = L->next; // 指针 p 指向链表的第一个元素
while (p != L && strcmp(p->studentsNumber, cNumber) != 0) // 循环直到找到具有指定学号的学生或者到达链表尾部
{
p = p->next; // 移动指针到下一个元素
}
if (p == L) // 如果指针回到了头结点,说明未找到该学生
{
cout << "Student (ID=" << cNumber << ") is not found in the students list." << endl; // 输出学生未找到的信息
}
else // 如果找到了该学生
{
Course* f = L1->next; // 初始化课程链表指针
cout << "Modify student (ID=" << cNumber << ") [major|enroll|drop]:"; // 提示用户输入修改指令
string command; // 定义字符串变量用于存储用户的输入
cin >> command; // 读取用户输入的指令
if (command == "major") // 如果用户选择修改专业
{
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 清除输入缓冲区直到遇到换行符
cout << "Please enter student major:"; // 提示用户输入专业
cin.getline(maj, 100); // 读取用户输入的专业
for (int j = 0; j < 100; j++) // 更新学生的专业
{
p->major[j] = maj[j]; // 将新专业赋值给学生结构体中的专业字段
}
cout << endl << "Modified the major of Student (ID=" << cNumber << ")." << endl; // 输出专业修改成功的信息
cout << "----------------------------------------" << endl << endl;
cout << "Student Name:" << p->studentsName << endl; // 输出学生姓名
cout << "Student ID (Gender):" << p->studentsNumber << "(" << p->studentssex << ")" << endl; // 输出学生ID和性别
cout << "Major:" << p->major << endl; // 输出学生专业
cout << "Selected Courses (" << p->numberOfCourses << ") :" << endl; // 输出已选课程数量
for (int j = 0; j < (p->numberOfCourses - '0'); j++) // 遍历并输出所有已选课程
{
Course* s = L1; // 初始化课程链表指针
while (strcmp(s->courseNumber, p->courseNumber[j]) != 0) // 查找对应的课程
s = s->next; // 移动课程链表指针
cout << " " << p->courseNumber[j] << ":" << s->courseName << endl; // 输出课程编号和课程名
}
return;
}
else if (command == "enroll") // 如果用户选择增加课程
{
if (L1 == NULL) // 如果课程链表为空
{
cout << "No courses, add failed." << endl; // 输出无法添加课程
return;
}
cout << "Please enter ID of course to be enrolled in:"; // 提示用户输入要添加的课程ID
cin >> courNumber; // 读取用户输入的课程ID
while (f != NULL && strcmp(f->courseNumber, courNumber) != 0) // 循环查找课程
{
f = f->next; // 移动课程链表指针
}
if (f == NULL) // 如果找不到课程
{
cout << "Number does not exist, course addition failed." << endl; // 输出添加失败
return;
}
else // 如果找到了课程
{
for (int a = 0; a < (p->numberOfCourses - '0'); a++) // 检查学生是否已经选择了该课程
{
if (strcmp(p->courseNumber[a], courNumber) == 0)
{
cout << endl << "Course (ID=" << courNumber << ") is already in the enrolled courses list." << endl << endl << endl; // 输出课程已存在
return;
}
}
p->numberOfCourses = p->numberOfCourses + 1; // 增加已选课程数量
int m = 0;
for (; courNumber[m] != '\0'; m++) // 复制课程ID到学生结构体
p->courseNumber[p->numberOfCourses - '1'][m] = courNumber[m];
p->courseNumber[p->numberOfCourses - '1'][m] = '\0'; // 终止字符串
cout << endl << "Modified the major of Student (ID=" << cNumber << ")." << endl; // 输出修改成功
cout << "----------------------------------------" << endl << endl;
cout << "Student Name:" << p->studentsName << endl; // 输出学生姓名
cout << "Student ID (Gender):" << p->studentsNumber << "(" << p->studentssex << ")" << endl; // 输出学生ID和性别
cout << "Major:" << p->major << endl; // 输出学生专业
cout << "Selected Courses (" << p->numberOfCourses << ") :" << endl; // 输出已选课程数量
for (int j = 0; j < (p->numberOfCourses - '0'); j++) // 遍历并输出所有已选课程
{
Course* s = L1; // 初始化课程链表指针
while (strcmp(s->courseNumber, p->courseNumber[j]) != 0) // 查找对应的课程
s = s->next; // 移动课程链表指针
cout << " " << p->courseNumber[j] << ":" << s->courseName << endl; // 输出课程编号和课程名
}
return;
}
}
else if (command == "drop") // 如果用户选择删除课程
{
Course* h = L1->next; // 初始化课程链表指针
cout << "Please enter ID of course to be dropped:"; // 提示用户输入要删除的课程ID
cin >> courNumber; // 读取用户输入的课程ID
int b = 0;
while (h != NULL && strcmp(h->courseNumber, courNumber) != 0) // 查找课程
{
b++; // 记录序号
h = h->next; // 移动课程链表指针
}
if (h == NULL) // 如果找不到课程
{
cout << "Course (ID=" << courNumber << ") is not found in the courses list." << endl; // 输出课程未找到
return;
}
else // 如果找到了课程
{
for (int a = 0; a < (p->numberOfCourses - '0'); a++) // 检查学生是否选择了该课程
{
if (strcmp(p->courseNumber[a], courNumber) == 0)
{
if (a == (p->numberOfCourses - '1')) // 如果是最后一个课程
{
for (int j = 0; j < 10; j++)
p->courseNumber[a][j] = '\0'; // 清空课程ID
}
else // 如果不是最后一个课程
{
for (; a < (p->numberOfCourses - '1'); a++) // 移动前面的课程ID向前覆盖被删除的课程ID
for (int j = 0; j < 10; j++)
p->courseNumber[a][j] = p->courseNumber[a + 1][j];
for (int j = 0; j < 10; j++)
p->courseNumber[a][j] = '\0'; // 清空最后的位置
}
p->numberOfCourses = p->numberOfCourses - 1; // 减少已选课程数量
cout << endl << "Modified the major of Student (ID=" << cNumber << ")." << endl; // 输出修改成功
cout << "----------------------------------------" << endl << endl;
cout << "Student Name:" << p->studentsName << endl; // 输出学生姓名
cout << "Student ID (Gender):" << p->studentsNumber << "(" << p->studentssex << ")" << endl; // 输出学生ID和性别
cout << "Major:" << p->major << endl; // 输出学生专业
cout << "Selected Courses (" << p->numberOfCourses << ") :" << endl; // 输出已选课程数量
for (int j = 0; j < (p->numberOfCourses - '0'); j++) // 遍历并输出所有已选课程
{
Course* s = L1; // 初始化课程链表指针
while (strcmp(s->courseNumber, p->courseNumber[j]) != 0) // 查找对应的课程
s = s->next; // 移动课程链表指针
cout << " " << p->courseNumber[j] << ":" << s->courseName << endl; // 输出课程编号和课程名
}
return;
}
}
cout << "The course is not included in the courses already taken by the student, and the withdrawal failed." << endl; // 输出课程不在已选课程中
return;
}
}
else // 如果用户输入了无效的命令
cout << "Not a valid command - returning to main menu." << endl; // 输出无效命令提示
}
}
销毁学生表 (DestroyStudents)
void DestroyStudents(Students*& L) // 销毁线性表
{
Students* pre = L, * p = pre->next; // pre 指向头结点,p 指向头结点的下一个结点
while (p != L) // 当 p 不等于头结点时(即链表中还有结点)
{
free(pre); // 释放 pre 所指向的结点
pre = p; // pre 和 p 同步后移一个结点
p = pre->next; // p 指向下一个结点
}
free(pre); // 此时 p=L,pre 指向尾结点,释放它
}
4. 测试系统
主函数
下面我们编写主函数调用上述实现的函数:
void main()
{
Course* courses_list; // 课程链表指针
Students* students_list; // 学生链表指针
ReadCourse(courses_list); // 读取课程信息到链表
ReadStudents(students_list); // 读取学生信息到链表
while (true) // 主循环
{
cout << "-------------------------------------------------------------------------------------------" << endl;
cout << "The choice to maintain COURSEs information [add|remove|update|search|show]" << endl;
cout << "The choice to maintain STUDENTs information [insert|delete|modify|retrieve|display]" << endl;
cout << "The choice quit means to finish the program.[quit]" << endl << endl << endl;
string command; // 用户命令
cout << "Please enter choice:"; // 提示用户输入命令
cin >> command; // 读取用户命令
if (command == "add") Add(courses_list); // 添加课程
else if (command == "remove") Remove(courses_list); // 删除课程
else if (command == "update") Update(courses_list); // 更新课程
else if (command == "search") Search(courses_list); // 搜索课程
else if (command == "show") Show(courses_list); // 显示所有课程
else if (command == "insert") Insert(students_list); // 插入学生
else if (command == "delete") Delete(students_list); // 删除学生
else if (command == "modify") Modify(students_list, courses_list); // 修改学生信息
else if (command == "retrieve") Retrieve(students_list); // 检索学生信息
else if (command == "display") Display(students_list, courses_list); // 显示所有学生信息
else if (command == "quit") break; // 退出程序
else cout << "Not a valid command - please try again." << endl << endl << endl << endl; // 输入无效命令
}
errno_t err_1 = 0, err_2 = 0; // 文件错误码
FILE* fp_1 = NULL, * fp_2 = NULL; // 文件指针
err_1 = fopen_s(&fp_1, "C:\Users\墨痕\Desktop\数据结构实验\实验1\源程序\实验1.3\new_courses.txt", "w"); // 打开课程文件
err_2 = fopen_s(&fp_2, "C:\Users\墨痕\Desktop\数据结构实验\实验1\源程序\实验1.3\new_students.txt", "w"); // 打开学生文件
if (err_1 == 0) // 如果打开课程文件成功
{
fclose(fp_1); // 关闭文件,由于模式为"w",所以文件内容会被清空
WriteCouse(courses_list); // 写入课程信息
}
if (err_2 == 0) // 如果打开学生文件成功
{
fclose(fp_2); // 关闭文件,由于模式为"w",所以文件内容会被清空
WriteStudents(students_list); // 写入学生信息
}
DestroyList(courses_list); // 销毁课程链表
DestroyStudents(students_list); // 销毁学生链表
cout << endl << endl << "-- Program Terminating --" << endl; // 程序终止
}
测试结果
运行实例1演示函数调用功能、show、search以及quit功能,当输入的命令不为提示调用的功能(即功能不存在时),系统会提示“Not a valid command - please try again.”。当使用search搜索ID不存在的课程编号时,系统会提示“Course (ID=输入的查询编号) is not found in the courses list.”。输入已存在的命令(即系统提示输入的命令),会自动调用相关函数,如使用show命令将会展示所有的课程信息,使用search命令在输入正确编号后会输出对应的课程信息。当输入quit时程序运行结束,信息被写入两个新文件“new_courses.txt”、“new_students.txt”。
程序具体测试结果见下图:
程序运行之后,两个新文件“new_courses.txt”、“new_students.txt”内容分别如下:
new_courses.txt:
new_students.txt:
运行实例2主要演示add和update功能(其他功能前文已测试,这里不再叙述,但是在测试程序时也用到了)。当添加已有课程编号时会提示“Course (ID=输入的添加编号) already exists in the courses list.”,反之则会提示继续添加课程的其他信息,待信息输入完毕之后会提示“Successfully added the Course (ID=输入的添加编号) to the courses list.”。当使用update功能时,如果输入不存在的课程编号会提示“Course (ID=输入的课程编号) is not found in the courses list.”,如果编号存在则会先输出此时对应的课程信息,然后提示输入将要修改的信息,输入完毕后会提示“Successfully updated the Course (ID=输入的课程编号) to the courses list.”。
程序具体测试结果见下图:
程序运行之后,两个新文件“new_courses.txt”、“new_students.txt”内容分别如下:
new_courses.txt:
new_students.txt:
运行实例3主要演示了display、retrieve、insert以及modify功能(其他功能前文已测试,这里不再叙述,但是在测试程序时也用到了)。当输入display指令,程序会打印所有的学生信息。retrieve指令通过学生学号来查找学生信息,如果没找到则会提示相应信息。insert指令用来插入学生信息,如果将要插入的学生学号与已有学号重复也会提示相应信息。
在modify下有三个子指令,分别是major、enroll、drop。major用来修改学生专业,enroll用来为学生添加一门课程,drop用来为学生退掉一门课程。
程序具体测试结果见下图:
程序运行之后,两个新文件“new_courses.txt”、“new_students.txt”内容分别如下:
new_courses.txt:
new_students.txt:
运行实例4主要演示remove功能(其他功能前文已测试,这里不再叙述,但是在测试程序时也用到了)。当删除课程信息之后,如果学生课表中有该课程也会同时删除。
具体测试功能见下图:
程序运行之后,两个新文件“new_courses.txt”、“new_students.txt”内容分别如下:
new_courses.txt:
new_students.txt:
运行实例5主要演示delete功能(其他功能前文已测试,这里不再叙述,但是在测试程序时也用到了)。即删除学号对应的学生信息。
具体测试功能见下图:
程序运行之后,两个新文件“new_courses.txt”、“new_students.txt”内容分别如下:
new_courses.txt:
new_students.txt: