今天学习内容是关于C语言数据结构的单链表程序,其中涉及了结构体、malloc动态分配地址的操作。
首先我们根据程序需要,写出头文件
cpp
#define _CRT_SECURE_NO_WARNINGS 1//使scanf函数生效
#include <stdio.h>//标准输入输出函数
#include <stdlib.h>//malloc动态地址分配函数
//原本是使用malloc.h来作为malloc()函数的头文件,但是后续发现malloc.h是非标准扩展,主要在某些Unix/Linux系统中存在,不是C标准的一部分,因此采用C标准库头文件stdlib.h,其中包含了malloc,calloc,realloc,free等内存管理函数。
为什么推荐使用 stdlib.h:
-
标准兼容性 - 符合C标准,在所有编译器上都能工作
-
可移植性 - 代码可以在不同平台间移植
-
未来兼容 - 不会被新编译器淘汰
接下来我们回归正题,在完成头文件的选择后,需要使用#define宏定义两个数,用于后续的使用,这里无需深刻理解,等在程序中遇到的时候再回来看
cpp
#define NULL 0
#define LEN sizeof(struct student)
int n;//用于计数,当创建一个新的结点时加一
接下来是定义一个结构体student,该结构体包含了num学号,score分数,struct student*next下一个结构体的存储地址。
cpp
struct student
{
int num;
float score;
struct student* next;
};
接下来是创建链表的函数creat(),在本函数中创建了head、p1、p2三个结构体指针,其中head表示头结点,而p1作为缓冲指针,接收键盘输入的新数据,然后通过p2连接到链表中。(第一个结点连接的时候head和p2两个指针所指向的结构体是同一个,因此p2->next等同于head->next,并且在后续,p2->next能够重复使用,用于连接下一个新的结点)
模糊点:struct student*的使用原因:函数要返回什么类型的数据,就用什么类型来定义函数的返回类型
cpp
struct student* creat()
{
struct student* head, * p1, * p2;
n = 0;
head = NULL;
p1 = p2 = (struct student*)malloc(LEN);//注意此处的p1和p2结构体指针指向同一个结构体
scanf_s("%d%f", &p1->num, &p1->score);
while (p1->num != 0)
{
n++;//标识为第n个结点
if (n == 1)//当链表初次创建时,n=1,将结构体指针p1的地址赋值给结构体指针head
head = p1;
else//当链表已经存在一个结点,p2的指向始终是新增结点p1的前一位
p2->next = p1;//因为前两行head=p1;并且p1和p2的地址相同,因此此处的p2->next等同于head->next;
p2 = p1;//此处将p2指针更新成新增的结构体地址,而p1在下一行中要被重新分配
p1 = (struct student*)malloc(LEN);
scanf_s("%d%f", &p1->num, &p1->score);//若%p1->num为0,当回车按下时,循环会结束,将p2的next设置为空,刚输入未接入链表的p1会被释放内存空间
}
p2->next = NULL;
free(p1);
return head;//返回头结点的地址
}
开辟一个新的地址存储结构体变量,并且p1和p2指向相同
p1的用法是代表最新结点,p2的用法是代表上一个结点,因此p1和p2的地址在while中总有相同的时候,只不过他们一相同,p1就会指向新的地址
通俗理解就是,p2是p1的专属摄影师,p1走到哪,p2就会给p1拍一张照片,在p1走到下一个地点时,p2会先在刚拍好的照片中写下下一个地点(p2->next)
那么,当你了解如何创建链表,并且能够不看示例,自己写出创建链表的函数之后,删除结点和添加结点的方法就无师自通了,接下来我就不再继续详细讲解删除结点和添加结点的操作了。
文章的最后有全部代码
接下来是删除结点
cpp
struct student* del(struct student* head, int num)
{
struct student* p1, * p2 = NULL; // 初始化 p2 为 NULL
if (head == NULL)
{
printf("链表为空!\n");
return head;
}
p1 = head;
// 遍历链表查找要删除的节点
while (p1 != NULL && p1->num != num)
{
p2 = p1; // p2 始终指向 p1 的前一个节点
p1 = p1->next;
}
if (p1 == NULL)//这个就是上一程序中while循环里的p1!=NULL的情况,意思是链表已经查询到最后一个NULL结点了,还没找到需要查找的学号,就会报告错误并结束程序
{
printf("未找到学号为 %d 的节点\n", num);
return head;
}
// 找到要删除的节点
if (p2 == NULL) // 删除的是头节点
{
head = p1->next;
}
else // 删除的是中间或尾节点
{
p2->next = p1->next;
}
free(p1);
n--;
printf("删除成功!\n");
return head;
}
接下来是插入结点(按学号顺序插入)
cpp
struct student* insert(struct student* head, struct student* stu)
{
struct student* p0, * p1, * p2;
p0 = stu; // 要插入的新节点
p1 = head; // 从头部开始查找
p2 = NULL;
if (head == NULL) // 空链表情况
{
head = p0;
p0->next = NULL;
n++;
return head;
}
// 查找插入位置(按学号升序)
while ((p1->num < p0->num) && (p1->next != NULL))
{
p2 = p1; // p2记录前一个节点
p1 = p1->next; // p1指向下一个节点
}
if (p0->num <= p1->num) // 找到插入位置
{
if (head == p1) // 插入到链表头部
head = p0;
else // 插入到p2和p1之间
p2->next = p0;
p0->next = p1; // 新节点指向p1
n++; // 节点数加1
}
else // 插入到链表尾部
{
p1->next = p0;
p0->next = NULL;
n++;
}
return head;
}
接下来是输出函数
cpp
// 输出链表
void print(struct student* head)
{
struct student* p;
printf("\n现在共有 %d 条记录:\n", n);
p = head;
if (head != NULL)
{
do
{
printf("学号:%d,成绩:%.1f\n", p->num, p->score);
p = p->next;
} while (p != NULL);//在creat()函数结尾有令最后一个结点的next为空p2->next = NULL;
}
else
{
printf("链表为空!\n");
}
}
接下来是主函数
cpp
int main()
{
struct student* p = NULL;
struct student a, * p0 = &a;
int xh;
printf("请输入学号和成绩,直到学号输入0为止:\n");
p = creat();//return head;
print(p);
printf("请输入待删除的学号:\n");
scanf_s("%d", &xh);
p = del(p, xh);//return head;
print(p);
printf("请输入待插入的学号和成绩:\n");
scanf_s("%d%f", &p0->num, &p0->score);
p = insert(p, p0);//return head;
print(p);
return 0;
}
接下来是完整代码示范
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#define NULL 0
#define LEN sizeof(struct student)
int n;
struct student
{
int num;
float score;
struct student* next;
};
// 创建链表
struct student* creat()
{
struct student* head, * p1, * p2;
n = 0;
head = NULL;
p1 = p2 = (struct student*)malloc(LEN);
scanf_s("%d%f", &p1->num, &p1->score);
while (p1->num != 0)
{
n++;
if (n == 1)
head = p1;
else
p2->next = p1;
p2 = p1;
p1 = (struct student*)malloc(LEN);
scanf_s("%d%f", &p1->num, &p1->score);
}
p2->next = NULL;
free(p1);
return head;
}
// 删除节点 - 修复版本
struct student* del(struct student* head, int num)
{
struct student* p1, * p2 = NULL; // 初始化 p2 为 NULL
if (head == NULL)
{
printf("链表为空!\n");
return head;
}
p1 = head;
// 遍历链表查找要删除的节点
while (p1 != NULL && p1->num != num)
{
p2 = p1; // p2 始终指向 p1 的前一个节点
p1 = p1->next;
}
if (p1 == NULL)
{
printf("未找到学号为 %d 的节点\n", num);
return head;
}
// 找到要删除的节点
if (p2 == NULL) // 删除的是头节点
{
head = p1->next;
}
else // 删除的是中间或尾节点
{
p2->next = p1->next;
}
free(p1);
n--;
printf("删除成功!\n");
return head;
}
// 插入节点(按学号顺序插入)
struct student* insert(struct student* head, struct student* stu)
{
struct student* p0, * p1, * p2;
p0 = stu; // 要插入的新节点
p1 = head; // 从头部开始查找
p2 = NULL;
if (head == NULL) // 空链表情况
{
head = p0;
p0->next = NULL;
n++;
return head;
}
// 查找插入位置(按学号升序)
while ((p1->num < p0->num) && (p1->next != NULL))
{
p2 = p1; // p2记录前一个节点
p1 = p1->next; // p1指向下一个节点
}
if (p0->num <= p1->num) // 找到插入位置
{
if (head == p1) // 插入到链表头部
head = p0;
else // 插入到p2和p1之间
p2->next = p0;
p0->next = p1; // 新节点指向p1
n++; // 节点数加1
}
else // 插入到链表尾部
{
p1->next = p0;
p0->next = NULL;
n++;
}
return head;
}
// 输出链表
void print(struct student* head)
{
struct student* p;
printf("\n现在共有 %d 条记录:\n", n);
p = head;
if (head != NULL)
{
do
{
printf("学号:%d,成绩:%.1f\n", p->num, p->score);
p = p->next;
} while (p != NULL);//在creat()函数结尾有令最后一个结点的next为空p2->next = NULL;
}
else
{
printf("链表为空!\n");
}
}
// 主函数
int main()
{
struct student* p = NULL;
struct student a, * p0 = &a;
int xh;
printf("请输入学号和成绩,直到学号输入0为止:\n");
p = creat();//return head;
print(p);
printf("请输入待删除的学号:\n");
scanf_s("%d", &xh);
p = del(p, xh);//return head;
print(p);
printf("请输入待插入的学号和成绩:\n");
scanf_s("%d%f", &p0->num, &p0->score);
p = insert(p, p0);//return head;
print(p);
return 0;
}