链表和数组是两种常见的数据结构,各有优缺点和适用场景。以下是它们的主要区别和使用场景。
数组
- 固定大小:数组的大小在创建时确定,不能动态调整。
- 连续内存:数组在内存中是连续分配的,这使得它可以通过索引快速访问元素。
- 快速访问:访问数组中的任意元素时间复杂度为 (O(1))。
- 插入和删除:在数组中插入或删除元素需要移动其他元素,时间复杂度为 (O(n))。
- 频繁访问:当需要频繁访问数据并且数据大小是已知且固定的,数组是很好的选择。
- 内存局部性:数组在内存中是连续的,这使得其具有较好的缓存性能。
- 简单结构:对于简单、固定大小的数据,数组实现简单且效率高。
链表
- 动态大小:链表的大小可以动态调整,插入和删除元素不需要移动其他元素。
- 非连续内存:链表的元素在内存中不必是连续的,每个元素包含数据和一个指向下一个元素的指针。
- 快速插入和删除:在链表中插入或删除元素,只需要调整指针,时间复杂度为 (O(1))(假设已知插入/删除位置)。
- 访问慢:访问链表中的任意元素需要从头遍历,时间复杂度为 (O(n)) 。
- 频繁插入和删除:当需要频繁进行插入和删除操作,且操作的位置是已知的,链表是很好的选择。
- 不确定大小:当数据大小不确定或需要动态调整时,链表具有灵活性。
- 分散内存:当内存分配不连续时,链表可以充分利用碎片化内存。
单向非循环链表
单向非循环链表是一种链式存储的数据结构,其中每个节点包含一个数据元素和一个指向下一个节点的指针。链表的最后一个节点的指针指向 null
,表示链表的结束。它不像数组那样需要连续的内存空间,可以通过动态分配内存来增加或删除元素。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//节点构建
typedef struct AddrBook
{
//数据域
char Name[32];
char PhoneNum[12];
//指针域
struct AddrBook *next;
}AdrB;
//编写带头结点空表的创建
AdrB *List_Create(void)
{
AdrB *p ;//临时存放头指针的变量
p = malloc(sizeof(AdrB));
if(p != NULL)
{
p->next = NULL;//单项不循环空表
}
return p;//不论成功失败 返回头指针值
}
/*
函数功能:单项不循环链表节点插入的链式关系处理-内部函数
参数1:
参数2:
参数3:
返回值:无
*/
static void _NodeInsert(AdrB *PreNode,AdrB * NextNode,AdrB *NewNode)
{
NewNode->next = NextNode;
PreNode->next = NewNode;
}
// _NodeInsert(head,head->next,Newnode) //头插链式处理
// _NodeInsert(TailNode,TailNode->next,Newnode)//尾插链式处理
//头插函数
AdrB *List_HeadInsert(AdrB *head, char *Name, char *Num)
{
AdrB *Newnode = (AdrB *)malloc(sizeof(AdrB));
if (Newnode == NULL)
{
return NULL;
}
//指针域处理
_NodeInsert(head,head->next,Newnode);
//数据域处理
strcpy(Newnode->Name, Name);
strcpy(Newnode->PhoneNum, Num);
return head;
}
//尾插函数
void List_TailInsert(AdrB *head, char *Name, char *Num)
{
AdrB *p = head;//从头节点开始
AdrB *NewNode;
//先找到尾节点-遍历---p->next是NULL p就是尾
while(p->next!=NULL)
p=p->next;
NewNode = malloc(sizeof(AdrB));
//数据域处理
//指针域处理
_NodeInsert(p,p->next,NewNode);
}
//遍历打印
void List_printf(AdrB *head)
{
AdrB *p;
p = head->next;
while(p != NULL)
{
printf("%s %s\n",p->Name,p->PhoneNum);
p = p->next;
}
}
//内部函数
static void _NodeDelete(AdrB *PreNode, AdrB *DelNode)
{
//链式关系处理
PreNode->next = DelNode->next;
//可以free()
free(DelNode);
}
//删除节点 按照条件删除
int List_DelNode(AdrB *head,char *Nameofphone)
{
AdrB *p = head;
while(p->next != NULL)
{
if(strcmp(p->next->Name,Nameofphone)==0 || strcmp(p->next->PhoneNum,Nameofphone)==0)
{
//找到了名字匹配的节点 或 找到了电话号码匹配的节点
_NodeDelete(p,p->next);
return 0;
}
// else if(strcmp(p->next->PhoneNum,Nameofphone)==0)
// {
// //找到了电话号码匹配的节点
// }
p = p->next;//下轮之前p指向下一个节点
}
return -1;
}
//链表的销毁
int List_Destroy(AdrB **head)
{
//定义两个临时指针,p指向头节点,pre指向第一个有效节点
AdrB *p,*pre;
p = *head;
pre = (*head)->next;
//如果pre指向不为空,
while(pre != NULL)
{
free(p); //删除
p = pre; //找回指引
pre =pre->next; //往后移
}
//说明只有一个节点
free(p);
p = NULL;
*head = NULL;//把外面实参的Head变为NULL
return 0;
}
int main()
{
char name[32]={0};
char phone[12]={0};
//①先创建一条空表
AdrB *Head = List_Create();
//②测试头插
printf("插入节点-头插:\n");
for(int i=0;i<3;i++)
{
scanf("%s %s",name,phone);
List_HeadInsert(Head,name,phone);
}
printf("遍历:\n");
List_printf(Head);
//②删除节点测试
printf("删除节点测试-输入要删除的节点信息:\n");
//③销毁链表测试
printf("链表销毁测试:\n");
List_Destroy(&Head);
printf("%p\n",Head);
//List_printf(Head);
return 0;
}
双向链表
双向链表是一种链式存储的数据结构,其中每个节点包含数据元素、一个指向下一个节点的指针和一个指向前一个节点的指针。与单向非循环链表不同,双向链表可以双向遍历,增加了操作的灵活性。
#include <stdio.h>
#include <stdlib.h>
// 节点结构体
typedef struct Node {
int data;
struct Node* prev;
struct Node* next;
} Node;
// 链表结构体
typedef struct DoublyLinkedList {
Node* head;
Node* tail;
} DoublyLinkedList;
// 创建新节点
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}
// 初始化链表
DoublyLinkedList* createList() {
DoublyLinkedList* list = (DoublyLinkedList*)malloc(sizeof(DoublyLinkedList));
list->head = NULL;
list->tail = NULL;
return list;
}
// 在链表末尾添加节点
void append(DoublyLinkedList* list, int data) {
Node* newNode = createNode(data);
if (list->head == NULL) {
list->head = newNode;
list->tail = newNode;
} else {
list->tail->next = newNode;
newNode->prev = list->tail;
list->tail = newNode;
}
}
// 在链表开头添加节点
void prepend(DoublyLinkedList* list, int data) {
Node* newNode = createNode(data);
if (list->head == NULL) {
list->head = newNode;
list->tail = newNode;
} else {
newNode->next = list->head;
list->head->prev = newNode;
list->head = newNode;
}
}
// 删除指定节点
void deleteNode(DoublyLinkedList* list, Node* nodeToDelete) {
if (list->head == NULL || nodeToDelete == NULL) {
return;
}
if (nodeToDelete == list->head) {
list->head = nodeToDelete->next;
if (list->head) {
list->head->prev = NULL;
}
} else {
nodeToDelete->prev->next = nodeToDelete->next;
}
if (nodeToDelete == list->tail) {
list->tail = nodeToDelete->prev;
if (list->tail) {
list->tail->next = NULL;
}
} else {
nodeToDelete->next->prev = nodeToDelete->prev;
}
free(nodeToDelete);
}
// 打印链表
void printList(DoublyLinkedList* list) {
Node* current = list->head;
while (current) {
printf("%d <-> ", current->data);
current = current->next;
}
printf("NULL\n");
}
// 示例用法
int main() {
DoublyLinkedList* dll = createList();
append(dll, 1);
append(dll, 2);
append(dll, 3);
prepend(dll, 0);
printf("链表内容:\n");
printList(dll); // 输出: 0 <-> 1 <-> 2 <-> 3 <-> NULL
// 删除元素
Node* nodeToDelete = dll->head->next; // 节点值为 1
deleteNode(dll, nodeToDelete);
printf("删除节点值为 1 后的链表内容:\n");
printList(dll); // 输出: 0 <-> 2 <-> 3 <-> NULL
// 释放内存
while (dll->head) {
Node* temp = dll->head;
dll->head = dll->head->next;
free(temp);
}
free(dll);
return 0;
}
双向循环链表
双向循环链表是一种数据结构,它结合了双向链表和循环链表的特性。它可以在两个方向上遍历,并且在到达末尾时会回到起点。下面是这种数据结构的基本概念和特点:
- 双向性:每个节点都有两个指针,一个指向前一个节点(prev),一个指向后一个节点(next)。这使得从任意节点出发,可以向前或向后遍历整个列表。
- 循环性:最后一个节点的
next
指针指向第一个节点,而第一个节点的prev
指针指向最后一个节点。这样,列表形成一个环状结构,遍历时不会遇到终点,可以不断循环。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//节点构建
typedef struct AddrBook
{
//数据域
char Name[32];
char PhoneNum[12];
//指针域
struct AddrBook *next; //后继指针
struct AddrBook *prev; //前驱指针
} AdrB;
//编写带头结点空表的创建
AdrB *List_Create(void)
{
AdrB *p; //临时存放头指针的变量
p = malloc(sizeof(AdrB));
if (p != NULL)
{
p->next = p; //双向循环空表
p->prev = p;
}
return p; //不论成功失败 返回头指针值
}
/*
函数功能:单项不循环链表节点插入的链式关系处理-内部函数
参数1:
参数2:
参数3:
返回值:无
*/
static void _NodeInsert(AdrB *PreNode, AdrB *NextNode, AdrB *NewNode)
{
// NewNode->next = NextNode;
// PreNode->next = NewNode;
NewNode->next = NextNode;
PreNode->next = NewNode;
NewNode->prev = PreNode;
NextNode->prev = NewNode;
}
// _NodeInsert(head,head->next,NewNode)
// _NodeInsert(head,head->next,Newnode) //头插链式处理
// _NodeInsert(TailNode,TailNode->next,Newnode)//尾插链式处理
//头插函数
AdrB *List_HeadInsert(AdrB *head, char *Name, char *Num)
{
AdrB *Newnode = (AdrB *)malloc(sizeof(AdrB));
if (Newnode == NULL)
{
return NULL;
}
//指针域处理
_NodeInsert(head, head->next, Newnode);
//数据域处理
strcpy(Newnode->Name, Name);
strcpy(Newnode->PhoneNum, Num);
return head;
}
//尾插函数
void List_TailInsert(AdrB *head, char *Name, char *Num)
{
// AdrB *p = head;//从头节点开始
AdrB *NewNode;
//先找到尾节点-遍历---p->next是NULL p就是尾
// while(p->next!=NULL)
// p=p->next;
NewNode = malloc(sizeof(AdrB));
//数据域处理
//指针域处理
_NodeInsert(head->prev, head, NewNode);
}
//遍历打印
void List_printf(AdrB *head)
{
AdrB *p;
p = head->next;
while (p != head)
{
printf("%s %s\n", p->Name, p->PhoneNum);
p = p->next;
}
}
//内部函数-节点删除的链式关系处理 两个节点间
static void _NodeDelete(AdrB *PrevNode,AdrB *NextNode, AdrB *DelNode)
{
//链式关系处理
PrevNode->next = NextNode;
NextNode->prev = PrevNode;
//可以free()
free(DelNode);
}
//删除节点 按照条件删除
int List_DelNode(AdrB *head, char *Nameofphone)
{
AdrB *p = head;
while (p->next != head)
{
if (strcmp(p->next->Name, Nameofphone) == 0 || strcmp(p->next->PhoneNum, Nameofphone) == 0)
{
//找到了名字匹配的节点 或 找到了电话号码匹配的节点
_NodeDelete(p,p->next->next, p->next);
return 0;
}
// else if(strcmp(p->next->PhoneNum,Nameofphone)==0)
// {
// //找到了电话号码匹配的节点
// }
p = p->next; //下轮之前p指向下一个节点
}
return -1;
}
//链表的销毁
int List_Destroy(AdrB **head)
{
//定义两个临时指针,p指向头节点,pre指向第一个有效节点
AdrB *p, *pre;
p = *head;
pre = (*head)->next;
//如果pre指向不为空,
while (pre != head)
{
free(p); //删除
p = pre; //找回指引
pre = pre->next; //往后移
}
//说明只有一个节点
free(p);
p = NULL;
*head = NULL; //把外面实参的Head变为NULL
return 0;
}
int main()
{
char name[32] = {0};
char phone[12] = {0};
//①先创建一条空表
AdrB *Head = List_Create();
//②测试头插
printf("插入节点-头插:\n");
for (int i = 0; i < 3; i++)
{
scanf("%s %s", name, phone);
List_HeadInsert(Head, name, phone);
}
printf("遍历:\n");
List_printf(Head);
//②删除节点测试
printf("删除节点测试-输入要删除的节点信息:\n");
//③销毁链表测试
printf("链表销毁测试:\n");
List_Destroy(&Head);
printf("%p\n", Head);
// List_printf(Head);
return 0;
}
单双链表图解
通用链表
通用链表是一种灵活的数据结构,允许存储不同类型的数据。注意通用型算法一律都只能写到头文件 list.h 中,因为编译的时候 datatype 必须结合用户提供的 *.c 源文件才能确定切确的类型,如果单独编辑 list.c,那么在编译产生 list.o 的过程中就无法使用用 户所指定的类型。 上述代码必须写在 *.h 头文件中,而不是 *.c 源文件 用户使用该容器的时候,定义 DATATYPE 为其所需要的数据类型。
注意通用型算法代码 list.h 的使用方法,就是直接作为头文件放在用户程序中即可,如果用户需要使用 链表容器处理其特定的数据,那么就自定义宏 DATATYPE,如:
头文件:
#include <stdbool.h>
#ifndef DATATYPE //此条件决定了下面通用结构体的具体类型 如果在.c文件中有包含 则 使
//用头文件中数据类型---用户提供
#define DATATYPE int //用户不提供 则默认int类型 防止编译报错
#endif
// 原名 别名
typedef DATATYPE datatype;
//通用型链表的节点构建
typedef struct node
{
//数据域--不再固定
datatype Data;
//指针域固定
struct node *next;
struct node *prev;
}listnode;
//通用型双向循环链表的空表创建
static listnode * List_Init(void)
{
//头结点空间申请
listnode * head = (listnode *)malloc(sizeof(listnode));
//如果malloc成功 则处理指针域 数据域无效
if(head != NULL)
{
head->prev = head;
head->next = head;
}
return head;
}
//双向链式关系处理 头插 尾插 条件插入都可以用
static void __NodeInsert(listnode *prevnode,listnode *nextnode,listnode *newnode)
{
newnode->next = nextnode;
prevnode->next = newnode;
nextnode->prev = newnode;
newnode->prev = prevnode;
}
//通用型双向循环链表的头插
static int List_HeadInsert(listnode *head,datatype data)
{
listnode * NewNode = (listnode *)malloc(sizeof(listnode));
if(NewNode == NULL)
return -1;
//处理数据域
NewNode->Data = data;
//处理指针域
__NodeInsert(head,head->next,NewNode);
return 0;
}
//通用型链表遍历
static void List_Traverse(listnode *head,void (*function)(listnode *))
{
listnode *p = head->next;//第一个有效节点
while(p != head)
{
//p指向当前节点--传入遍历到的当前节点的地址
function(p);
p=p->next;
}
}
//
//参数1:head指定哪条链表
//参数2:用来查找节点的参考数据---参考这个data来找的
//参数3:equal 查找的方法 外面决定
static listnode *List_find(listnode *head, datatype data,bool (*equal)(datatype, datatype))
{
for(listnode *p=head->next; p!=head; p=p->next)
{
if(equal(p->Data, data))
return p;
}
return NULL;
}
//将两个任意节点连接起来
static void __NodeDelete(listnode *prevnode,listnode *nextnode)
{
prevnode->next = nextnode;
nextnode->prev = prevnode;
}
//通用型链表的删除节点---指定节点 不需要知道怎么
static bool List_DelNode(listnode *p)
{
if(p==NULL)
return false;//失败
//链式关系处理
__NodeDelete(p->prev,p->next);
free(p);
return true;//成功
}
主文件:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//定义存放通讯录信息的结构---用户自定义的类型
typedef struct
{
char Name[32];
char PhoneNum[12];
}AdrB;
#define DATATYPE AdrB
#include "List_Universal.h"//自己写的头文件 双引号包含
//自己写的回调函数,根据需要在不同的场合传入到遍历函数中处理节点
//1、传入一个节点并打印数据域内容
void print_data(listnode *p)
{
printf("%s %s\n",p->Data.Name,p->Data.PhoneNum);
}
//2、传入一个节点,对其节点做数据更改
void change_data(listnode *p)
{
strcpy(p->Data.Name,"老王");
}
///查找的功能的回调/
bool find_name(datatype data1,datatype data2)
{
//按名字对比传入的两个节点 看名字是否匹配 是表示找到了 返回true
if(strcmp(data1.Name,data2.Name) == 0)
return true;
else
return false;
}
bool find_phone(datatype data1,datatype data2)
{
//按名字对比传入的两个节点 看名字是否匹配 是表示找到了 返回true
if(strcmp(data1.PhoneNum,data2.PhoneNum) == 0)
return true;
else
return false;
}
int main()
{
datatype data;
listnode *temp;
//①先创建一条空表
listnode *Head = List_Init();
//②测试头插
printf("插入节点-头插:\n");
for (int i = 0; i < 3; i++)
{
scanf("%s %s", data.Name, data.PhoneNum);
List_HeadInsert(Head, data);
}
List_Traverse(Head,print_data);
printf("查找测试:\n");
while(1)
{
scanf("%s",data.Name);
temp = List_find(Head,data,find_name);
if(temp == NULL)
printf("没找到\n");
else
{
List_DelNode(temp);
printf("找到了 并删除\n");
}
List_Traverse(Head,print_data);
}
// List_Traverse(Head,change_data);
// List_Traverse(Head,print_data);
}
内核链表
内核链表是 Linux 内核中使用的一种高效链表实现。它提供了一种灵活的数据结构,适用于在内核模块中管理不同类型的数据。内核链表的实现是基于双向链表,可以方便地进行插入、删除和迭代等操作。内核链表通常通过 <linux/list.h>
头文件提供。
内核链表头文件:
#ifndef __DLIST_H
#define __DLIST_H
/* This file is from Linux Kernel (include/linux/list.h)
* and modified by simply removing hardware prefetching of list items.
* Here by copyright, credits attributed to wherever they belong.
* Kulesh Shanmugasundaram (kulesh [squiggly] isis.poly.edu)
*/
/*
* Simple doubly linked list implementation.
*
* Some of the internal functions (“__xxx”) are useful when
* manipulating whole lists rather than single entries, as
* sometimes we already know the next/prev entries and we can
* generate better code by using them directly rather than
* using the generic single-entry routines.
*/
/**
* container_of - cast a member of a structure out to the containing structure
*
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
/*
* These are non-NULL pointers that will result in page faults
* under normal circumstances, used to verify that nobody uses
* non-initialized list entries.
*/
#define LIST_POISON1 ((void *) 0x00100100)
#define LIST_POISON2 ((void *) 0x00200)
struct list_head
{
struct list_head *prev;
struct list_head *next;
};
//节点构建
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)
//struct list_head* Head;
//LIST_HEAD(Head); == struct list_head Head = {&Head,&Head};
// struct list_head Head;
// Head.prev = &(Head);
// Head.next = &(Head) ;
// struct list_head *head = &Head;
//
// struct node
// {
// char a;
// int b;
// float c;
// };
// // struct node temp={10,-2,12.5};
// struct node temp={.b=-7,.c=1.23};
// char a[3] ={[1]=3,[2]=100};
// 宏定义语法规定只能有一条语句
// 如果需要多条语句,那就必须将多条语句放入一个do{}while(0)中使之成为一条复合语句
#define INIT_LIST_HEAD(ptr) \
do { \
(ptr)->next = (ptr); \
(ptr)->prev = (ptr); \
} while (0)
// struct list_head *Head = malloc(sizeof(struct list_head));
// INIT_LIST_HEAD(Head);
/*
插入节点的链式处理
*/
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
/**
头插
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
/**
尾插
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
/*
将同一条链表中的任意两个节点直接连接
*/
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
next->prev = prev;
prev->next = next;
}
/**
删除指定的节点entry 链式处理
*/
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = (void *) 0;
entry->prev = (void *) 0;
}
/**
从某条链表中删除一个节点 并以该节点尾开始创建一个空表--或者单纯让自己指向指向
*/
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
/**
移动节点list到head节点后--链式处理
*/
static inline void list_move(struct list_head *list,
struct list_head *head)
{
__list_del(list->prev, list->next);
list_add(list, head);
}
/**
移动节点list到head节点前--链式处理
*/
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del(list->prev, list->next);
list_add_tail(list, head);
}
/**
判断链表是否为空---前提是传入的head是某条链的头指针
*/
static inline int list_empty(struct list_head *head)
{
return head->next == head;
}
static inline void __list_splice(struct list_head *list,
struct list_head *head)
{
struct list_head *first = list->next;
struct list_head *last = list->prev;
struct list_head *at = head->next;
first->prev = head;
head->next = first;
last->next = at;
at->prev = last;
}
/**
* list_splice – join two lists
* @list: the new list to add.
* @head: the place to add it in the first list.
*/
static inline void list_splice(struct list_head *list, struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head);
}
/**
* list_splice_init – join two lists and reinitialise the emptied list.
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* The list at @list is reinitialised
*/
static inline void list_splice_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head);
INIT_LIST_HEAD(list);
}
}
/**
标准型链表节点--小结构体-----去找到包裹它的大结构体
((struct list_head *)NULL)->next = p;
*/
#define list_entry(ptr, type, member) ((type *)((char *)(ptr)-(char *)(&((type *)0)->member)))
/**
遍历每个节点---只写了个for循环头
*/
#define list_for_each(pos, head) for(pos = (head)->next; pos != (head); pos = pos->next)
/**
从尾结点往前遍历
*/
#define list_for_each_prev(pos, head) for (pos = (head)->prev; pos != (head);pos = pos->prev)
/**
* list_for_each_safe - iterate over a list safe against removal of list entry
* @pos: the &struct list_head to use as a loop counter.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop counter.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
/**
* list_for_each_entry_safe – iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop counter.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
#endif
内核链表结构
初始化链表
void INIT_LIST_HEAD(struct list_head *list);
头部插入
void list_add(struct list_head *new, struct list_head *head);
尾部插入
void list_add_tail(struct list_head *new, struct list_head *head);
删除节点
void list_del(struct list_head *entry);
遍历链表
list_for_each(pos, head) { // 访问 pos 指向的节点 },
list_for_each
是一个宏,用于遍历链表中的元素。获取包含数据的结构体
使用
list_entry
宏可以从链表节点获取包含该节点的结构体:struct my_struct *entry = list_entry(pos, struct my_struct, list);
关于内核链表
普通链表概念简单,操作方便,但存在有致命的缺陷,即:每一条链表都是特殊的,不具有通用 性。因为对每一种不同的数据,所构建出来的链表都是跟这些数据相关的,所有的操作函数也都是数 据密切相关的,换一种数据节点,则所有的操作函数都需要一一重新编写,这种缺陷对于一个具有成 千上万种数据节点的工程来说是灾难性的。
在普通链表的节点设计中,不同的链表所使用的指针不同,就直接导致操作函数的参数不同,在C 语言的环境下,无法统一这些所有的操作,这给编程开发带来了很大的麻烦,尤其在节点种类众多的场合。
分析上述问题,其产生的根本原因是链表节点的设计,没有把数据和逻辑分开,也就是将具体的数 据与组织这些数据的链表揉在一起,导致链表的操作不得已绑定了某个固定类型的数据。
既然是因为数据和链表指针混在一起导致了通用性问题,那么解决的思路就是将它们分开。将链表 逻辑单独抽出来,去掉节点内的具体数据,让节点只包含双向指针。这样的节点连接起来形成一条单 纯的链表如下所示:
接着,将这样的不含任何数据的链表,镶嵌在具体要用的串起来的数据节点之中,这样一来,就可 以将任何节点的链表操作完全统一了。
内核中的源码文件 list.h 实际上包含了两部分内容,一是内核链表,二是哈希链表。这个标准节点的用法,就是在实际用户数据节点中,镶嵌进去。为了表述简单,一般将用户的数据 节点称为大结构体,将标准节点称为小结构体,例如:
struct node // 大结构体
{
// 用户数据
datatype data;
...
// 标准链表
struct list_head list; // 小结构体
}
大小结构体转换
由于内核链表的所有操作都只针对标准的小结构体,与包裹它的大结构体没有关系,但是用户关心 的是实际数据所在的大结构体,因此一个最基本的问题是:如何快速方便地转换大小结构体指针。 要搞清楚这个问题,首先必须清楚用户节点的细节。假设大结构体的类型为 type,小结构体在用 户节点中的名称为 member,指向小结构体的指针为 ptr,现在要通过 ptr 求得指向大结构体的指针 p,它们的关系如下图所示:
#define list_entry(ptr, type, member)
2 ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
计算流程:
由于用户数据都存储于大结构体中,因此只要求得指针 p 即可获取所有的用户数据。
从图中很容易看 到:
p = ptr - offset ptr
是已知的,关键是 offset,而这个偏移量就是:
&((type *)0x00000000)->member &((NODE *)0xFD6E00E0)->member - 0xFD6E00E0; NODE p;
90 0 == 90
100 10 ==90
#define list_entry(ptr, type, member)
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
200 210 == 90
p = (NODE *)((char *)pos- (unsigned long)(&(((NODE *)&p)->list)))----offset
唯一需要解释的中间的数值0,这个0代表:如果以0地址为起始,那么小结构体member的地址值就是 它在大结构体中相对于大结构体起始地址的偏移量。 由于C语言特殊的语法,当我们对结构体中某个成员取地址时,那么程序并不需要操作内存中的任何数 据,而只会根据大小结构体的相对位置,纯粹计算地址的值。因此,虽然宏中出现了0,但并不涉及任 何内存数据的存取。 实际上,将这个内存地址0换成任意地址x都是可以的,只不过得到的就是相对于地址x的偏移量了,然 后又要减掉x以求的小结构体在大结构体中的偏移量,这个操作无疑是多余的。
于是,用户节点指针等于:
p = ptr - &((type *)0)->member;
最后,由于以上算式都是指针,为了让计算结果是字节,将所有的运算操作数强制类型转化为单字节 运算的整数或者char型指针即可。
其内核源码是:
#define list_entry(ptr, type, member) \ ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)- >member)))
通过上面的宏,我们可以直接从标准链表中获取用户大结构体指针,从而访问用户数据
用例
在引入list.h头文件后。我又在此基础上定义了一个新的头文件用来封装自己的数据和对内核链表的调用以实现功能。
my_list.h
#ifndef __DEF_LIST_H
#define __DEF_LIST_H
#include "kernel_list.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
// #ifndef DATATYPE //此条件决定了下面通用结构体的具体类型 如果在.c文件中有包含 则 使 //用头文件中数据类型---用户提供
// #define DATATYPE int //用户不提供 则默认int类型 防止编译报错
// #endif
// 原名 别名
typedef DATATYPE datatype;
typedef struct LIST
{
//数据域
datatype data;
//指针域 定义一个小结构体成员变量
struct list_head kernellist;
}node;
//内核链表数据域与指针域完全分开,函数只对列表用链式操作,需要用小结构体(指针域),访问整个大结构体来调用数据域,使用以下宏
//list_entry(pos,node,kernellist)
/
/*
def_name : void List_Traverse
功能 :遍历链表
参数1 :list_head 类型指针(指针域结构体指针)
参数2 :list_head 类型指针(指针域结构体指针)
参数3 :node 类型指针(包含数据域大结构体头节点指针)
参数4 :功能回调函数
参数5 :true : 正向遍历 false :反向遍历
*/
static void List_Traverse_direction(struct list_head *pos,struct list_head *pos1,node *Head,void (*function)(node *),bool direction)
{
if(direction == true)
{
list_for_each_safe(pos,pos1,&(Head->kernellist))
{
function(list_entry(pos,node,kernellist));
}
}
else{
list_for_each_save_prev(pos,pos1,&(Head->kernellist))
{
function(list_entry(pos,node,kernellist));
}
}
}
/*
def_name : node *list_Find_node
功能 :查找数据
参数1 :list_head 类型指针(指针域结构体指针)
参数2 :list_head 类型指针(指针域结构体指针)
参数3 :node 类型指针(包含数据域大结构体头节点指针)
参数4 :要查找节点的num
参数5 :查找回调函数
*/
static node *list_Find_node(struct list_head *pos,struct list_head *pos1,node *Head,datatype data,bool (*equal)(datatype,datatype))
{
list_for_each_safe(pos,pos1,&(Head->kernellist))
{
if(equal(list_entry(pos,node,kernellist)->data,data))
{
return list_entry(pos,node,kernellist);
}
}
return NULL;
}
/*
def_name : void List_Del_node
功能 :删除数据
参数1 :list_head 类型指针(指针域结构体指针)
参数2 :list_head 类型指针(指针域结构体指针)
参数3 :node 类型指针(包含数据域大结构体头节点指针)
参数4 :要删除节点数据
参数5 :删除回调函数(大结构体指针,指针域(小结构体指针),要删除的数据)
*/
static void List_Del_node(struct list_head *pos,struct list_head *pos1,node *Head,datatype data,void (*function)(node *,struct list_head *,datatype))
{
list_for_each_safe(pos,pos1,&(Head->kernellist))
{
function(list_entry(pos,node,kernellist),pos,data);
}
}
/*
def_name :void List_Replace_node
功能 :替换数据
参数1 :list_head 类型指针(指针域结构体指针)
参数2 :list_head 类型指针(指针域结构体指针)
参数3 :node 类型指针(包含数据域大结构体头节点指针)
参数4 :要找到节点数据
参数5 :要找到替换成的内容
参数6 :替换回调函数(大结构体指针,要找的数据,替换成的内容)
*/
static void List_Replace_node(struct list_head *pos,struct list_head *pos1,node *Head,datatype data,datatype R_data,void (*function)(node *,datatype,datatype))
{
list_for_each_safe(pos,pos1,&(Head->kernellist))
{
function(list_entry(pos,node,kernellist),data,R_data);
}
}
/*
def_name :void List_Add_direction
功能 :插入数据 /前插/后插
参数1 :list_head 类型指针(指针域结构体指针)
参数2 :list_head 类型指针(指针域结构体指针)
参数3 :node 类型指针(包含数据域大结构体头节点指针)
参数4 :要找到插入位置节点数据
参数5 :替换回调函数(插入节点大结构体指针,新节点大结构体指针,位置节点数据)
//插入回调函数 插入到后面
void add_newnode_behind(node *p,node *new,datatype data)
//插入回调函数 插入到前面
void add_newnode_ftont(node *p,node *new,datatype data)
*/
static void List_Add_direction(struct list_head *pos,struct list_head *pos1,node *Head,node *newnode,datatype data,void (*function)(node *,node*,datatype))
{
list_for_each_safe(pos,pos1,&(Head->kernellist))
{
function(list_entry(pos,node,kernellist),newnode,data);
}
}
/*
def_name :void List_Del_all
功能 :删除链表
参数1 :list_head 类型指针(指针域结构体指针)
参数2 :list_head 类型指针(指针域结构体指针)
参数3 :node 类型地址(包含数据域大结构体头节点地址)
参数3 :node 类型指针(包含数据域大结构体头节点指针)
*/
static void List_Del_all(struct list_head *pos,struct list_head *pos1,node **Head,node *_Hrad)
{
if(_Hrad->kernellist.next==&(_Hrad->kernellist))
{
free(*Head);
*Head = NULL;
}
list_for_each_safe(pos,pos1,&(_Hrad->kernellist))
{
list_del(pos);
free(list_entry(pos,node,kernellist));//找到的大结构体
}
free(*Head);
*Head = NULL;
}
/*
def_name :void Create_list
功能 :创建链表
参数1 :node 类型指针(包含数据域大结构体头节点指针)
*/
static node *List_Create()
{
node *Head = malloc(sizeof(node));
if(Head == NULL)
{
exit(0);
}
INIT_LIST_HEAD(&(Head->kernellist));//使用内核链表处理大结构体中的小结构体的链式关系
return Head;
}
/*
def_name :void Head_Insert
功能 :头插/尾插
参数1 :node 类型指针(包含数据域大结构体头节点指针)
参数2 :头/尾插回调函数
参数3 :要插入节点的个数
*/
static void Head_Tail_Insert(node *Head,void (*function)(node *),int j,bool direction)
{
int i;
for(i=0;i<j;i++)
{
node *newnode = malloc(sizeof(node));
function(newnode);
if(direction == true)
{
list_add_tail(&(newnode->kernellist),&(Head->kernellist));
}
else
{
list_add(&(newnode->kernellist),&(Head->kernellist));
}
}
}
/*
def_name :void Clean_List_data
功能 :清空数据域
*/
static void Clean_List_data(struct list_head *pos,struct list_head *pos1,node *Head)
{
datatype Adata;
list_for_each_safe(pos,pos1,&(Head->kernellist))
{
list_entry(pos,node,kernellist)->data = Adata;
}
}
#endif
main.c
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel_list.h"
//大结构体
typedef struct
{
int num;
char name[20];
}AdrB;
#define DATATYPE AdrB
#include "def_list.h"
//遍历回调函数
void print_data(node *p)
{
printf("%d\n",p->data.num);
printf("%s\n",p->data.name);
}
//查找回调函数
bool find_num(datatype data1,datatype data2)
{
if(data1.num == data2.num)
return true;
else
return false;
}
//修改回调函数
void modify_num(node *p,datatype data2,datatype data3)
{
if(p->data.num == data2.num)
{
p->data.num = data3.num;
}
}
//删除回调函数
void del_node_num(node *p,struct list_head *q,datatype data)
{
if(p->data.num == data.num)
{
list_del(q);
free(p);
}
}
//插入回调函数 插入到后面
void add_newnode_behind(node *p,node *new,datatype data)
{
if(p->data.num == data.num)
{
list_add(&(new->kernellist), &(p->kernellist));
}
}
//插入回调函数 插入到前面
void add_newnode_ftont(node *p,node *new,datatype data)
{
if(p->data.num == data.num)
{
list_add(&(new->kernellist), &(p->kernellist));
}
}
void get_data(node *new)
{
printf("num:");
scanf("%d",&(new->data.num));
printf("name:");
scanf("%s",new->data.name);
}
int main()
{
struct list_head *pos;
struct list_head *pos1;
AdrB DATA,R_data;
/*****创建链表范例语句*****/
node *Head =List_Create();
/*****新增头插尾插范例语句*****/
Head_Tail_Insert(Head,get_data,3,true);
/*****清空数据域范例语句*****/
// Clean_List_data(pos,pos1,Head);
// printf("\n");
/*****查找节点范例语句*****/
// scanf("%d",&DATA.num);
// node *find = list_Find_node(pos,pos1,Head,DATA,find_num);
// printf("你要找的是:%d",find->data.num);
/*****删除范例语句*****/
// scanf("%d",&DATA.num);
// List_Del_node(pos,pos1,Head,DATA,del_node_num);
// List_Replace_node(pos,pos1,Head,DATA,R_data,modify_num);
/*****插入范例语句在某节点之后*****/
// scanf("%d %d",&DATA.num,&R_data.num);
// node *new = malloc(sizeof(node));
// new->data.num = DATA.num;
// List_Add_direction(pos,pos1,Head,new,R_data,add_newnode_behind);
/*****插入范例语句在某节点之前*****/
// scanf("%d %d",&DATA.num,&R_data.num);
// node *new_1 = malloc(sizeof(node));
// new_1->data.num = DATA.num;
// List_Add_direction(pos,pos1,Head,new_1,R_data,add_newnode_ftont);
/*****销毁链表范例语句*****/
//List_Del_all(pos,pos1,&Head,Head);
/*****遍历链表范例语句*****/
//List_Traverse_direction(pos,pos1,Head,print_data,true);
List_Traverse_direction(pos,pos1,Head,print_data,true);
}
链表补充
双向链表的冒泡排序。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct AddrBook
{
//数据域
int num;
//指针域
struct AddrBook *next;
struct AddrBook *prec;
}AddrBook;//别名
void *List_Create()
{
struct AddrBook *head;
head = malloc(sizeof(AddrBook));
if(head == NULL)
{
exit(0);
}
head->next = head;
head->prec = head;
return head;
}
void List_Headlnsert(AddrBook *head,int pri)
{
AddrBook *newnode;
newnode = malloc(sizeof(AddrBook));
//数据域
newnode->num = pri;
//指针域处理
newnode->next = head->next;
newnode->prec = head;
head->next->prec = newnode;
head->next = newnode;
}
void VIEW(AddrBook *head)
{
AddrBook *p = head->prec;
while(p != head)
{
printf("num:%d\n",p->num);
p = p->prec;
}
}
static void List_SwapNode(AddrBook *PrevNode, AddrBook *LeftNode, AddrBook *RightNode, AddrBook *NextNode)
{
PrevNode->next = RightNode;
LeftNode->next = NextNode;
LeftNode->prec = RightNode;
RightNode->next=LeftNode;
RightNode->prec =PrevNode;
NextNode->prec = LeftNode;
}
void list(AddrBook *head)
{
int i=0,j=0;
AddrBook *m = head->next;
while(i<11)
{
while(j<11-i)
{
if(m->next!=head)
{
if((m->num) > (m->next->num))
{
List_SwapNode(m->prec, m, m->next, m->next->next);
}
}
j++;
m = m->next;
}
m=head->next;
j=0;
i++;
}
}
int main()
{
AddrBook *person = List_Create();
List_Headlnsert(person,9);
List_Headlnsert(person,1);
List_Headlnsert(person,7);
List_Headlnsert(person,7);
List_Headlnsert(person,2);
List_Headlnsert(person,9);
List_Headlnsert(person,2);
List_Headlnsert(person,3);
List_Headlnsert(person,3);
//swap_list(person->next,person->next->next);
//List_SwapNode(person, person->next, person->next->next,person->next->next->next );
list(person);
VIEW(person);
}
完结