单向链表学习

单链表代码

#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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值