可读写的学生选课信息管理系统

322 阅读26分钟

前言

在数据结构的学习过程中,我们经常需要将理论知识应用到实际问题中去。本文正是基于这一目的,通过使用线性表来构建一个简单的学生选课信息管理系统。这个系统可以实现读取课程和学生信息、添加或删除学生和课程、修改学生信息以及展示所有信息等功能。

正文

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: