1.特殊链表
针对于特殊的用户数据,在该链表中,节点只有指针域,没有数据域;用户数据预留4个节点的空间给节点使用就行,节点负责连接各个用户数据,不负责维护用户数据.
2.代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 节点结构体
struct LinkNode
{
struct LinkNode* next; // 只有指针域,数据域由用户自己维护
};
// 链表结构体
struct LList
{
struct LinkNode pHead; // 链表头节点
int m_size; // 链表长度
};
typedef void* LinkList;
// 链表初始化
LinkList init_LinkList()
{
// 为链表申请堆空间
struct LList* myList = (struct LList*)malloc(sizeof(struct LList));
if (myList == NULL)
{
printf("链表申请堆空间失败!\n");
return NULL;
}
myList->m_size = 0;
myList->pHead.next = NULL;
return myList;
}
// 在链表中插入节点,用户会把数据地址传进来,所以用void *data1去接就行
void insert_LinkList(LinkList list1, int pos, void *data1)
{
if (list1 == NULL)
{
return;
}
if (data1 == NULL)
{
return;
}
struct LList* myList = (struct LList*)list1;
if (pos<0 || pos>myList->m_size)
{
// 插入位置无效就默认尾插
pos = myList->m_size;
}
// 用户的数据包括一个 struct LinkNode节点和真正的数据
// 需要把前四个字节地址取出来
struct LinkNode* userNode = data1; // 类似于int a=0x11223344; char *p = (char*)&a;
// p取出了a的第一个字节空间地址
// 找到待插入位置的前驱节点
struct LinkNode* pCurrent = &myList->pHead;
for (int i = 0; i < myList->m_size; i++)
{
pCurrent = pCurrent->next;
}
//从for循环退出之后pCurrent指向待插入位置的前驱节点
// 更改链表节点的指针指向
userNode->next = pCurrent->next;
pCurrent->next = userNode;
// 更新链表长度
myList->m_size++;
}
// 遍历链表
void foreach_LinkList(LinkList list1, void (*userPrint)(void*))
{
if (NULL == list1)
{
return;
}
// 把list1转换为真实链表
struct LList* myList = (struct LList*)list1;
// 定义一个临时节点指向链表用户数据首地址
struct LinkNode* pCurrent = myList->pHead.next;
for (int i = 0; i < myList->m_size; i++)
{
// 节点是没有数据域的,所以让用户自己打印数据
// pCurrent就是用户数据的首地址
userPrint(pCurrent);
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 destory_LinkList(LinkList list1)
{
if (NULL == list1)
{
return;
}
free(list1); // 有多少个malloc,就对应多少个free
list1 = NULL;
}
// 以下是用户代码
struct Person
{
//struct LinkNode pNode; //容易被用户知道底层代码
int *a; // 用户只需要预留4字节给底层程序猿就行,所以int a,double *a等都可以
char name[64];
int age;
};
void personPrint(void* data)
{
struct Person* data1 = data;
printf("人物姓名:%8s, 年龄:%d\n", data1->name, data1->age);
}
void test01()
{
// 链表初始化,用户只能调用底层程序猿提供的接口
LinkList* list1 = init_LinkList();
// list1-> // 用户没办法访问真实链表的属性
struct Person p1 = { NULL, "百里守约", 6 };
struct Person p2 = { NULL, "东皇太一", 22 };
struct Person p3 = { NULL, "炸弹猫", 18 };
struct Person p4 = { NULL, "孙悟空", 500 };
struct Person p5 = { NULL, "凯皇", 33 };
struct Person p6 = { NULL, "裁判", 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);
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}