Take you to play with double linked lists

Article directory


Preface

I believe that after the previous study, everyone has already understood the defects and uses of single-linked lists. Today we are learning double-linked lists. Different from before, today we increase the difficulty of implementing double-linked lists, but I believe these difficulties are no problem for everyone. Compared with the previous implementation of single linked list, our data type is fixed. Whatever is passed in the main function, our single linked list structure needs the corresponding data type. Today, in the implementation of double linked list, we will change to the main function (user) to pass whatever We can receive and implement all data types.
This chapter involves knowledge points about function pointers, callback functions, and flexible arrays. Friends who have forgotten can review them in this chapter.


How linked lists are stored in space:

1. The idea of ​​doubly linked list

Before implementing it, let's first understand the doubly linked list:
What is a doubly linked list: There are two pointers prev and next in the nodes of the doubly linked list, which point to the predecessor node and the successor node respectively.
Advantages of a double linked list: It solves the problem that when a single linked list wants to access the predecessor node of a node, it can only be traversed from the beginning. The complexity of accessing the successor node is O(1), and the complexity of accessing the predecessor node is O( n) question.
The structure of a doubly linked list:
Insert image description here
In a non-cyclic doubly linked list, the head node or the first node in the doubly linked list has no predecessor node, and the last node has no successor node. The head node does not store valid data! ! !
Insert image description here
In a circular doubly linked list, the predecessor node of the head node or the first node in the doubly linked list is the last node, and the successor node of the last node is the head node or the first node in the doubly linked list. The head node does not store valid data! ! !
I believe that we already have a general implementation idea through the structure, and what we want to implement today is the most perfect structure in the double-linked list. The double-linked list that takes the lead in looping is also the structure we commonly use in the future.

2. Implementation analysis of leading circular doubly linked list

We want to realize that any data type passed by the user (main function) can form our doubly linked list, so the type of the user's doubly linked list data field is unknown, so what type of data do we want to build? When we choose a pointer of type char, there will be a new problem. How should the user respond to a pointer of type integer or structure char passed in? In response to the above problems, we design two structures:
Insert image description here
This is the structure we designed. Since our head node does not store data and is different from other nodes, how should we point it?
Insert image description here
Our pointer points to the position. We point the pointers of the tail node and the first valid node to the position of the linked list node member of the head node. In this way, our linked list will be circular.

2. Implementation of taking the lead in circular doubly linked list 1

1. Overview of the header file for taking the lead in circular double linked list implementation

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<time.h>
#define HEADINSERTION 1 //头插选项
#define BACKINSERTION 2 //尾插选项
typedef struct ListNode//链表的节点
{
    
    
	char* data;//传入的数据
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向上一个节点的指针
}ListNode;
typedef struct ListHead //不一样的头节点
{
    
    
	int size;//用来存储传入数据的大小
	ListNode list;//用来存储头节点的数据
}ListHead;
typedef void Printf(const void* );//对用户传递的打印函数进行重命名
typedef int Cmp(const void*, const void*);//对用户传递的打印函数进行重命名
ListHead* ListCreate(int datasize);//用来创建特殊的头节点
void Destory(ListHead* pHead);// 双向链表销毁
void ListPrint(ListHead* pHead, Printf* print);// 双向链表打印
int ListInsert(ListHead* pHead, const void* pos, int Optional);//双向链表的插入
void* ListFind(ListHead* pHead, const void* key,Cmp* cmp);//双向链表查找
int Listdestroy(ListHead* pHead, const void* key, Cmp* cmp);//双向链表删除
int Listtravel(ListHead* pHead, const void* key, Cmp* cmp,void* retu);//双向链表删除,并把删除节点返回

Let’s first look at the functions in the header file and why they are designed this way. We answer them one by one below.

2. Take the lead in initializing the circular doubly linked list

ListHead* ListCreate(int datasize)//用来创建特殊的头节点
{
    
    
	ListHead* pHead = (ListHead*)malloc(sizeof(ListHead));
	if (pHead == NULL)//开辟空间失败就报错结束
	{
    
    
		perror("pHead malloc");
		exit(-1);
	}
	pHead->size = datasize;//用来接收用户要构建链表的数据区的大小
	pHead->list.data = NULL;//头节点不存储有效数据,所以置为NULL
	pHead->list.next = &pHead->list;//后继节点指向自己
	pHead->list.prev = &pHead->list;//前驱节点指向自己
	return pHead;
}

When we initialize, we need to build our special head node. Only when we know the size of the data area that the user wants to build, can we open up space for subsequent insertions.

3. Take the lead in inserting circular doubly linked list

//pos:用来接收用户所传数据,由于用户所传数据未知,所以我们用void指针进行接收
//Optional:插入选择,接收用户是头插还是尾插
//#define HEADINSERTION 1 //头插选项,在我们的头文件中定义的
//#define BACKINSERTION 2 //尾插选项,在我们的头文件中定义的
int ListInsert(ListHead* pHead, const void* pos, int Optional)//双向链表的插入
{
    
    
	assert(pHead);
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)//开辟空间失败就报错结束
	{
    
    
		perror("node malloc");//节点开辟失败
		return 1;//返回值为1代表节点开辟失败
	}
	node->data = (char*)malloc(sizeof(char) * pHead->size);//数据区的开辟,开辟的大小为用户所传的大小
	if (node->data == NULL)
	{
    
    
		free(node);//释放开辟好的节点,防止内存泄露
		perror("node->data malloc");//数据域开辟失败,进行报错
		return 2;//返回值为2代表节点开辟成功,但数据区开辟失败
	}
	memcpy(node->data, (char*)pos,pHead->size);//把数据拷贝到我们开的节点中
	if (HEADINSERTION == Optional)//判断是否为头插
	{
    
    
		node->next = (pHead->list).next;
		node->prev = &(pHead->list);//取头节点中的链表节点地址
	}
	else if (BACKINSERTION == Optional)//判断是否为尾插
	{
    
    
		node->next = &pHead->list;
		node->prev = pHead->list.prev;
	}
	else
	{
    
    
		free(node->data);//释放开辟好的数据区
		free(node);//释放开辟好的节点,防止内存泄露
		return 3;//返回值为3代表插入位置不符合要求
	}
	node->prev->next = node;
	node->next->prev = node;
	return 0;//代表此函数正常结束
}

In the project, it is illegal for us to randomly specify the position to insert, because the content of the linked list elements is unknown, and random insertion can easily destroy the structure, so we implement tail insertion and head insertion here. When we insert the head node, it points to the position of the double linked list node in the head node, not to the position starting from size! ! !
Our implementation of head insertion and tail insertion is based on user choice, and there is no need to divide it into two functions to implement it here.
In projects, we should use less printing functions in functions. Here we return by return value. Users can use the return value to determine the cause of the error. This ensures the unity of our function.
The data area passed by the user is unknown, so we need to use the library function memcpy or strcpy function to copy.
Insert image description here
Insert image description here
The idea of ​​implementing head insertion and tail insertion here is the same. You can draw a picture. We must pay attention to the fact that when our double linked list node is successfully developed, but the data area fails to be opened, the opened node must be released, otherwise it will cause a memory leak.

4. Take the lead in printing and destroying circular doubly linked lists

void Destory(ListHead* pHead)// 双向链表销毁
{
    
    
	assert(pHead);
	ListNode* pos = (&pHead->list)->next;//从头节点的下一个节点开始
	while (pos != &pHead->list)
	{
    
    
		ListNode* del = pos;//要释放的节点
		pos = pos->next;//保存下一个节点
		free(del->data);//data数据也是动态开辟的
		free(del);//释放节点
	}
}
void ListPrint(ListHead* pHead, Printf * print)// 双向链表打印,使用回调函数
{
    
    
	assert(pHead);
	ListNode* pos = pHead->list.next;
	while (pos != &pHead->list)//判断是否走到头节点
	{
    
    
		print(pos->data);//调用用户提供的打印函数
		pos = pos->next;
	}
}

Here, whether it is the head node is determined by comparing the address of the doubly linked list node and the doubly linked list node in the head node.
How do we print when we don't know the user type?
This is done through the callback function. Since we don’t know the user data type, but the user knows it, we can set the function type and let the user implement it by themselves, and then we call it through the callback function.
This renaming function of our header file works here

typedef void Printf(const void* );//对用户传递的打印函数进行重命名
//void Printf(const void* );//用户需要实现的函数
//typedef对该函数进行重命名

Here is the test function:

#define NAME_SIZE 32
typedef struct Stu
{
    
    
	int id;
	char name[NAME_SIZE];
	int math;
	int chinese;
}Stu;
void Printf_s(const void* print)//用户写的打印函数
{
    
    
	Stu* prin = (Stu*)print;//把void类型转换为用户的类型
	printf("id:%2d  name:%s  math:%2d  chinese:%2d\n",prin->id, prin->name, prin->math, prin->chinese);
}
void test1()
{
    
    
	ListHead* pHead = ListCreate(sizeof(Stu));
	Stu stu;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
    
    
		stu.id = i;
		snprintf(stu.name, NAME_SIZE, "stu%d:", i);
		stu.math = rand()%100;
		stu.chinese = rand()%100;
		ListInsert(pHead, &stu, 1);//传入1,进行头插
		//ListInsert(pHead, &stu, 2);//传入2,进行尾插
	}
	ListPrint(pHead, Printf_s);// 双向链表打印
	Destory(pHead);// 双向链表销毁
}
int main()
{
    
    
	srand((unsigned)time(NULL));
	test1();
	return 0;
}

Head insertion:
Insert image description here
Tail insertion:
Insert image description here
We randomly select values ​​for the grades, so the two results are different. The method we use to assign values ​​to the string array here is the same as the method for the single-linked list. Not much introduction here.

5. Take the lead in searching and deleting circular doubly linked lists

ListNode* find(ListHead* pHead, const void* key, Cmp* cmp)//双向链表查找
{
    
    
	assert(pHead);
	ListNode* pos = pHead->list.next;
	while (pos != &pHead->list)//判断是否走到头节点
	{
    
    
		if (cmp(key, pos->data) == 0)//调用用户提供的比较函数
		{
    
    
			break;
		}
		pos = pos->next;
	}
	return pos;//如果找到时,循环终止,返回值为找到的节点,如果找不到,则返回pos为头节点。
}
void* ListFind(ListHead* pHead, const void* key, Cmp* cmp)//双向链表查找
{
    
    
	assert(pHead);
	return (void*)find(pHead, key, cmp)->data;//返会我们的数据区。如果返回的头节点,头节点的值为NULL。不影响我们正常判断
}
int Listdestroy(ListHead* pHead, const void* key, Cmp* cmp)//双向链表删除
{
    
    
	assert(pHead);
	ListNode* del = find(pHead, key, cmp);
	if (del == &pHead->list)
	{
    
    
		return 1;//代表未找到我们要删除的节点
	}
	//删除节点
	del->prev->next = del->next;
	del->next->prev = del->prev;
	free(del->data);//我们的数据区也是动态开辟的,所以也要内存释放
	free(del);
	return 0;
}
int Listtravel(ListHead* pHead, const void* key, Cmp* cmp, void* retu)//双向链表删除,并把删除节点返回
{
    
    
	assert(pHead);
	ListNode* del = find(pHead, key, cmp);
	if (del == &pHead->list)
	{
    
    
		return 1;//代表未找到我们要删除的节点
	}
	if (del->data != NULL)
	{
    
    
		memcpy(retu, del->data, pHead->size);//通过函数参数返回
	}
	//删除节点
	del->prev->next = del->next;
	del->next->prev = del->prev;
	free(del->data);
	free(del);
	return 0;
}

To implement search and deletion, we need to find the node first, so we create a separate function to return the searched node. Because we don't know the type to be compared, we need to receive the type that the user wants to compare and the comparison function provided by the user to implement our search.
Our head node does not contain valid data. We set the data of the head node to empty, so if the search is not found, the data we return is the data of the head node (NULL).
We have set up two deletion functions, one is to delete directly, and the other is to delete and return the deleted value to the user. The two implementation ideas are the same, but one adds an extra parameter.

typedef int Cmp(const void*, const void*);//对用户传递的比较函数进行重命名

Test function:

#define NAME_SIZE 32
typedef struct Stu
{
    
    
	int id;
	char name[NAME_SIZE];
	int math;
	int chinese;
}Stu;
void Printf_s(const void* print)//用户写的打印函数
{
    
    
	Stu* prin = (Stu*)print;
	printf("id:%2d  name:%s  math:%2d  chinese:%2d\n",prin->id, prin->name, prin->math, prin->chinese);
}
int cmp_id(const void* s1, const void*s2)//用户写的比较函数
{
    
    
	int *key = (int*)s1;
	Stu *stu = (Stu*)s2;
	return (*key - stu->id);
}
int cmp_name(const void* s1, const void* s2)//用户写的name比较函数
{
    
    
	char* key = (char*)s1;
	Stu* stu = (Stu*)s2;
	return strcmp(key, stu->name);
}

void test1()
{
    
    
	ListHead* pHead = ListCreate(sizeof(Stu));
	Stu stu;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
    
    
		stu.id = i;
		snprintf(stu.name, NAME_SIZE, "stu%d:", i);
		stu.math = rand()%100;
		stu.chinese = rand()%100;
		ListInsert(pHead, &stu, 1);//传入1,进行头插
		//ListInsert(pHead, &stu, 2);//传入2,进行尾插
	}
	ListPrint(pHead, Printf_s);// 双向链表打印
	printf("\n\n");
	//链表元素的查找,通过id查找
	int id = 3;
	Stu *st = ListFind(pHead, &id, cmp_id);
	if (st == NULL)
	{
    
    
		printf("can not find\n");
	}
	else
	{
    
    
		Printf_s(st);
	}
	//链表元素的删除,通过id删除
	printf("\n\n");
	Listdestroy(pHead, &id, cmp_id);
	ListPrint(pHead, Printf_s);
	//链表元素的删除并且返回,通过姓名删除
	printf("\n\n");
	char* p = "stu2:";//不要忘了加分号
	Stu *s = &stu;
	Listtravel(pHead, p, cmp_name, s);
	if (s == NULL)
	{
    
    
		printf("can not find\n");
	}
	else
	{
    
    
		Printf_s(s);
	}
	printf("\n\n");
	ListPrint(pHead, Printf_s);
	Destory(pHead);// 双向链表销毁
}
int main()
{
    
    
	srand((unsigned)time(NULL));
	test1();
	return 0;
}

Insert image description here
In order to verify that we can select any type, here we use id deletion and name deletion respectively. The implementation idea here is the same as the qsort library function.

3. Implementation of leading circular double linked list 2

The first method is to open up space through dynamic memory. Can we not dynamically open up the space of our data area? The answer is yes. Our second idea is to implement our doubly linked list by not dynamically opening up the data area.
Our second idea is to make changes based on the first function implementation. Just keep the test function and the modified function unchanged! ! !

1. Take the lead in cyclic double-linked list implementation 2 structure

typedef struct ListNode//链表的节点
{
    
    
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向上一个节点的指针
	char data[];//传入的数据
	//char data[1];//传入的数据
}ListNode;
typedef struct ListHead //不一样的头节点
{
    
    
	int size;//用来存储传入数据的大小
	ListNode list;//用来存储头节点的数据
}ListHead;

Here we implement it through a flexible array. If the compiler does not support flexible arrays, you can assign the number of elements in the array to 1.
Insert image description here
Here, the purpose of our data is to stand in a position to facilitate us to find the address of the data area.

2. Take the lead in looping the double linked list to implement function changes

ListHead* ListCreate(int datasize)//用来创建特殊的头节点
{
    
    
	ListHead* pHead = (ListHead*)malloc(sizeof(ListHead));
	if (pHead == NULL)//开辟空间失败就报错结束
	{
    
    
		perror("pHead malloc");
		exit(-1);
	}
	pHead->size = datasize;
	pHead->list.next = &pHead->list;//后继节点指向自己
	pHead->list.prev = &pHead->list;//前驱节点指向自己
	return pHead;
}

Here, our data area no longer needs to dynamically open up space, so there is no need to assign values ​​here.

void Destory(ListHead* pHead)// 双向链表销毁
{
    
    
	assert(pHead);
	ListNode* pos = (&pHead->list)->next;//从头节点的下一个节点开始
	while (pos != &pHead->list)
	{
    
    
		ListNode* del = pos;//要释放的节点
		pos = pos->next;//保存下一个节点
		free(del);
	}
}
int ListInsert(ListHead* pHead, const void* pos, int Optional)//双向链表的插入
{
    
    
	assert(pHead);
	ListNode* node = (ListNode*)malloc(sizeof(ListNode)+pHead->size);//申请一个节点结构体的大小加上用户所传数据大小
	if (node == NULL)//开辟空间失败就报错结束
	{
    
    
		perror("node malloc");//节点开辟失败
		return 1;//返回值为1代表节点开辟失败
	}
	memcpy(node->data, (char*)pos,pHead->size);//把数据拷贝到我们开的节点中
	if (HEADINSERTION == Optional)//判断是否为头插
	{
    
    
		node->next = (pHead->list).next;
		node->prev = &(pHead->list);
	}
	else if (BACKINSERTION == Optional)//判断是否为尾插
	{
    
    
		node->next = &pHead->list;
		node->prev = pHead->list.prev;
	}
	else
	{
    
    
		return 2;//返回值为3代表插入位置不符合要求
	}
	node->prev->next = node;
	node->next->prev = node;
	return 0;//代表此函数正常结束
}

Here we need to delete the function that frees up the space. We only need to open up space for two pointers and the size of the data passed in by the user for the double-linked list structure.
Insert image description here
The data we copied is put into the data area.

void* ListFind(ListHead* pHead, const void* key, Cmp* cmp)//双向链表查找
{
    
    
	assert(pHead);
	ListNode* pos = find(pHead, key, cmp);
	if (pos == &pHead->list)//如果是头节点,则证明没找到
	{
    
    
		return NULL;
	}
	return pos->data;//返会我们的数据区。
}
int Listdestroy(ListHead* pHead, const void* key, Cmp* cmp)//双向链表删除
{
    
    
	assert(pHead);
	ListNode* del = find(pHead, key, cmp);
	if (del == &pHead->list)
	{
    
    
		return 1;//代表未找到我们要删除的节点
	}
	//删除节点
	del->prev->next = del->next;
	del->next->prev = del->prev;
	free(del);
	return 0;
}
int Listtravel(ListHead* pHead, const void* key, Cmp* cmp, void* retu)//双向链表删除,并把删除节点返回
{
    
    
	assert(pHead);
	ListNode* del = find(pHead, key, cmp);
	if (del == &pHead->list)
	{
    
    
		return 1;//代表未找到我们要删除的节点
	}
	if (del->data != NULL)
	{
    
    
		memcpy(retu, del->data, pHead->size);//通过函数参数返回
	}
	//删除节点
	del->prev->next = del->next;
	del->next->prev = del->prev;
	free(del);
	return 0;
}

Here we delete the function of releasing data area to open up memory and release memory, and add the judgment of whether it is the head node, because our head node does not have memory in the data area.
Run the test:
Insert image description here

4. Hidden encapsulation of leading circular doubly linked list

Our hidden encapsulation is modified based on implementation 2! ! !

1. Header loop double linked list hidden encapsulated header file preview

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<time.h>
#define HEADINSERTION 1 //头插选项
#define BACKINSERTION 2 //尾插选项
typedef void Printf(const void*);//对用户传递的打印函数进行重命名
typedef int Cmp(const void*, const void*);//对用户传递的打印函数进行重命名
typedef struct ListNode//链表的节点
{
    
    
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向上一个节点的指针
	char data[];//传入的数据
}ListNode;
typedef struct ListHead //不一样的头节点
{
    
    
	int size;//用来存储传入数据的大小
	void (*destory)(struct ListHead* pHead);//双向链表销毁
	void (*listprint)(struct ListHead* pHead, Printf* print);//双向链表打印
	int (*listinsert)(struct ListHead* pHead, const void* pos, int Optional);//双向链表的插入
	void* (*listfind)(struct ListHead* pHead, const void* key, Cmp* cmp);//双向链表查找
	int (*listdestroy)(struct ListHead* pHead, const void* key, Cmp* cmp);//双向链表删除
	int (*listtravel)(struct ListHead* pHead, const void* key, Cmp* cmp, void* retu);//双向链表删除,并把删除节点返回
	ListNode list;//用来存储头节点的数据,柔性数组,这个必须放在最下方
}ListHead;
ListHead* ListCreate(int datasize);//用来创建特殊的头节点

Here we set all the functions we want to implement as function pointers, so that function users can directly call them through the structure without knowing how our functions are implemented

2. Changes to the first loop doubly linked list function

We are making changes based on the second one, so we only show the changed code here.

//提前声明
void Destory(struct ListHead* pHead);// 双向链表销毁
void ListPrint(struct ListHead* pHead, Printf* print);// 双向链表打印
int ListInsert(struct ListHead* pHead, const void* pos, int Optional);//双向链表的插入
void* ListFind(struct ListHead* pHead, const void* key,Cmp* cmp);//双向链表查找
int Listdestroy(struct ListHead* pHead, const void* key, Cmp* cmp);//双向链表删除
int Listtravel(struct ListHead* pHead, const void* key, Cmp* cmp,void* retu);//双向链表删除,并把删除节点返回
ListHead* ListCreate(int datasize)//用来创建特殊的头节点
{
    
    
	ListHead* pHead = (ListHead*)malloc(sizeof(ListHead));
	if (pHead == NULL)//开辟空间失败就报错结束
	{
    
    
		perror("pHead malloc");
		exit(-1);
	}
	pHead->size = datasize;
	pHead->list.next = &pHead->list;//后继节点指向自己
	pHead->list.prev = &pHead->list;//前驱节点指向自己
	pHead->destory = Destory;//把我们封装函数指针指向相应的函数
	pHead->listprint = ListPrint;
	pHead->listinsert = ListInsert;
	pHead->listfind = ListFind;
	pHead->listdestroy = Listdestroy;
	pHead->listtravel = Listtravel;
	return pHead;
}

When we assign a function pointer, we must first declare the function we want to point to, so that no error will be reported.
Modification of test function:

#define NAME_SIZE 32
typedef struct Stu
{
    
    
	int id;
	char name[NAME_SIZE];
	int math;
	int chinese;
}Stu;
void Printf_s(const void* print)//用户写的打印函数
{
    
    
	Stu* prin = (Stu*)print;
	printf("id:%2d  name:%s  math:%2d  chinese:%2d\n",prin->id, prin->name, prin->math, prin->chinese);
}
int cmp_id(const void* s1, const void*s2)//用户写的id比较函数
{
    
    
	int *key = (int*)s1;
	Stu *stu = (Stu*)s2;
	return (*key - stu->id);
}
int cmp_name(const void* s1, const void* s2)//用户写的name比较函数
{
    
    
	char* key = (char*)s1;
	Stu* stu = (Stu*)s2;
	return strcmp(key,stu->name);
}
void test1()
{
    
    
	ListHead* pHead = ListCreate(sizeof(Stu));
	Stu stu;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
    
    
		stu.id = i;
		snprintf(stu.name, NAME_SIZE, "stu%d:", i);
		stu.math = rand()%100;
		stu.chinese = rand()%100;
		pHead->listinsert(pHead, &stu, 2);
	}
	pHead->listprint(pHead, Printf_s);// 双向链表打印
	//链表元素的查找,通过id查找
	printf("\n\n");
	int id = 3;
	Stu *st = pHead->listfind(pHead, &id, cmp_id);
	if (st == NULL)
	{
    
    
		printf("can not find\n");
	}
	else
	{
    
    
		Printf_s(st);
	}
	//链表元素的删除,通过id删除
	printf("\n\n");
	pHead->listdestroy(pHead, &id, cmp_id);
	pHead->listprint(pHead, Printf_s);
	//链表元素的删除,通过姓名删除
	printf("\n\n");
	char* p = "stu2:";//不要忘了加分号
	Stu *s = &stu;
	pHead->listtravel(pHead, p, cmp_name, s);
	if (s == NULL)
	{
    
    
		printf("can not find\n");
	}
	else
	{
    
    
		Printf_s(s);
	}
	printf("\n\n");
	pHead->listprint(pHead, Printf_s);
	pHead->destory(pHead);// 双向链表销毁
}
int main()
{
    
    
	srand((unsigned)time(NULL));
	test1();
	return 0;
}

We need to call the part of the transposed structure that we implemented through the function.
Insert image description here

5. Take the lead in encapsulating the circular double linked list into a library

Our encapsulation as a library also makes changes based on Implementation 2! ! !

1. Take the lead in the circular double-linked list package as the header file preview of the library

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<time.h>
#define HEADINSERTION 1 //头插选项
#define BACKINSERTION 2 //尾插选项
//可以做出成一个动态库或静态库,不需要知道我们的结构体类型就可以操作构建一个结构体
typedef void ListHead;// 把void类型改为ListHead,我们函数进行传参都是用的void
typedef void Printf(const void* );//对用户传递的打印函数进行重命名
typedef int Cmp(const void*, const void*);//对用户传递的打印函数进行重命名
ListHead* ListCreate(int datasize);//用来创建特殊的头节点
void Destory(ListHead* pHead);// 双向链表销毁
void ListPrint(ListHead* pHead, Printf* print);// 双向链表打印
int ListInsert(ListHead* pHead, const void* pos, int Optional);//双向链表的插入
void* ListFind(ListHead* pHead, const void* key,Cmp* cmp);//双向链表查找
int Listdestroy(ListHead* pHead, const void* key, Cmp* cmp);//双向链表删除
int Listtravel(ListHead* pHead, const void* key, Cmp* cmp,void* retu);//双向链表删除,并把删除节点返回

Here we have implemented the hiding of our structure. The user does not know the type of our structure, but only knows what functions our function can achieve, and the parameters passed in are all void pointer types. This prevents users from knowing our structure type and making changes. We put the structure type into the source file where we implement the function.

2. The source file for the implementation of the first loop double linked list function

typedef struct ListNode//链表的节点
{
    
    
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向上一个节点的指针
	char data[];//传入的数据
}ListNode;
struct ListHead //不一样的头节点
{
    
    
	int size;//用来存储传入数据的大小
	ListNode list;//用来存储头节点的数据
};
ListHead* ListCreate(int datasize)//用来创建特殊的头节点
{
    
    
	struct ListHead* pHead = (struct ListHead*)malloc(sizeof(struct ListHead));
	if (pHead == NULL)//开辟空间失败就报错结束
	{
    
    
		perror("pHead malloc");
		exit(-1);
	}
	pHead->size = datasize;
	pHead->list.next = &pHead->list;//后继节点指向自己
	pHead->list.prev = &pHead->list;//前驱节点指向自己
	return pHead;
}
void Destory(ListHead* p)// 双向链表销毁
{
    
    
	assert(p);
	struct ListHead *pHead = p;
	ListNode* pos = (&pHead->list)->next;//从头节点的下一个节点开始
	while (pos != &pHead->list)
	{
    
    
		ListNode* del = pos;//要释放的节点
		pos = pos->next;//保存下一个节点
		free(del);
	}
}
void ListPrint(ListHead* p, Printf * print)// 双向链表打印,使用回调函数
{
    
    
	assert(p);
	struct ListHead* pHead = p;
	ListNode* pos = pHead->list.next;
	while (pos != &pHead->list)//判断是否走到头节点
	{
    
    
		print(pos->data);//调用用户提供的打印函数
		pos = pos->next;
	}
}
int ListInsert(ListHead* p, const void* pos, int Optional)//双向链表的插入
{
    
    
	assert(p);
	struct ListHead* pHead = p;
	ListNode* node = (ListNode*)malloc(sizeof(ListNode)+pHead->size);//申请一个节点结构体的大小加上用户所传数据大小
	if (node == NULL)//开辟空间失败就报错结束
	{
    
    
		perror("node malloc");//节点开辟失败
		return 1;//返回值为1代表节点开辟失败
	}
	memcpy(node->data, (char*)pos,pHead->size);//把数据拷贝到我们开的节点中
	if (HEADINSERTION == Optional)//判断是否为头插
	{
    
    
		node->next = (pHead->list).next;
		node->prev = &(pHead->list);
	}
	else if (BACKINSERTION == Optional)//判断是否为尾插
	{
    
    
		node->next = &pHead->list;
		node->prev = pHead->list.prev;
	}
	else
	{
    
    
		return 2;//返回值为3代表插入位置不符合要求
	}
	node->prev->next = node;
	node->next->prev = node;
	return 0;//代表此函数正常结束
}
ListNode* find(ListHead* p, const void* key, Cmp* cmp)//双向链表查找
{
    
    
	assert(p);
	struct ListHead* pHead = p;
	ListNode* pos = pHead->list.next;
	while (pos != &pHead->list)//判断是否走到头节点
	{
    
    
		if (cmp(key, pos->data) == 0)//调用用户提供的比较函数
		{
    
    
			break;
		}
		pos = pos->next;
	}
	return pos;//如果找到时,循环终止,返回值为找到的节点,如果找不到,则返回pos为头节点。
}
void* ListFind(ListHead* p, const void* key, Cmp* cmp)//双向链表查找
{
    
    
	assert(p);
	struct ListHead* pHead = p;
	ListNode* pos = find(pHead, key, cmp);
	if (pos == &pHead->list)//如果是头节点,则证明没找到
	{
    
    
		return NULL;
	}
	return pos->data;//返会我们的数据区。
}
int Listdestroy(ListHead* p, const void* key, Cmp* cmp)//双向链表删除
{
    
    
	assert(p);
	struct ListHead* pHead = p;
	ListNode* del = find(pHead, key, cmp);
	if (del == &pHead->list)
	{
    
    
		return 1;//代表未找到我们要删除的节点
	}
	//删除节点
	del->prev->next = del->next;
	del->next->prev = del->prev;
	free(del);
	return 0;
}
int Listtravel(ListHead* p, const void* key, Cmp* cmp, void* retu)//双向链表删除,并把删除节点返回
{
    
    
	assert(p);
	struct ListHead* pHead = p;
	ListNode* del = find(pHead, key, cmp);
	if (del == &pHead->list)
	{
    
    
		return 1;//代表未找到我们要删除的节点
	}
	if (del->data != NULL)
	{
    
    
		memcpy(retu, del->data, pHead->size);//通过函数参数返回
	}
	//删除节点
	del->prev->next = del->next;
	del->next->prev = del->prev;
	free(del);
	return 0;
}

What we changed is to convert the void type pointer to the type of our own structure in the function we implemented.
Insert image description here
Our test function is the same as we implemented 2.

3. Take the lead in encapsulating the circular double linked list into a library

1. Find the project, select Properties, and in the configuration properties, change the configuration type to static library (.lib).
Insert image description here
2. Generate
3. Find our .lib file in our project file, which is the generated static library.

6. Partial analysis of the kernel double-linked list to quickly implement a double-linked list

1. Partial analysis of the kernel double linked list

Insert image description here
The most important node pointer in the linked list is the linked list structure in the kernel. There is no data area in it. If we want to create a double linked list, we need to include its structure in our structure. Can appear anywhere in our structure.
Insert image description here
Insert image description here
The head insertion and tail insertion implemented here are both defined functions, and then this function is called to implement our insertion and deletion.

2. Quickly implement a simple lead circular double linked list

typedef struct ListNode//链表的节点
{
    
    
	int data;
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向上一个节点的指针
}ListNode;
ListNode* ListCreate()//用来创建头节点
{
    
    
	ListNode* pHead = (ListNode*)malloc(sizeof(ListNode));
	if (pHead == NULL)//开辟空间失败就报错结束
	{
    
    
		perror("pHead malloc");
		exit(-1);
	}
	pHead->data = 0;
	pHead->next = pHead;//后继节点指向自己
	pHead->prev = pHead;//前驱节点指向自己
	return pHead;
}
void Destory(ListNode* pHead)// 双向链表销毁
{
    
    
	assert(pHead);
	ListNode* pos = pHead->next;//从头节点的下一个节点开始
	while (pos != pHead)
	{
    
    
		ListNode* del = pos;//要释放的节点
		pos = pos->next;//保存下一个节点
		free(del);//释放节点
	}
}
void ListPrint(ListNode* pHead)// 双向链表打印
{
    
    
	assert(pHead);
	ListNode* pos = pHead->next;//从头节点的下一个节点开始
	while (pos != pHead)
	{
    
    
		printf("%d->", pos->data);
		pos = pos->next;
	}
	printf("\n");
}
void ListInsert(ListNode* pos, int key)//双向链表的插入
{
    
    
	assert(pos);
	ListNode* init = (ListNode*)malloc(sizeof(ListNode));
	if (init == NULL)//开辟空间失败就报错结束
	{
    
    
		perror("pHead malloc");
		exit(-1);
	}
	init->data = key;
	init->next = pos->next;
	init->prev = pos;
	pos->next->prev = init;
	pos->next = init;
}
void Listdestroy(ListNode* pHead,ListNode* pos)//双向链表删除
{
    
    
	assert(pos);
	if (pos == pHead)
	{
    
    
		return;
	}
	ListNode* del = pos;
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(del);
}
void SListPushBack(ListNode* pHead, int x)//单链表尾插
{
    
    
	assert(pHead);
	ListInsert(pHead->prev, x);
}
void SListPushFront(ListNode* pHead, int x)//单链表的头插
{
    
    
	assert(pHead);
	ListInsert(pHead, x);
}
void SListPopBack(ListNode* pHead)// 单链表的尾删
{
    
    
	assert(pHead);
	Listdestroy(pHead,pHead->prev);
}
void SListPopFront(ListNode* pHead)// 单链表头删
{
    
    
	assert(pHead);
	Listdestroy(pHead,pHead->next);
}
ListNode* ListFind(ListNode* pHead, int key)//双向链表查找
{
    
    
	assert(pos);
	ListNode* pos = pHead->next;
	while (pos != pHead)
	{
    
    
		if (pos->data == key)
		{
    
    
			return pos;
		}
		pos = pos->next;
	}
	return NULL;
}

According to the above code, we can see that we only need to implement the insertion and deletion of the double-linked list to realize most of the functions of the double-linked list.
I believe that through so many cases and exercises, everyone has an understanding of the implementation and use of the double-linked list, so let's implement it yourself.

7. Code for double linked list implementation 2

Since our subsequent encapsulation and composition library are all changed through 2, let's take a look at the code that implements 2.

1. The header file of the code for double linked list implementation 2

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<time.h>
#define HEADINSERTION 1 //头插选项
#define BACKINSERTION 2 //尾插选项
typedef struct ListNode//链表的节点
{
    
    
	struct ListNode* next;//指向下一个节点的指针
	struct ListNode* prev;//指向上一个节点的指针
	char data[];//传入的数据
	//char data[1];//传入的数据
}ListNode;
typedef struct ListHead //不一样的头节点
{
    
    
	int size;//用来存储传入数据的大小
	ListNode list;//用来存储头节点的数据
}ListHead;
typedef void Printf(const void* );//对用户传递的打印函数进行重命名
typedef int Cmp(const void*, const void*);//对用户传递的打印函数进行重命名
ListHead* ListCreate(int datasize);//用来创建特殊的头节点
void Destory(ListHead* pHead);// 双向链表销毁
void ListPrint(ListHead* pHead, Printf* print);// 双向链表打印
int ListInsert(ListHead* pHead, const void* pos, int Optional);//双向链表的插入
void* ListFind(ListHead* pHead, const void* key,Cmp* cmp);//双向链表查找
int Listdestroy(ListHead* pHead, const void* key, Cmp* cmp);//双向链表删除
int Listtravel(ListHead* pHead, const void* key, Cmp* cmp,void* retu);//双向链表删除,并把删除节点返回

2. Function implementation source file of double linked list implementation 2

#include"main2.h"
ListHead* ListCreate(int datasize)//用来创建特殊的头节点
{
    
    
	ListHead* pHead = (ListHead*)malloc(sizeof(ListHead));
	if (pHead == NULL)//开辟空间失败就报错结束
	{
    
    
		perror("pHead malloc");
		exit(-1);
	}
	pHead->size = datasize;
	pHead->list.next = &pHead->list;//后继节点指向自己
	pHead->list.prev = &pHead->list;//前驱节点指向自己
	return pHead;
}
void Destory(ListHead* pHead)// 双向链表销毁
{
    
    
	assert(pHead);
	ListNode* pos = (&pHead->list)->next;//从头节点的下一个节点开始
	while (pos != &pHead->list)
	{
    
    
		ListNode* del = pos;//要释放的节点
		pos = pos->next;//保存下一个节点
		free(del);
	}
}
void ListPrint(ListHead* pHead, Printf * print)// 双向链表打印,使用回调函数
{
    
    
	assert(pHead);
	ListNode* pos = pHead->list.next;
	while (pos != &pHead->list)//判断是否走到头节点
	{
    
    
		print(pos->data);//调用用户提供的打印函数
		pos = pos->next;
	}
}
int ListInsert(ListHead* pHead, const void* pos, int Optional)//双向链表的插入
{
    
    
	assert(pHead);
	ListNode* node = (ListNode*)malloc(sizeof(ListNode)+pHead->size);//申请一个节点结构体的大小加上用户所传数据大小
	if (node == NULL)//开辟空间失败就报错结束
	{
    
    
		perror("node malloc");//节点开辟失败
		return 1;//返回值为1代表节点开辟失败
	}
	memcpy(node->data, (char*)pos,pHead->size);//把数据拷贝到我们开的节点中
	if (HEADINSERTION == Optional)//判断是否为头插
	{
    
    
		node->next = (pHead->list).next;
		node->prev = &(pHead->list);
	}
	else if (BACKINSERTION == Optional)//判断是否为尾插
	{
    
    
		node->next = &pHead->list;
		node->prev = pHead->list.prev;
	}
	else
	{
    
    
		return 2;//返回值为3代表插入位置不符合要求
	}
	node->prev->next = node;
	node->next->prev = node;
	return 0;//代表此函数正常结束
}
ListNode* find(ListHead* pHead, const void* key, Cmp* cmp)//双向链表查找
{
    
    
	assert(pHead);
	ListNode* pos = pHead->list.next;
	while (pos != &pHead->list)//判断是否走到头节点
	{
    
    
		if (cmp(key, pos->data) == 0)//调用用户提供的比较函数
		{
    
    
			break;
		}
		pos = pos->next;
	}
	return pos;//如果找到时,循环终止,返回值为找到的节点,如果找不到,则返回pos为头节点。
}
void* ListFind(ListHead* pHead, const void* key, Cmp* cmp)//双向链表查找
{
    
    
	assert(pHead);
	ListNode* pos = find(pHead, key, cmp);
	if (pos == &pHead->list)//如果是头节点,则证明没找到
	{
    
    
		return NULL;
	}
	return pos->data;//返会我们的数据区。
}
int Listdestroy(ListHead* pHead, const void* key, Cmp* cmp)//双向链表删除
{
    
    
	assert(pHead);
	ListNode* del = find(pHead, key, cmp);
	if (del == &pHead->list)
	{
    
    
		return 1;//代表未找到我们要删除的节点
	}
	//删除节点
	del->prev->next = del->next;
	del->next->prev = del->prev;
	free(del);
	return 0;
}
int Listtravel(ListHead* pHead, const void* key, Cmp* cmp, void* retu)//双向链表删除,并把删除节点返回
{
    
    
	assert(pHead);
	ListNode* del = find(pHead, key, cmp);
	if (del == &pHead->list)
	{
    
    
		return 1;//代表未找到我们要删除的节点
	}
	if (del->data != NULL)
	{
    
    
		memcpy(retu, del->data, pHead->size);//通过函数参数返回
	}
	//删除节点
	del->prev->next = del->next;
	del->next->prev = del->prev;
	free(del);
	return 0;
}

3. Main function source file of double linked list implementation 2

#include"main2.h"
#define NAME_SIZE 32
typedef struct Stu
{
    
    
	int id;
	char name[NAME_SIZE];
	int math;
	int chinese;
}Stu;
void Printf_s(const void* print)//用户写的打印函数
{
    
    
	Stu* prin = (Stu*)print;
	printf("id:%2d  name:%s  math:%2d  chinese:%2d\n",prin->id, prin->name, prin->math, prin->chinese);
}
int cmp_id(const void* s1, const void*s2)//用户写的比较函数
{
    
    
	int *key = (int*)s1;
	Stu *stu = (Stu*)s2;
	return (*key - stu->id);
}
int cmp_name(const void* s1, const void* s2)//用户写的name比较函数
{
    
    
	char* key = (char*)s1;
	Stu* stu = (Stu*)s2;
	return strcmp(key, stu->name);
}

void test1()
{
    
    
	ListHead* pHead = ListCreate(sizeof(Stu));
	Stu stu;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
    
    
		stu.id = i;
		snprintf(stu.name, NAME_SIZE, "stu%d:", i);
		stu.math = rand()%100;
		stu.chinese = rand()%100;
		ListInsert(pHead, &stu, 1);//传入1,进行头插
		//ListInsert(pHead, &stu, 2);//传入2,进行尾插
	}
	ListPrint(pHead, Printf_s);// 双向链表打印
	//链表元素的查找,通过id查找
	printf("\n\n");
	int id = 3;
	Stu *st = ListFind(pHead, &id, cmp_id);
	if (st == NULL)
	{
    
    
		printf("can not find\n");
	}
	else
	{
    
    
		Printf_s(st);
	}
	//链表元素的删除,通过id删除
	printf("\n\n");
	Listdestroy(pHead, &id, cmp_id);
	ListPrint(pHead, Printf_s);
	//链表元素的删除并且返回,通过姓名删除
	printf("\n\n");
	char* p = "stu2:";//不要忘了加分号
	Stu *s = &stu;
	Listtravel(pHead, p, cmp_name, s);
	if (s == NULL)
	{
    
    
		printf("can not find\n");
	}
	else
	{
    
    
		Printf_s(s);
	}
	printf("\n\n");
	ListPrint(pHead, Printf_s);
	Destory(pHead);// 双向链表销毁
}
int main()
{
    
    
	srand((unsigned)time(NULL));
	test1();
	return 0;
}

Summarize

I believe that everyone has a deep understanding of our double linked list. This leading circular linked list will be the structure we use the most , so this requires us to have a deep understanding. Everyone is welcome to leave a message. Thanks for the support.

Guess you like

Origin blog.csdn.net/2301_76986069/article/details/132172980