单链表代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct LinkNode // 节点结构体
{
void* data; // 节点数据域
struct LinkNode* next; // 节点指针域
};
struct LList // 链表结构体
{
struct LinkNode pHead; // 链表头节点
int m_size; // 链表长度
};
typedef void* LinkList; // 给void *类型起个别名
// 初始化链表:返回值是LinkList类型,用户无法用LinkList类型修改链表结构体数据
LinkList init_LinkList()
{
// 给链表分配空间
struct LList* myList = (struct LList*)malloc(sizeof(struct LList));
if (myList == NULL)
{
return NULL;
}
myList->pHead.data = NULL; // 链表里的头节点数据域不需要维护,所以随便设置
myList->pHead.next = NULL; // 链表里的头节点的指针域初始化的时候为空
myList->m_size = 0; // 链表的长度初始化为0
return myList;
}
// 给链表插入节点:用户并不知道链表struct LList的成员数据,所以使用void *类型链表承接
void insert_LinkList(LinkList list, int pos, void *data1)
{
if (list == NULL)
{
return;
}
if (data1 == NULL)
{
return;
}
// list-> // list是void *属性类型,它的成员什么都没有
struct LList* myList = list; // 底层代码程序猿用struct LList*类型链表承接用户链表 list
// 判断用户插入的位置
if (pos<0 || pos>myList->m_size)
{
// 用户插入无效位置的话,就设定为尾部插入
pos = myList->m_size;
}
// 1.找到用户插入位置的前驱节点
struct LinkNode* pCurrenent = &myList->pHead; // 临时节点指针指向链表头节点
for(int i = 0; i < pos; i++)
{
pCurrenent = pCurrenent->next;
}
// 比如要插入链表的 第二个节点 位置,经过for循环之后,此时临时节点指针指向了 第一个节点 位置
// 2. 建立新节点
struct LinkNode* newNode = malloc(sizeof(struct LinkNode)); // 创建新节点
newNode->data = data1; // 类似于int *p = &a; 指针p保存着a的地址
// 若是int *p = NULL,就得给p申请堆区内存才能让
// p指向的内存地址保存数据, *p = 11;
newNode->next = NULL;
// 3. 建立节点之间的关系
newNode->next = pCurrenent->next;
pCurrenent->next = newNode;
// 4.更新链表长度
myList->m_size++;
}
// 遍历链表 形参1: void*list,用户不知道具体链表数据 形参二:回调函数,由用户决定打印什么类型数据
void foreach_LinkList(LinkList list, void (*userPrint)(void *))
{
if (list == NULL)
{
return;
}
// 底层的程序猿知道具体链表数据
struct LList* myList = list;
// 创建一个临时节点指针指向链表头节点后继,即指向链表真实数据节点
struct LinkNode* pCurrent = myList->pHead.next;
for (int i = 0; i < myList->m_size; i++)
{
userPrint(pCurrent->data);
pCurrent = pCurrent->next;
}
}
// 按位置删除链表节点
void removeByPos_LinkList(LinkList list, int pos)
{
if (list == NULL)
{
return;
}
// 底层程序猿知道用真实链表的成员属性
struct LList* myList = (struct LList*)list;
if (pos<0 || pos>myList->m_size-1)
{
return;
}
// 1.先找到被删除位置的前驱节点
struct LinkNode* pCurrent = &myList->pHead;
for (int i = 0; i < pos; i++)
{
pCurrent = pCurrent->next;
}
// 跳出for循环的时候pCurrent指向被删除节点的前驱节点
// 2.删除节点
struct LinkNode* delNode = pCurrent->next; // 记录被删节点
pCurrent->next = delNode->next; // 更改节点指针指向
free(delNode);
delNode = NULL;
//3. 更新链表长度
myList->m_size--;
}
// 按值删除节点
void removeByValue_LinkList(LinkList list, void *data1, int (*userCompare1)(void *, void *))
{
if (list == NULL)
{
return;
}
if (data1 == NULL)
{
return;
}
struct LList* myList = (struct LList*)list;
//struct LinkNode* pPrev = &myList->pHead; // 被删节点的前驱
//struct LinkNode* pCurrent = myList->pHead.next; // 被删节点,千万不要写成 &myList->pHead.next
//struct LinkNode* pCurrent = &myList->pHead; // 注意,链表头节点的数据域为空,访问他程序会崩溃
struct LinkNode* pCurrent = myList->pHead.next;
for (int i = 0; i < myList->m_size; i++)
{
//if(pCurrent->data == data) // 单纯的地址比较没有意义
if(userCompare1(data1, pCurrent->data)) // 比较地址指向的内存空间的值才有意义
{
// 方法一
removeByPos_LinkList(myList, i);
//方法二
//pPrev->next = pCurrent->next;
//free(pCurrent);
//pCurrent = NULL;
//myList->m_size--;
break;
}
//pPrev = pCurrent;
pCurrent = pCurrent->next;
}
}
void clear_LinkList(LinkList list)
{
if (list == NULL)
{
return;
}
struct LList* myList = (struct LList*)list;
struct LinkNode* pCurrent = myList->pHead.next;
for (int i = 0; i < myList->m_size; i++)
{
// 1.先记录删除节点的下一个节点
struct LinkNode* pNext = pCurrent->next;
// 2. 删除节点
free(pCurrent);
pCurrent = pNext;
}
myList->pHead.next = NULL;
myList->m_size = 0;
}
void destroy_Linklist(LinkList list)
{
if (list == NULL)
{
return;
}
// 1.先清空节点数据
clear_LinkList(list);
// 2.再销毁链表
//struct LList* myList = (struct LList*)list;
free(list); // free函数只关心链表的首地址,不关心链表是什么样的数据类型
// 所以不需要把list转为真实链表
list = NULL;
}
// 以下代码是用户层程序
struct Person
{
char name[64];
int age;
};
void personPrint(void* data)
{
struct Person* data1 = data;
printf("人物姓名:%8s, 年龄:%d\n", data1->name, data1->age);
}
int personCompare(void* data1, void* data2)
{
if (data1 == NULL)
{
printf("data1为空,非法访问\n");
return -1;
}
if (data2 == NULL)
{
printf("data2为空,非法访问\n");
return -1;
}
struct Person* p1 = data1;
struct Person* p2 = data2;
return (strcmp(p1->name, p2->name) == 0) && (p1->age == p2->age);
}
void test01()
{
// 链表初始化,用户智能调用底层程序猿提供的接口
LinkList* list1 = init_LinkList();
// list1-> // 用户没办法访问真实链表的属性
struct Person p1 = { "百里守约", 6 };
struct Person p2 = { "东皇太一", 22 };
struct Person p3 = { "炸弹猫", 18 };
struct Person p4 = { "孙悟空", 500 };
struct Person p5 = { "凯皇", 33 };
struct Person p6 = { "裁判", 40 };
insert_LinkList(list1, 0, &p1);
insert_LinkList(list1, 0, &p2);
insert_LinkList(list1, 1, &p3);
insert_LinkList(list1, -1, &p4);
insert_LinkList(list1, 1, &p5);
insert_LinkList(list1, -1, &p6);
foreach_LinkList(list1, personPrint);
printf("-----------------\n");
printf("删除第三个节点之后:\n");
removeByPos_LinkList(list1, 3);
foreach_LinkList(list1, personPrint);
printf("-----------------\n");
struct Person tmp = { "炸弹猫", 18 };
printf("删除\"炸弹猫\"之后:\n");
removeByValue_LinkList(list1, &tmp, personCompare);
foreach_LinkList(list1, personPrint);
//销毁链表
destroy_Linklist(list1); // 指针值传递无法改变值
list1 = NULL; // 所以还得list1 = NULL;
//foreach_LinkList(list1, personPrint);
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
``
# 2.运行结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/57ac7b776023435aafa440d0b7a30734.png)