C语言-数据结构-单链表程序-增删改查
今天学习内容是关于C语言数据结构的单链表程序,其中涉及了结构体、malloc动态分配地址的操作。
首先我们根据程序需要,写出头文件
#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宏定义两个数,用于后续的使用,这里无需深刻理解,等在程序中遇到的时候再回来看
#define NULL 0
#define LEN sizeof(struct student)int n;//用于计数,当创建一个新的结点时加一接下来是定义一个结构体student,该结构体包含了num学号,score分数,struct student*next下一个结构体的存储地址。
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*的使用原因:函数要返回什么类型的数据,就用什么类型来定义函数的返回类型
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的地址赋值给结构体指针headhead = 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)
那么,当你了解如何创建链表,并且能够不看示例,自己写出创建链表的函数之后,删除结点和添加结点的方法就无师自通了,接下来我就不再继续详细讲解删除结点和添加结点的操作了。
文章的最后有全部代码
接下来是删除结点
struct student* del(struct student* head, int num)
{struct student* p1, * p2 = NULL; // 初始化 p2 为 NULLif (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;
}接下来是插入结点(按学号顺序插入)
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; // 新节点指向p1n++; // 节点数加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;
}接下来是完整代码示范
#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;elsep2->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 为 NULLif (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; // 新节点指向p1n++; // 节点数加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;
}