预处理
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define STUDENT_NUMBER_MAX 50
#define STUDENT_NAME_SEX_MAX 20
#define STUDENT_SEX_MAX 5
#define SUBJECT_NUMBER 2
菜单
//目录
void menu()
{
printf("\n\n\n");
printf("\t\t-------------------------------------------------\n");
printf("\t\t|| ---------------- ||\n");
printf("\t\t||**************学生信息管理系统(链表版)*******||\n");
printf("\t\t|| ---------------- ||\n");
printf("\t\t|| ||\n");
printf("\t\t||~~~~~~~~~~~~~~~1.录入学生信息~~~~~~~~~~~~~~~~||\n");
printf("\t\t||~~~~~~~~~~~~~~~2.删除学生信息~~~~~~~~~~~~~~~~||\n");
printf("\t\t||~~~~~~~~~~~~~~~3.修改学生信息~~~~~~~~~~~~~~~~||\n");
printf("\t\t||~~~~~~~~~~~~~~~4.查询学生信息~~~~~~~~~~~~~~~~||\n");
printf("\t\t||~~~~~~~~~~~~~~~5.显示学生信息~~~~~~~~~~~~~~~~||\n");
printf("\t\t||~~~~~~~~~~~~~~~7.依平均分排序~~~~~~~~~~~~~~~~||\n");
printf("\t\t||~~~~~~~~~~~~~~~8.清屏~~~~~~~~~~~~~~~~~~~~~~~~||\n");
printf("\t\t||~~~~~~~~~~~~~~~9.退出~~~~~~~~~~~~~~~~~~~~~~~~||\n");
printf("\t\t|| ||\n");
printf("\t\t||*********************************************||\n");
}
构造学生结构体,构造链表
typedef struct student
{
int num;
char name[STUDENT_NAME_SEX_MAX];
char clas[STUDENT_NAME_SEX_MAX];
char sex[STUDENT_SEX_MAX];
float Eng_score;
float C_score;
float total;
float aver;
}student;
//构造链表,数据域为学生信息
typedef struct Node
{
student stu;
struct Node* next;
}Node;
功能实现
Node* initList();//初始化链表
void addStudent(Node* list);//录入
void showStudent(Node* list);//显示
void sortByAverage(Node* list);//排序(平均分)
void searchStudent(Node* list);//查找
void deleteStudent(Node* list);//删除
void modifyStudent(Node* list);//修改
主函数
int main()
{
int input;
Node* list = initList();//头节点指针
do
{
menu();
printf("请输入:");
scanf("%d", &input);
getchar();
switch (input)
{
case 9:
printf("已退出!\n");//退出
exit(1);
case 1:
addStudent(list); //添加学生信息
break;
case 2:
deleteStudent(list); //删除学生信息
break;
case 3:
modifyStudent(list); //修改学生信息
break;
case 4:
searchStudent(list); //查找学生信息
break;
case 5:
showStudent(list); //显示学生信息
break;
case 7:
sortByAverage(list); //依平均分排序
break;
case 8:
system("clear"); //清屏
break;
default:
printf("输入指令有误!\n");
break;
}
} while (input);
return 0;
}
初始化
//初始化(带哑节点)
Node* initList() {
Node* p = (Node*)malloc(sizeof(Node)); // 分配内存空间
if (p == NULL) {
// 处理内存分配失败的情况
perror("Failed to allocate memory for the dummy node");
exit(EXIT_FAILURE);
}
memset(p, 0, sizeof(Node)); // 清零整个节点
p->next = NULL; // 确保哑节点的next指针设置为NULL
return p; // 返回哑节点指针
}
添加(头插法)
//头插法添加
void addStudent(Node* list)
{
Node* node = (Node*)malloc(sizeof(Node));
if (!node) {
printf("内存分配失败!\n");
return;
}
memset(&node->stu, 0, sizeof(student));//将节点的学生信息清零
printf("请输入学号:");
scanf("%d", &node->stu.num);
printf("请输入姓名:");
scanf("%s", node->stu.name);
printf("请输入班级:");
scanf("%s", node->stu.clas);
printf("请输入性别:");
scanf("%s", node->stu.sex);
printf("请输入英语成绩:");
scanf("%f", &node->stu.Eng_score);
printf("请输入c语言成绩:");
scanf("%f", &node->stu.C_score);
// 插入节点到链表头部
node->next = list->next; // 新节点的next指向原链表的第一个节点
list->next = node; // 链表头的next指向新节点
printf("插入成功!\n");
}
删除
// 删除
void deleteStudent(Node* list)
{
// 判断头节点后面是否有节点
if (list->next == NULL)
{
printf("没有信息可删除!\n");
return;
}
// 删除需要两个指针,一个指向前一个,一个指向当前
Node* pre = list;
Node* current = list->next;
int num;
printf("请输入要删除的学号:");
scanf("%d", &num);
getchar();
//在单向链表中,遍历的方向是从链表的头部开始,沿着 next 指针向链表的尾部移动。
// 这是因为单向链表只提供了向前的链接,没有向后的链接。
while (current)//当前节点不为空
{
if (current->stu.num == num)
{
/* NULL(头) < -pre < -current < -next_node < -...(尾)
v v
+ ---- - ++---- - +
| stu | | stu |
| ... | -----> | ... |
+---- - ++---- - + */
pre->next = current->next;
free(current);//参数是一个指针
current = NULL;//避免野指针
printf("已删除!\n");
return;
}
pre = current;
current = current->next;
}
printf("学生信息不存在!\n");
}
修改
// 修改
void modifyStudent(Node* list)
{
// 判断头节点后面是否有节点
if (list->next == NULL)
{
printf("没有信息可修改!\n");
return;
}
int num;
printf("请输入要修改的学号:");
scanf("%d", &num);
getchar();
list = list->next;
while (list)
{
if (list->stu.num == num)
{
printf("请输入姓名:");
scanf("%s", list->stu.name);
printf("请输入班级:");
scanf("%d", &list->stu.clas);
printf("请输入性别:");
scanf("%d", &list->stu.sex);
printf("请输入英语成绩:");
scanf("%f", &list->stu.Eng_score);
printf("请输入c语言成绩:");
scanf("%f", &list->stu.C_score);
printf("修改成功!\n");
return;
}
list = list->next;
}
printf("学生信息不存在!\n");
}
查找
// 查找学生信息
void searchStudent(Node* list)
{
// 判断头节点后面是否有节点
if (list->next == NULL)
{
printf("没有信息可查询!\n");
return;
}
int num;
printf("请输入要要查找的学号:");
scanf("%d", &num);
getchar();
list = list->next;
while (list)
{
if (num == list->stu.num)
{
printf("\n");
printf("学号: %d, 姓名: %s, 班级: %s, 性别: %s, 英语: %.2f, C语言: %.2f, 平均分: %.2f\n",
list->stu.num, list->stu.name, list->stu.clas, list->stu.sex,
list->stu.Eng_score, list->stu.C_score, list->stu.aver);
printf("\n");
return;
}
list = list->next;
}
printf("学生信息不存在!\n");
}
显示
// 展示学生信息
void showStudent(Node* list) {
if (list == NULL) return; // 确保list不是NULL
// 跳过可能的哑节点(如果存在)
Node* current = list->next;
if (current == NULL) return; // 确保链表不为空
printf("\n");
while (current) {
printf("学号: %d, 姓名: %s, 班级: %s, 性别: %s, 英语: %.2f, C语言: %.2f, 平均分: %.2f\n",
current->stu.num, current->stu.name, current->stu.clas, current->stu.sex,
current->stu.Eng_score, current->stu.C_score, (current->stu.Eng_score + current->stu.C_score) / 2.00);
current = current->next;
}
printf("\n");
}
排序(冒泡降序)
// 冒泡排序,根据学生的平均分降序排序链表
void sortByAverage(Node* head) {
if (head == NULL || head->next == NULL) {
printf("没有成绩可排序!\n");
return;
}
//遍历链表计算每个节点的平均分
Node* current = head->next;
// 跳过哑节点
while (current!=NULL ) {
current->stu.aver = (current->stu.Eng_score + current->stu.C_score) / 2.0;
printf("%.2f\n",current->stu.aver);
current = current->next;
}
bool swapped;
do {
swapped = false; // 每次开始新的一轮排序前,设置swapped为false
Node* current = head->next; // 哑节点后的首个数据节点
// 开始冒泡排序
while (current != NULL && current->next != NULL) {
Node* nextNode = current->next;
// 比较当前节点和下一个节点的平均分
if (current->stu.aver < nextNode->stu.aver) {
// 发生交换,更新swapped标记
swapped = true;
// 交换两个节点的Student信息
student tempStu = current->stu;
current->stu = nextNode->stu;
nextNode->stu = tempStu;
// 交换完成,将current指针前移一位
current = nextNode;
}
// 移动到下一个节点
current = current->next;
}
} while (swapped); // 如果进行了交换,继续进行下一轮排序
// 冒泡排序结束后,swapped标志位会停止循环,此时current指针应该在最后一个节点上
// 我们需要从head的下一个节点开始打印,即从头节点的下一个节点开始遍历整个链表
current = head->next; // 从头节点的下一个节点开始
// 打印排序后的链表
printf("排序后的链表:\n");
while (current != NULL) { // 确保current不是NULL
printf("学号: %d, 姓名: %s, 班级: %s, 性别: %s, 英语: %.2f, C语言: %.2f, 平均分: %.2f\n",
current->stu.num, current->stu.name, current->stu.clas, current->stu.sex,
current->stu.Eng_score, current->stu.C_score, current->stu.aver); // 使用已计算的平均分
current = current->next; // 移动到下一个节点
}
printf("\n");
}
&
在C语言中,如果使用 scanf
来读取整数到结构体成员时,确实需要使用 &
操作符来获取成员的地址。这是因为 node->stu.num
是一个整数,而不是一个指针。&
操作符用于获取变量的内存地址,以便 scanf
可以正确地将输入的整数值存储到该地址处。
以下是正确的 scanf
使用示例:
scanf("%d", &node->stu.clas); // 正确,因为clas是整数
scanf("%d", &node->stu.sex); // 正确,因为sex是整数
scanf("%d", &node->stu.Eng_score); // 正确,因为Eng_score是整数
scanf("%d", &node->stu.C_score); // 正确,因为C_score是整数
对于字符串类型的成员,比如 node->stu.num
如果它实际上是一个字符串(尽管通常学号是一个整数),使用 %s
格式符时,不需要 &
操作符,因为数组名本身就是地址。但是,如果 node->stu.num
是一个字符数组用来存储学号(通常是一个字符串),并且你想要读取一个字符串,应该这样写:
scanf("%s", node->stu.num); // 正确,如果num是一个字符数组