数据结构 - 链表作业1: 学生管理系统 (链表)

懒猫老师-C语言-链表作业1:学生管理系统(代码模板) - 知乎
懒猫老师链表作业实现–学生信息管理系统 - 知乎


1 模板框架

//============================================================================
// Name : LinkBlank.cpp
// Author :
// Version :
// Copyright : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

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

#define NO_LENGTH 20
#define NAME_LENGTH 11

/* 定义学生结构体的数据结构 */
typedef struct Student{
char studentNo[NO_LENGTH];
char studentName[NAME_LENGTH];
}st;

/* 定义每条记录或节点的数据结构 */
typedef struct node
{
struct Student data; //数据域
struct node *next; //指针域
}Node,*Link; //Node为node类型的别名,Link为node类型的指针别名

//定义提示菜单
void myMenu(){
printf(" * * * * * * * * * 菜 单 * * * * * * * * * *\n");
printf(" 1 增加学生记录 2 删除学生记录 \n");
printf(" 3 查找学生记录 4 修改学生记录 \n");
printf(" 5 统计学生人数 6 显示学生记录 \n");
printf(" 7 退出系统 \n");
printf(" * * * * * * * * * * * * * * * * * * * * * * * *\n");
}

void inputStudent(Link l){
printf("请输入学生学号:");
scanf("%s",l->data.studentNo);
printf("请输入学生的姓名:");
scanf("%s",l->data.studentName);

//每个新创建的节点的next域都初始化为NULL
l->next = NULL;
}

void inputStudentNo(char s[],char no[]){
printf("请输入要%s的学生学号:",s);
scanf("%s",no);
}

void displayNode(Link head){
// 填写代码,根据传入的链表head头指针,扫描链表显示所有节点的信息
}

/* 增加学生记录 */
bool addNode(Link head){
Link p,q; //p,q两个节点一前一后
Link node; //node指针指向新创建的节点
node=(Link)malloc(sizeof(Node));
inputStudent(node);

q = head;
p = head->next; //q指向head后面的第一个有效节点
if(head->next==NULL)
//链表为空时
head->next = node;
else {
//循环访问链表中的所有节点
while(p != NULL){
if (node->data.studentNo < p->data.studentNo){
//如果node节点的学号比p节点的学号小,则插在p的前面,完成插入后,提前退出子程序
q->next = node;
node->next = p;
return true;
}
else{
//如果node节点的学号比p节点的学号大,继续向后移动指针(依然保持pq一前一后)
q = p;
p = p->next;

}
}
//如果没能提前退出循环,则说明之前没有插入,那么当前node节点的学号是最大值,此时插在链表的最后面
q->next = node;

}
return true;
}

bool deleteNode(Link head){
// 按照给定的学号删除学生记录,如果删除成功返回true,如果没找到学号返回false

//输入要处理的学号
char no[NO_LENGTH];
inputStudentNo("修改",no);

return false;
}

bool queryNode(Link head){
// 按照给定的学号查询学生记录,如果删除成功返回true,如果没找到学号返回false

//输入要处理的学号
char no[NO_LENGTH];
inputStudentNo("修改",no);

return false;
}

bool modifyNode(Link head){
// 按照给定的学号找到学生记录节点,如果修改成功返回true,如果没找到学号返回false

//输入要处理的学号
char no[NO_LENGTH];
inputStudentNo("修改",no);

return false;
}

int countNode(Link head){
//统计学生人数,扫描链表统计节点个数,返回节点数
Link p;
int count = 0;
p = head->next;

//填充代码
return false;
}

void clearLink(Link head){
Link q,p;
//遍历链表,用free语句删除链表中用malloc建立起的所有的节点
}

int main() {
int select;
int count;
Link head; // 定义链表

//建立head头结点,在这个程序中head指向头结点,头结点data部分没有内容,其后续节点才有真正的数据
head = (Link)malloc(sizeof(Node));
head->next = NULL;

while(1)
{
myMenu();
printf("\n请输入你的选择(0-7):"); //显示提示信息
scanf("%d",&select);
switch(select)
{
case 1:
//增加学生记录
if(addNode(head))
printf("成功插入一个学生记录。\n\n");
break;
case 2:
//删除学生记录
if(deleteNode(head))
printf("成功删除一个学生记录。\n\n");
else
printf("没有找到要删除的学生节点。\n\n");
break;
case 3:
//查询学生记录
if(queryNode(head))
printf("成功找到学生记录。\n\n");
else
printf("没有找到要查询的学生节点。\n\n");
break;
case 4:
//修改学生记录
if(modifyNode(head))
printf("成功修改一个学生记录。\n\n");
else
printf("没有找到要修改的学生节点。\n\n");
break;
case 5:
//统计学生人数
count = countNode(head);
printf("学生人数为:%d\n\n",count);
break;
case 6:
//显示学生记录
displayNode(head);
break;
case 7:
//退出前清除链表中的所有结点
clearLink(head);
return 0;
default:
printf("输入不正确,应该输入0-7之间的数。\n\n");
break;
}
}
return 0;
}

2 代码

这也太长了……感觉可以优化……

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

#define NO_LENGTH 20
#define NAME_LENGTH 11

// 定义学生结构体的数据结构
typedef struct Student {
char studentNo[NO_LENGTH];
char studentName[NAME_LENGTH];
} Student;

// 定义每条记录或节点的数据结构
typedef struct Node {
Student data;
struct Node *next;
} Node, *Link;

void myMenu(); // 定义提示菜单
void inputStudent(Student *student);
void inputStudentNo(const char* s, char no[]);
void displayNode(Link head); // 6 显示学生记录
bool addNode(Link head); // 1 增加学生记录
bool deleteNode(Link head); // 2 删除学生记录
bool queryNode(Link head); // 3 查找学生记录
bool modifyNode(Link head); // 4 修改学生记录
int countNode(Link head); // 5 统计学生记录
void clearLink(Link head); // 清除所有节点

int main(void) {
Link head = (Link)malloc(sizeof(Node)); // 定义链表头节点
head->next = NULL; // 初始化为空链表

int select;
while (true) {
myMenu(); // 显示菜单
printf("请输入你的选择(1-7): ");
scanf("%d", &select); // 读取用户选择

switch (select) {
case 1: // 增加
if (addNode(head))
printf("成功插入一个学生记录.\n\n");
break;
case 2: // 删除
if (deleteNode(head))
printf("成功删除一个学生记录.\n\n");
else
printf("没有找到要删除的学生节点.\n\n");
break;
case 3: // 查询
if (queryNode(head))
printf("成功找到学生记录.\n\n");
else
printf("没有找到要查询的学生节点.\n\n");
break;
case 4: // 修改
if (modifyNode(head))
printf("成功修改一个学生记录.\n\n");
else
printf("没有找到要修改的学生节点.\n\n");
break;
case 5: // 统计
printf("学生人数为: %d\n\n", countNode(head));
break;
case 6: // 显示
displayNode(head);
break;
case 7: // 退出系统,清除链表中的所有节点
clearLink(head);
return 0;
default:
printf("输入不正确, 应该输入1-7之间的数.\n\n");
break;
}
}

return 0;
}

void myMenu() {
printf(" * * * * * * * * * * 菜 单 * * * * * * * * * \n");
printf(" 1 增加学生记录 2 删除学生记录 \n");
printf(" 3 查找学生记录 4 修改学生记录 \n");
printf(" 5 统计学生记录 6 显示学生记录 \n");
printf(" 7 退出系统 \n");
printf("* * * * * * * * * * * * * * * * * * * * * * * \n\n");
}

// 输入学生信息
void inputStudent(Student *student) {
printf("请输入学生学号: ");
scanf("%s", student->studentNo);
printf("请输入学生的姓名: ");
scanf("%s", student->studentName);
}

// 输入学号
void inputStudentNo(const char* s, char no[]) {
printf("请输入要%s的学生学号: ", s);
scanf("%s", no);
}

// 显示学生记录
void displayNode(Link head) {
Link p = head;

if (p == NULL || p->next == NULL){
printf("暂无学生信息录入!\n");
return;
}

while (p->next != NULL) {
printf("%s %s\n", p->data.studentNo, p->data.studentName);
p = p->next;
}
}

// 增加学生记录
bool addNode(Link head) {
if (head == NULL) {
printf("链表不存在!\n");
return false;
}

Link p, q, node;
node = (Link)malloc(sizeof(Node));
inputStudent(&node->data); // 输入学生信息

q = head;
p = head->next; // q 指向 head 后面的第一个有效节点
if (head->next == NULL)
head->next = node;
else {
while (p != NULL) {
if (strcmp(node->data.studentNo, p->data.studentNo) < 0) {
// 如果 node 节点的学号比 p 节点的学号小, 则插在 p 的前面, 完成插入
q->next = node;
node->next = p;
return true;
} else {
// 如果 node 节点的学号比 q 节点的学号大, 继续向后移动指针
q = p;
p = p->next;
}
}
q->next = node; // 没有退出循环, 说明之前没有插入, 则此 node 节点的学号是最大值
}
return true;
}

// 删除学生记录
bool deleteNode(Link head) {
if (head == NULL || head->next == NULL) {
printf("暂无学生信息!\n");
return false;
}

char no[NO_LENGTH];
inputStudentNo("删除", no);

Link p, q;
q = head;
p = head->next;

while (p != NULL) {
if (strcmp(p->data.studentNo, no) == 0) {
q->next = p->next;
free(p);
return true;
} else {
q = p;
p = p->next;
}
}
return false;
}

// 查询学生记录
bool queryNode(Link head) {
if (head == NULL || head->next == NULL) {
printf("暂无学生录入!\n");
return false;
}

char no[NO_LENGTH];
inputStudentNo("查询", no);

for (Link p = head->next; p != NULL; p = p->next) {
if (strcmp(p->data.studentNo, no) == 0) {
printf("查询成功!\n");
printf("此学生学号为: %s\n", p->data.studentNo);
printf("此学生姓名为: %s\n", p->data.studentName);
return true;
}
}
printf("没有找到该学生!\n");
return false;
}

// 修改学生记录
bool modifyNode(Link head) {
if (head == NULL || head->next == NULL) {
printf("暂无学生录入!\n");
return false;
}

// 输入要处理的学号
char no[NO_LENGTH];
inputStudentNo("修改", no);

for (Link p = head->next; p != NULL; p = p->next) {
if (strcmp(p->data.studentNo, no) == 0) {
printf("您要修改的学生记录如下, 请确认:\n");
printf("学号: %s\n", p->data.studentNo);
printf("姓名: %s\n", p->data.studentName);

printf("请选择修改 (1: 学号; 2: 姓名)\n");
int x;
scanf("%d", &x);
switch (x) {
case 1:
printf("请输入要修改的学号:\n");
scanf("%s", p->data.studentNo);
break;
case 2:
printf("请输入要修改的姓名:\n");
scanf("%s", p->data.studentName);
break;
}
return true;
}
}
return false;
}

// 统计学生记录
int countNode(Link head) {
if (head == NULL || head->next == NULL) {
printf("暂无学生信息!\n");
return 0;
}

int count = 0;
for (Link p = head->next; p != NULL; p = p->next) {
count++;
}
return count;
}

// 清除所有节点
void clearLink(Link head) {
if (head == NULL || head->next == NULL) {
printf("暂无学生信息!\n");
return;
}

Link p, q;
q = head;
p = head->next;
while (p != NULL) {
q = p->next;
free(p);
p = q;
}
free(head);
printf("清除成功!\n");
}

3 遇到的问题

3.1 displayNode( ) 输出一行乱码

void displayNode(Link head){
Link p = head;

if (p == NULL || p->next == NULL){
printf("暂无学生信息录入!\n\n");
}

while (p != NULL){
printf("%s %s\n", p->data.studentNo, p->data.studentName);
p = p->next;
}
}

问题出在 while() 中的循环条件和判断条件上, 当循环到最后一个结点时, p 的 next 一定为空, 即 p->next == NULL, 然后循环继续执行一次, 会多输出一行.

改正: while (p->next != NULL), 这样循环会在打印最后一个节点之后终止, 而不会再多输出一行乱码. 修改后的代码如下所示:

while (p->next != NULL) {
...
}

3.2 bool deleteNode( ) 字符串常量问题

把文件从 c 转 c++ 时:

void inputStudentNo(char s[], char no[]){
printf("请输入要%s的学生学号", s);
scanf("%s", no);
}

出现错误:

In function 'bool deleteNode(Link)':
[警告] ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]

代码中将字符串常量赋值给 char* 类型的变量, 这在 C++ 中是不允许的, 因为字符串常量是不可变的, 而将其赋值给 char* 类型的变量可能会导致试图修改常量字符串的行为, 这是不安全的。

所以, 可以将字符数组声明为常量指针, 即使用 const char* 类型而不是 char* 类型。这样做可以避免试图修改字符串常量, 并消除警告

例如,将 inputStudentNo() 函数中的参数 s 改为 const char* 类型:

void inputStudentNo(const char* s, char no[]){
printf("请输入要%s的学生学号", s);
scanf("%s", no);
}

详细解释:

C++对于字符串常量的处理有所不同,它们是 const char* 类型而不是 char* 类型。因此,在C++中,需要将函数参数声明为 const char* 而不是 char[] 类型。

在C++中,字符串常量(如 “删除”)被视为字符数组的地址,类型为 **const char***,而不是 char*。这是因为字符串常量是不可变的,所以C++将它们声明为常量,不允许对它们进行修改。因此,在C++中,需要使用 const char* 来接收字符串常量的地址.

3.3 char* no 和 char no[]的区别

在函数参数中,char* nochar no[] 都可以用来接收字符串的地址。在这两种情况下,传递给函数的实际参数都将被视为字符串的起始地址。

然而,它们之间确实存在一些微妙的差别:

  1. 数组长度:

    • char* no 中的 no 是一个指向字符的指针,它只包含了字符串的起始地址,并没有包含任何有关字符串长度的信息。在这种情况下,函数无法直接知道传递的字符串有多长,除非在字符串中使用了特殊的结束符(如空字符 \0)。
    • char no[] 中的 no 是一个字符数组,它实际上包含了整个字符串的内容。编译器会自动为数组分配足够的空间来存储字符串,并且可以通过 sizeof 运算符获取数组的长度(不包括空字符 \0)。
  2. 参数传递:

  • 在函数调用时,传递给 char* no 的参数可以是一个字符数组的地址,也可以是指向字符串常量的指针,甚至可以是一个动态分配的字符数组。这样的参数传递方式更加灵活。
  • 而对于 **char no[]**,函数参数必须是一个数组,因此只能传递一个字符数组。

总的来说,char* no 更常用于函数参数,因为它更加灵活,可以接收多种类型的字符串参数。而 char no[] 在声明函数参数时,更适合于限定参数为一个字符数组的情况。